# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/ require 'rails_helper' RSpec.describe Gql::Mutations::Ticket::Update, :aggregate_failures, type: :graphql do let(:query) do <<~QUERY mutation ticketUpdate($ticketId: ID!, $input: TicketUpdateInput!, $meta: TicketUpdateMetaInput) { ticketUpdate(ticketId: $ticketId, input: $input, meta: $meta) { ticket { id title group { name } priority { name } customer { fullname } owner { fullname } objectAttributeValues { attribute { name } value } } errors { message field exception } } } QUERY end let(:agent) { create(:agent, groups: [ Group.find_by(name: 'Users')]) } let(:customer) { create(:customer) } let(:user) { agent } let(:group) { agent.groups.first } let(:priority) { Ticket::Priority.last } let(:ticket) { create(:ticket, group: agent.groups.first, customer: customer) } let(:article_payload) { nil } let(:input_base_payload) do { title: 'Ticket Create Mutation Test', groupId: gql.id(group), priorityId: gql.id(priority), customer: { id: gql.id(customer) }, ownerId: gql.id(agent), article: article_payload # pending_time: 10.minutes.from_now, # type: ... } end let(:input_payload) { input_base_payload } let(:variables) { { ticketId: gql.id(ticket), input: input_payload } } let(:expected_base_response) do { 'id' => gql.id(Ticket.last), 'title' => 'Ticket Create Mutation Test', 'owner' => { 'fullname' => agent.fullname }, 'group' => { 'name' => agent.groups.first.name }, 'customer' => { 'fullname' => customer.fullname }, 'priority' => { 'name' => Ticket::Priority.last.name }, 'objectAttributeValues' => [], } end let(:expected_response) do expected_base_response end context 'when updating a ticket' do context 'with an agent', authenticated_as: :agent do it 'updates the attributes' do gql.execute(query, variables: variables) expect(gql.result.data[:ticket]).to eq(expected_response) end context 'without title' do let(:input_payload) { input_base_payload.tap { |h| h[:title] = ' ' } } it 'fails validation' do gql.execute(query, variables: variables) expect(gql.result.error_message).to include('Variable $input of type TicketUpdateInput! was provided invalid value for title') end end context 'with an article payload' do let(:article_payload) do { body: 'dummy', type: 'note', } end it 'adds a new article with a specific type' do expect { gql.execute(query, variables: variables) } .to change(Ticket::Article, :count).by(1) expect(Ticket.last.articles.last.type.name).to eq('note') end it 'adds a new article with a specific sender' do expect { gql.execute(query, variables: variables) } .to change(Ticket::Article, :count).by(1) expect(Ticket.last.articles.last.sender.name).to eq('Agent') end context 'with macro' do let(:new_title) { Faker::Lorem.word } let(:macro) { create(:macro, perform: { 'ticket.title' => { 'value' => new_title } }) } let(:variables) do { ticketId: gql.id(ticket), input: input_payload, meta: { macroId: gql.id(macro) } } end it 'adds a new article with time unit' do gql.execute(query, variables:) expect(ticket.reload).to have_attributes(title: new_title) end end context 'with time unit' do let(:time_accounting_enabled) { true } let(:exception) { Gql::Types::Enum::BaseEnum.graphql_compatible_name('Service::Ticket::Update::Validator::TimeAccounting::Error') } let(:accounted_time_type) { create(:ticket_time_accounting_type) } let(:variables) do { ticketId: gql.id(ticket), input: input_payload, meta: { skipValidators: [exception], }, } end let(:article_payload) do { body: 'dummy', type: 'web', timeUnit: 123, accountedTimeTypeId: gql.id(accounted_time_type), } end before do Setting.set('time_accounting', time_accounting_enabled) end it 'adds a new article with time unit' do expect { gql.execute(query, variables: variables) } .to change(Ticket::Article, :count).by(1) expect(Ticket.last.articles.last.ticket_time_accounting.time_unit).to eq(123) expect(Ticket.last.articles.last.ticket_time_accounting.type).to eq(accounted_time_type) end context 'when time accounting disabled' do let(:time_accounting_enabled) { false } it 'does not create ticket article' do expect { gql.execute(query, variables: variables) } .not_to change(Ticket::Article, :count) expect(gql.result.error_message) .to match('Time Accounting is not enabled') end end end context 'with active secure mailing (S/MIME)' do before do Setting.set('smime_integration', true) end it 'adds a new article' do expect { gql.execute(query, variables: variables) } .to change(Ticket::Article, :count).by(1) expect(Ticket.last.articles.last.type.name).to eq('note') end end end context 'with custom object_attribute', db_strategy: :reset do let(:object_attribute) do screens = { create: { 'admin.organization': { shown: true, required: false } } } create(:object_manager_attribute_text, object_name: 'Ticket', screens: screens).tap do |_oa| ObjectManager::Attribute.migration_execute end end let(:input_payload) do input_base_payload.merge( { objectAttributeValues: [ { name: object_attribute.name, value: 'object_attribute_value' } ] } ) end let(:expected_response) do expected_base_response.merge( { 'objectAttributeValues' => [{ 'attribute' => { 'name'=>object_attribute.name }, 'value' => 'object_attribute_value' }] } ) end it 'updates the attributes' do gql.execute(query, variables: variables) expect(gql.result.data[:ticket]).to eq(expected_response) end end context 'when moving the ticket into a group with only :change permission' do let(:group) { create(:group) } before do user.groups << group user.group_names_access_map = { user.groups.first.name => %w[read change], group.name => ['change'] } end it 'updates the ticket, but returns an error trying to access the new ticket' do gql.execute(query, variables: variables) expect(ticket.reload.group_id).to eq(group.id) expect(gql.result.payload['data']['ticketUpdate']).to eq({ 'ticket' => nil, 'errors' => nil }) # Mutation did run, but data retrieval was not authorized. expect(gql.result.payload['errors'].first['message']).to eq('Access forbidden by Gql::Types::TicketType') expect(gql.result.payload['errors'].first['extensions']['type']).to eq('Exceptions::Forbidden') end end context 'with no permission to the group' do let(:group) { create(:group) } it 'raises an error', :aggregate_failures do gql.execute(query, variables: variables) expect(gql.result.error_type).to eq(Exceptions::Forbidden) expect(gql.result.error_message).to eq('Access forbidden by Gql::Types::GroupType') end end context 'when ticket has a checklist and is being closed', current_user_id: 1 do let(:checklist) { create(:checklist, ticket: ticket) } let(:exception) { Gql::Types::Enum::BaseEnum.graphql_compatible_name('Service::Ticket::Update::Validator::ChecklistCompleted::Error') } let(:input_base_payload) do { title: 'Ticket Create Mutation Test', groupId: gql.id(group), priorityId: gql.id(priority), customer: { id: gql.id(customer) }, ownerId: gql.id(agent), article: article_payload, stateId: gql.id(Ticket::State.find_by(name: 'closed')), } end before do checklist gql.execute(query, variables: variables) end it 'returns a user error' do expect(gql.result.data).to eq({ 'ticket' => nil, 'errors' => [ 'message' => 'The ticket checklist is incomplete.', 'field' => nil, 'exception' => exception, ], }) end context 'when validator is being skipped' do let(:variables) do { ticketId: gql.id(ticket), input: input_payload, meta: { skipValidators: [exception], }, } end it 'updates the ticket' do expect(gql.result.data[:ticket]).to eq(expected_response) end end end end context 'with a customer', authenticated_as: :customer do let(:input_payload) { input_base_payload.tap { |h| h.delete(:customer) } } let(:expected_response) do expected_base_response.merge( { 'owner' => { 'fullname' => nil }, 'priority' => { 'name' => Ticket::Priority.where(default_create: true).first.name }, } ) end it 'updates the ticket with filtered values' do gql.execute(query, variables: variables) expect(gql.result.data[:ticket]).to eq(expected_response) end context 'when sending a different customerId' do let(:input_payload) { input_base_payload.tap { |h| h[:customer][:id] = gql.id(create(:customer)) } } it 'fails creating a ticket with permission exception' do gql.execute(query, variables: variables) expect(gql.result.error_type).to eq(Exceptions::Forbidden) expect(gql.result.error_message).to eq('Access forbidden by Gql::Types::UserType') end end context 'with an article payload' do let(:article_payload) do { body: 'dummy', type: 'web', } end it 'adds a new article with a specific type' do expect { gql.execute(query, variables: variables) } .to change(Ticket::Article, :count).by(1) expect(Ticket.last.articles.last.type.name).to eq('web') end it 'adds a new article with a specific sender' do expect { gql.execute(query, variables: variables) } .to change(Ticket::Article, :count).by(1) expect(Ticket.last.articles.last.sender.name).to eq('Customer') end end end end end