update_spec.rb 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. require 'rails_helper'
  3. RSpec.describe Gql::Mutations::Ticket::Update, :aggregate_failures, type: :graphql do
  4. let(:query) do
  5. <<~QUERY
  6. mutation ticketUpdate($ticketId: ID!, $input: TicketUpdateInput!, $meta: TicketUpdateMetaInput) {
  7. ticketUpdate(ticketId: $ticketId, input: $input, meta: $meta) {
  8. ticket {
  9. id
  10. title
  11. group {
  12. name
  13. }
  14. priority {
  15. name
  16. }
  17. customer {
  18. fullname
  19. }
  20. owner {
  21. fullname
  22. }
  23. objectAttributeValues {
  24. attribute {
  25. name
  26. }
  27. value
  28. }
  29. }
  30. errors {
  31. message
  32. field
  33. exception
  34. }
  35. }
  36. }
  37. QUERY
  38. end
  39. let(:agent) { create(:agent, groups: [ Group.find_by(name: 'Users')]) }
  40. let(:customer) { create(:customer) }
  41. let(:user) { agent }
  42. let(:group) { agent.groups.first }
  43. let(:priority) { Ticket::Priority.last }
  44. let(:ticket) { create(:ticket, group: agent.groups.first, customer: customer) }
  45. let(:article_payload) { nil }
  46. let(:input_base_payload) do
  47. {
  48. title: 'Ticket Create Mutation Test',
  49. groupId: gql.id(group),
  50. priorityId: gql.id(priority),
  51. customer: { id: gql.id(customer) },
  52. ownerId: gql.id(agent),
  53. article: article_payload
  54. # pending_time: 10.minutes.from_now,
  55. # type: ...
  56. }
  57. end
  58. let(:input_payload) { input_base_payload }
  59. let(:variables) { { ticketId: gql.id(ticket), input: input_payload } }
  60. let(:expected_base_response) do
  61. {
  62. 'id' => gql.id(Ticket.last),
  63. 'title' => 'Ticket Create Mutation Test',
  64. 'owner' => { 'fullname' => agent.fullname },
  65. 'group' => { 'name' => agent.groups.first.name },
  66. 'customer' => { 'fullname' => customer.fullname },
  67. 'priority' => { 'name' => Ticket::Priority.last.name },
  68. 'objectAttributeValues' => [],
  69. }
  70. end
  71. let(:expected_response) do
  72. expected_base_response
  73. end
  74. context 'when updating a ticket' do
  75. context 'with an agent', authenticated_as: :agent do
  76. it 'updates the attributes' do
  77. gql.execute(query, variables: variables)
  78. expect(gql.result.data[:ticket]).to eq(expected_response)
  79. end
  80. context 'without title' do
  81. let(:input_payload) { input_base_payload.tap { |h| h[:title] = ' ' } }
  82. it 'fails validation' do
  83. gql.execute(query, variables: variables)
  84. expect(gql.result.error_message).to include('Variable $input of type TicketUpdateInput! was provided invalid value for title')
  85. end
  86. end
  87. context 'with an article payload' do
  88. let(:article_payload) do
  89. {
  90. body: 'dummy',
  91. type: 'note',
  92. }
  93. end
  94. it 'adds a new article with a specific type' do
  95. expect { gql.execute(query, variables: variables) }
  96. .to change(Ticket::Article, :count).by(1)
  97. expect(Ticket.last.articles.last.type.name).to eq('note')
  98. end
  99. it 'adds a new article with a specific sender' do
  100. expect { gql.execute(query, variables: variables) }
  101. .to change(Ticket::Article, :count).by(1)
  102. expect(Ticket.last.articles.last.sender.name).to eq('Agent')
  103. end
  104. context 'with macro' do
  105. let(:new_title) { Faker::Lorem.word }
  106. let(:macro) { create(:macro, perform: { 'ticket.title' => { 'value' => new_title } }) }
  107. let(:variables) do
  108. {
  109. ticketId: gql.id(ticket),
  110. input: input_payload,
  111. meta: {
  112. macroId: gql.id(macro)
  113. }
  114. }
  115. end
  116. it 'adds a new article with time unit' do
  117. gql.execute(query, variables:)
  118. expect(ticket.reload).to have_attributes(title: new_title)
  119. end
  120. end
  121. context 'with time unit' do
  122. let(:time_accounting_enabled) { true }
  123. let(:exception) { Gql::Types::Enum::BaseEnum.graphql_compatible_name('Service::Ticket::Update::Validator::TimeAccounting::Error') }
  124. let(:accounted_time_type) { create(:ticket_time_accounting_type) }
  125. let(:variables) do
  126. {
  127. ticketId: gql.id(ticket),
  128. input: input_payload,
  129. meta: {
  130. skipValidators: [exception],
  131. },
  132. }
  133. end
  134. let(:article_payload) do
  135. {
  136. body: 'dummy',
  137. type: 'web',
  138. timeUnit: 123,
  139. accountedTimeTypeId: gql.id(accounted_time_type),
  140. }
  141. end
  142. before do
  143. Setting.set('time_accounting', time_accounting_enabled)
  144. end
  145. it 'adds a new article with time unit' do
  146. expect { gql.execute(query, variables: variables) }
  147. .to change(Ticket::Article, :count).by(1)
  148. expect(Ticket.last.articles.last.ticket_time_accounting.time_unit).to eq(123)
  149. expect(Ticket.last.articles.last.ticket_time_accounting.type).to eq(accounted_time_type)
  150. end
  151. context 'when time accounting disabled' do
  152. let(:time_accounting_enabled) { false }
  153. it 'does not create ticket article' do
  154. expect { gql.execute(query, variables: variables) }
  155. .not_to change(Ticket::Article, :count)
  156. expect(gql.result.error_message)
  157. .to match('Time Accounting is not enabled')
  158. end
  159. end
  160. end
  161. context 'with active secure mailing (S/MIME)' do
  162. before do
  163. Setting.set('smime_integration', true)
  164. end
  165. it 'adds a new article' do
  166. expect { gql.execute(query, variables: variables) }
  167. .to change(Ticket::Article, :count).by(1)
  168. expect(Ticket.last.articles.last.type.name).to eq('note')
  169. end
  170. end
  171. end
  172. context 'with custom object_attribute', db_strategy: :reset do
  173. let(:object_attribute) do
  174. screens = { create: { 'admin.organization': { shown: true, required: false } } }
  175. create(:object_manager_attribute_text, object_name: 'Ticket', screens: screens).tap do |_oa|
  176. ObjectManager::Attribute.migration_execute
  177. end
  178. end
  179. let(:input_payload) do
  180. input_base_payload.merge(
  181. {
  182. objectAttributeValues: [ { name: object_attribute.name, value: 'object_attribute_value' } ]
  183. }
  184. )
  185. end
  186. let(:expected_response) do
  187. expected_base_response.merge(
  188. {
  189. 'objectAttributeValues' => [{ 'attribute' => { 'name'=>object_attribute.name }, 'value' => 'object_attribute_value' }]
  190. }
  191. )
  192. end
  193. it 'updates the attributes' do
  194. gql.execute(query, variables: variables)
  195. expect(gql.result.data[:ticket]).to eq(expected_response)
  196. end
  197. end
  198. context 'when moving the ticket into a group with only :change permission' do
  199. let(:group) { create(:group) }
  200. before do
  201. user.groups << group
  202. user.group_names_access_map = { user.groups.first.name => %w[read change], group.name => ['change'] }
  203. end
  204. it 'updates the ticket, but returns an error trying to access the new ticket' do
  205. gql.execute(query, variables: variables)
  206. expect(ticket.reload.group_id).to eq(group.id)
  207. expect(gql.result.payload['data']['ticketUpdate']).to eq({ 'ticket' => nil, 'errors' => nil }) # Mutation did run, but data retrieval was not authorized.
  208. expect(gql.result.payload['errors'].first['message']).to eq('Access forbidden by Gql::Types::TicketType')
  209. expect(gql.result.payload['errors'].first['extensions']['type']).to eq('Exceptions::Forbidden')
  210. end
  211. end
  212. context 'with no permission to the group' do
  213. let(:group) { create(:group) }
  214. it 'raises an error', :aggregate_failures do
  215. gql.execute(query, variables: variables)
  216. expect(gql.result.error_type).to eq(Exceptions::Forbidden)
  217. expect(gql.result.error_message).to eq('Access forbidden by Gql::Types::GroupType')
  218. end
  219. end
  220. context 'when ticket has a checklist and is being closed', current_user_id: 1 do
  221. let(:checklist) { create(:checklist, ticket: ticket) }
  222. let(:exception) { Gql::Types::Enum::BaseEnum.graphql_compatible_name('Service::Ticket::Update::Validator::ChecklistCompleted::Error') }
  223. let(:input_base_payload) do
  224. {
  225. title: 'Ticket Create Mutation Test',
  226. groupId: gql.id(group),
  227. priorityId: gql.id(priority),
  228. customer: { id: gql.id(customer) },
  229. ownerId: gql.id(agent),
  230. article: article_payload,
  231. stateId: gql.id(Ticket::State.find_by(name: 'closed')),
  232. }
  233. end
  234. before do
  235. checklist
  236. gql.execute(query, variables: variables)
  237. end
  238. it 'returns a user error' do
  239. expect(gql.result.data).to eq({
  240. 'ticket' => nil,
  241. 'errors' => [
  242. 'message' => 'The ticket checklist is incomplete.',
  243. 'field' => nil,
  244. 'exception' => exception,
  245. ],
  246. })
  247. end
  248. context 'when validator is being skipped' do
  249. let(:variables) do
  250. {
  251. ticketId: gql.id(ticket),
  252. input: input_payload,
  253. meta: {
  254. skipValidators: [exception],
  255. },
  256. }
  257. end
  258. it 'updates the ticket' do
  259. expect(gql.result.data[:ticket]).to eq(expected_response)
  260. end
  261. end
  262. end
  263. end
  264. context 'with a customer', authenticated_as: :customer do
  265. let(:input_payload) { input_base_payload.tap { |h| h.delete(:customer) } }
  266. let(:expected_response) do
  267. expected_base_response.merge(
  268. {
  269. 'owner' => { 'fullname' => nil },
  270. 'priority' => { 'name' => Ticket::Priority.where(default_create: true).first.name },
  271. }
  272. )
  273. end
  274. it 'updates the ticket with filtered values' do
  275. gql.execute(query, variables: variables)
  276. expect(gql.result.data[:ticket]).to eq(expected_response)
  277. end
  278. context 'when sending a different customerId' do
  279. let(:input_payload) { input_base_payload.tap { |h| h[:customer][:id] = gql.id(create(:customer)) } }
  280. it 'fails creating a ticket with permission exception' do
  281. gql.execute(query, variables: variables)
  282. expect(gql.result.error_type).to eq(Exceptions::Forbidden)
  283. expect(gql.result.error_message).to eq('Access forbidden by Gql::Types::UserType')
  284. end
  285. end
  286. context 'with an article payload' do
  287. let(:article_payload) do
  288. {
  289. body: 'dummy',
  290. type: 'web',
  291. }
  292. end
  293. it 'adds a new article with a specific type' do
  294. expect { gql.execute(query, variables: variables) }
  295. .to change(Ticket::Article, :count).by(1)
  296. expect(Ticket.last.articles.last.type.name).to eq('web')
  297. end
  298. it 'adds a new article with a specific sender' do
  299. expect { gql.execute(query, variables: variables) }
  300. .to change(Ticket::Article, :count).by(1)
  301. expect(Ticket.last.articles.last.sender.name).to eq('Customer')
  302. end
  303. end
  304. end
  305. end
  306. end