record_loader_spec.rb 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. require 'rails_helper'
  3. RSpec.describe Gql::RecordLoader, :aggregate_failures, authenticated_as: :agent, type: :graphql do
  4. let(:agent) { create(:agent) }
  5. let(:debug) { false }
  6. def trace_queries(total_queries, uncached_queries)
  7. callback = lambda do |*, payload|
  8. total_queries[payload[:name]] ||= 0
  9. total_queries[payload[:name]] += 1
  10. if !payload[:cached]
  11. uncached_queries[payload[:name]] ||= 0
  12. uncached_queries[payload[:name]] += 1
  13. end
  14. next if !debug
  15. # rubocop:disable Rails/Output
  16. puts payload[:name], payload[:sql], payload[:cached]
  17. caller.reject { |line| line.match(%r{gems|spec}) }.first(30).each do |relevant_caller_line|
  18. puts(" ↳ #{relevant_caller_line.sub(Rails.root.join('/').to_s, '')}")
  19. end
  20. puts ''
  21. # rubocop:enable Rails/Output
  22. end
  23. ActiveSupport::Notifications.subscribed(callback, 'sql.active_record') do
  24. ActiveRecord::Base.cache do
  25. # ActiveRecord::Base.logger = Logger.new(STDOUT)
  26. return yield
  27. end
  28. end
  29. end
  30. context 'when querying multiple tickets' do
  31. before do
  32. 5.times do
  33. organization = create(:organization)
  34. user = create(:user, organization: organization)
  35. group = create(:group)
  36. 5.times do
  37. source_ticket = create(:ticket, customer: user, organization: organization, group: group, created_by_id: user.id, state: Ticket::State.all.sample)
  38. create(:ticket_article, ticket_id: source_ticket.id, from: 'asdf1@record_loader_test.org', created_by_id: user.id)
  39. end
  40. agent.group_names_access_map = Group.all.to_h { |g| [g.name, ['full']] }
  41. end
  42. end
  43. let(:overview_id) do
  44. condition = {
  45. 'article.from' => {
  46. operator: 'contains',
  47. value: 'record_loader_test.org',
  48. },
  49. }
  50. overview = create(:overview, condition: condition)
  51. gql.id(overview)
  52. end
  53. let(:query) do
  54. <<~QUERY
  55. query ticketsByOverview(
  56. $overviewId: ID!
  57. $pageSize: Int = 10
  58. ) {
  59. ticketsByOverview(
  60. overviewId: $overviewId
  61. first: $pageSize
  62. ) {
  63. totalCount
  64. edges {
  65. node {
  66. id
  67. internalId
  68. number
  69. title
  70. createdAt
  71. updatedAt
  72. owner {
  73. firstname
  74. lastname
  75. fullname
  76. }
  77. customer {
  78. firstname
  79. lastname
  80. fullname
  81. }
  82. organization {
  83. name
  84. }
  85. state {
  86. name
  87. stateType {
  88. name
  89. }
  90. }
  91. group {
  92. name
  93. }
  94. priority {
  95. name
  96. uiColor
  97. defaultCreate
  98. }
  99. }
  100. cursor
  101. }
  102. pageInfo {
  103. endCursor
  104. hasNextPage
  105. }
  106. }
  107. }
  108. QUERY
  109. end
  110. it 'performs only the expected amount of DB queries' do
  111. # Create variables here and not via let(), otherwise the objects would be instantiated later in the traced block.
  112. variables = { overviewId: overview_id }
  113. total_queries = {}
  114. uncached_queries = {}
  115. trace_queries(total_queries, uncached_queries) do
  116. gql.execute(query, variables: variables)
  117. end
  118. expect(gql.result.nodes.count).to eq(10)
  119. expect(total_queries).to include(
  120. {
  121. 'Overview Load' => 1,
  122. 'ObjectLookup Load' => 1,
  123. 'Permission Load' => 6,
  124. 'Permission Exists?' => 6,
  125. 'Group Load' => 11,
  126. 'UserGroup Exists?' => 4,
  127. 'Ticket Load' => 1,
  128. # 'Ticket Exists?' => 1,
  129. 'User Load' => 2,
  130. 'Organization Load' => 1,
  131. 'Ticket::State Load' => 1,
  132. 'Ticket::Priority Load' => 1,
  133. 'Ticket::StateType Load' => 1
  134. }
  135. )
  136. expect(uncached_queries).to include(
  137. {
  138. 'Overview Load' => 1,
  139. 'ObjectLookup Load' => 1,
  140. 'Permission Load' => 6,
  141. 'Permission Exists?' => 6,
  142. 'Group Load' => 3,
  143. 'UserGroup Exists?' => 4,
  144. 'Ticket Load' => 1,
  145. # 'Ticket Exists?' => 1,
  146. 'User Load' => 2,
  147. 'Organization Load' => 1,
  148. 'Ticket::State Load' => 1,
  149. 'Ticket::Priority Load' => 1,
  150. 'Ticket::StateType Load' => 1
  151. }
  152. )
  153. end
  154. end
  155. context 'when querying one ticket with many articles' do
  156. before do
  157. create_list(:user, 10, organization: create(:organization))
  158. end
  159. let(:organization_id) { gql.id(Organization.last) }
  160. let(:query) do
  161. <<~QUERY
  162. query organization($organizationId: ID, $organizationInternalId: Int) {
  163. organization( organization: { organizationId: $organizationId, organizationInternalId: $organizationInternalId } ) {
  164. id
  165. name
  166. shared
  167. domain
  168. domainAssignment
  169. active
  170. note
  171. ticketsCount {
  172. open
  173. closed
  174. }
  175. members{
  176. edges {
  177. node {
  178. firstname
  179. lastname
  180. }
  181. }
  182. }
  183. }
  184. }
  185. QUERY
  186. end
  187. it 'performs only the expected amount of DB queries' do
  188. variables = { organizationId: organization_id }
  189. total_queries = {}
  190. uncached_queries = {}
  191. trace_queries(total_queries, uncached_queries) do
  192. gql.execute(query, variables: variables)
  193. end
  194. expect(gql.result.data['id']).to eq(organization_id)
  195. expect(total_queries).to include(
  196. {
  197. 'Permission Load' => 5,
  198. 'Permission Exists?' => 5,
  199. 'User Load' => 2,
  200. 'Organization Load' => 1,
  201. }
  202. )
  203. expect(uncached_queries).to include(
  204. {
  205. 'Permission Load' => 4,
  206. 'Permission Exists?' => 5,
  207. 'User Load' => 2,
  208. 'Organization Load' => 1,
  209. }
  210. )
  211. end
  212. end
  213. end