ticket_spec.rb 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. require 'rails_helper'
  3. RSpec.describe Gql::Queries::Ticket, current_user_id: 1, type: :graphql do
  4. context 'when fetching tickets' do
  5. let(:agent) { create(:agent) }
  6. let(:query) do
  7. <<~QUERY
  8. query ticket($ticketId: ID, $ticketInternalId: Int, $ticketNumber: String) {
  9. ticket(
  10. ticket: {
  11. ticketId: $ticketId
  12. ticketInternalId: $ticketInternalId
  13. ticketNumber: $ticketNumber
  14. }
  15. ) {
  16. id
  17. internalId
  18. number
  19. title
  20. owner {
  21. id
  22. firstname
  23. email
  24. createdBy {
  25. internalId
  26. }
  27. updatedBy {
  28. internalId
  29. }
  30. }
  31. customer {
  32. id
  33. firstname
  34. email
  35. }
  36. organization {
  37. name
  38. }
  39. tags
  40. subscribed
  41. mentions(first: 20) {
  42. edges {
  43. node {
  44. user {
  45. id
  46. }
  47. userTicketAccess {
  48. agentReadAccess
  49. }
  50. }
  51. cursor
  52. }
  53. }
  54. policy {
  55. update
  56. destroy
  57. followUp
  58. agentReadAccess
  59. agentUpdateAccess
  60. createMentions
  61. }
  62. timeUnitsPerType {
  63. name
  64. timeUnit
  65. }
  66. stateColorCode
  67. checklist {
  68. name
  69. }
  70. referencingChecklistTickets {
  71. id
  72. }
  73. #{additional_query_fields}
  74. }
  75. }
  76. QUERY
  77. end
  78. let(:additional_query_fields) { '' }
  79. let(:variables) { { ticketId: gql.id(ticket) } }
  80. let(:ticket) do
  81. create(:ticket).tap do |t|
  82. t.tag_add('tag1', 1)
  83. t.tag_add('tag2', 1)
  84. end
  85. end
  86. let!(:checklist) do
  87. create(:checklist, ticket: ticket, item_count: 1, created_by: agent, updated_by: agent).tap do |checklist|
  88. checklist.items.last.update!(text: "Ticket##{another_ticket.number}", ticket_id: another_ticket.id)
  89. checklist.reload
  90. end
  91. end
  92. let!(:another_ticket) do
  93. create(:ticket, group: ticket.group, state: Ticket::State.find_by(name: 'new')).tap do |t|
  94. create(:checklist, ticket: t, item_count: 1, created_by: agent, updated_by: agent).tap do |checklist|
  95. checklist.items.last.update!(text: "Ticket##{ticket.number}", ticket_id: ticket.id)
  96. checklist.reload
  97. end
  98. end
  99. end
  100. before do
  101. setup if defined?(setup)
  102. gql.execute(query, variables: variables)
  103. end
  104. context 'with an agent', authenticated_as: :agent do
  105. context 'with permission' do
  106. let(:agent) { create(:agent, groups: [ticket.group]) }
  107. let(:base_expected_result) do
  108. {
  109. 'id' => gql.id(ticket),
  110. 'internalId' => ticket.id,
  111. 'number' => ticket.number,
  112. # Agent is allowed to see user data
  113. 'owner' => include(
  114. 'firstname' => ticket.owner.firstname,
  115. 'email' => ticket.owner.email,
  116. 'createdBy' => { 'internalId' => 1 },
  117. 'updatedBy' => { 'internalId' => 1 },
  118. ),
  119. 'tags' => %w[tag1 tag2],
  120. 'policy' => {
  121. 'agentReadAccess' => true,
  122. 'agentUpdateAccess' => true,
  123. 'createMentions' => true,
  124. 'destroy' => false,
  125. 'followUp' => true,
  126. 'update' => true
  127. },
  128. 'stateColorCode' => 'open',
  129. 'checklist' => {
  130. 'name' => checklist.name
  131. },
  132. 'referencingChecklistTickets' => [
  133. {
  134. 'id' => gql.id(another_ticket)
  135. }
  136. ]
  137. }
  138. end
  139. let(:expected_result) { base_expected_result }
  140. shared_examples 'finds the ticket' do
  141. it 'finds the ticket' do
  142. expect(gql.result.data).to include(expected_result)
  143. end
  144. end
  145. context 'when fetching a ticket by ticketId' do
  146. include_examples 'finds the ticket'
  147. end
  148. context 'when fetching a ticket by ticketInternalId' do
  149. let(:variables) { { ticketInternalId: ticket.id } }
  150. include_examples 'finds the ticket'
  151. end
  152. context 'when fetching a ticket by ticketNumber' do
  153. let(:variables) { { ticketNumber: ticket.number } }
  154. include_examples 'finds the ticket'
  155. end
  156. context 'when locator is missing' do
  157. let(:variables) { {} }
  158. it 'raises an exception' do
  159. expect(gql.result.error_type).to eq(GraphQL::Schema::Validator::ValidationFailedError)
  160. end
  161. end
  162. context 'with having checklist feature disabled' do
  163. let(:setup) do
  164. Setting.set('checklist', false)
  165. end
  166. let(:expected_result) { base_expected_result.merge({ 'checklist' => nil, 'referencingChecklistTickets' => nil }) }
  167. include_examples 'finds the ticket'
  168. end
  169. context 'with having time accounting enabled' do
  170. let(:ticket_time_accounting_types) { create_list(:ticket_time_accounting_type, 2) }
  171. let(:ticket_time_accounting) { create(:ticket_time_accounting, ticket: ticket, time_unit: 50) }
  172. let(:ticket_time_accounting_with_type) { create(:ticket_time_accounting, ticket: ticket, time_unit: 25, type: ticket_time_accounting_types[0]) }
  173. let(:ticket_time_accounting_with_type2) { create(:ticket_time_accounting, ticket: ticket, time_unit: 250, type: ticket_time_accounting_types[1]) }
  174. let(:setup) do
  175. Setting.set('time_accounting', true)
  176. Setting.set('time_accounting_types', true)
  177. ticket_time_accounting_with_type2 && ticket_time_accounting_with_type && ticket_time_accounting
  178. end
  179. it 'contains time unit entries grouped by type with a sum' do
  180. expect(gql.result.data[:timeUnitsPerType]).to eq([
  181. {
  182. 'name' => ticket_time_accounting_types[1].name,
  183. 'timeUnit' => 250.0,
  184. },
  185. {
  186. 'name' => 'None',
  187. 'timeUnit' => 50.0,
  188. },
  189. {
  190. 'name' => ticket_time_accounting_types[0].name,
  191. 'timeUnit' => 25.0,
  192. },
  193. ])
  194. end
  195. end
  196. context 'when subscribed' do
  197. let(:other_user) { create(:agent, groups: [ticket.group]) }
  198. before do
  199. Mention.subscribe! ticket, agent
  200. Mention.subscribe! ticket, other_user
  201. other_user.update(active: false)
  202. gql.execute(query, variables: variables)
  203. end
  204. it 'returns subscribed' do
  205. expect(gql.result.data).to include('subscribed' => true)
  206. end
  207. it 'returns user and access information in subscribers list' do
  208. expect(gql.result.data.dig('mentions', 'edges'))
  209. .to include(include('node' => include('user' => include('id' => gql.id(agent)), 'userTicketAccess' => { 'agentReadAccess' => true })))
  210. end
  211. it 'does not return inactive users' do
  212. expect(gql.result.data.dig('mentions', 'edges').count).to be(1)
  213. end
  214. end
  215. context 'with usage of issue tracker references' do
  216. let(:ticket) do
  217. Setting.set('github_integration', true)
  218. create(:ticket, preferences: { 'github' => { 'issue_links' => ['https://github.com/example/example/issues/1234'] } })
  219. end
  220. let(:additional_query_fields) do
  221. <<~ADDITIONALFIELDS
  222. externalReferences {
  223. github
  224. gitlab
  225. }
  226. ADDITIONALFIELDS
  227. end
  228. it 'contains issue tracker references' do
  229. expect(gql.result.data).to include(
  230. 'externalReferences' => include({
  231. 'github' => ['https://github.com/example/example/issues/1234'],
  232. 'gitlab' => nil
  233. })
  234. )
  235. end
  236. end
  237. end
  238. context 'without permission' do
  239. it 'raises authorization error' do
  240. expect(gql.result.error_type).to eq(Exceptions::Forbidden)
  241. end
  242. end
  243. context 'without ticket' do
  244. let(:ticket) { create(:ticket).tap(&:destroy) }
  245. let(:checklist) { nil }
  246. let(:another_ticket) { nil }
  247. it 'fetches no ticket' do
  248. expect(gql.result.error_type).to eq(ActiveRecord::RecordNotFound)
  249. end
  250. end
  251. end
  252. context 'with a customer', authenticated_as: :customer do
  253. let(:customer) { create(:customer) }
  254. let(:ticket) { create(:ticket, customer: customer) }
  255. let(:expected_result) do
  256. {
  257. 'id' => gql.id(ticket),
  258. 'internalId' => ticket.id,
  259. 'number' => ticket.number,
  260. # Customer is not allowed to see data of other users
  261. 'owner' => include(
  262. 'firstname' => ticket.owner.firstname,
  263. 'email' => nil,
  264. 'createdBy' => nil,
  265. 'updatedBy' => nil,
  266. ),
  267. # Customer may see their own data
  268. 'customer' => include(
  269. 'firstname' => customer.firstname,
  270. 'email' => customer.email,
  271. ),
  272. 'policy' => {
  273. 'agentReadAccess' => false,
  274. 'agentUpdateAccess' => false,
  275. 'createMentions' => false,
  276. 'destroy' => false,
  277. 'followUp' => true,
  278. 'update' => true
  279. },
  280. 'checklist' => nil,
  281. 'referencingChecklistTickets' => nil,
  282. }
  283. end
  284. it 'finds the ticket, but without data of other users' do
  285. expect(gql.result.data).to include(expected_result)
  286. end
  287. end
  288. it_behaves_like 'graphql responds with error if unauthenticated'
  289. end
  290. end