record_loader_spec.rb 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  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' => 5,
  124. 'Group Load' => 11,
  125. 'UserGroup Exists?' => 4,
  126. 'Ticket Load' => 1,
  127. # 'Ticket Exists?' => 1,
  128. 'User Load' => 2,
  129. 'Organization Load' => 1,
  130. 'Ticket::State Load' => 1,
  131. 'Ticket::Priority Load' => 1,
  132. 'Ticket::StateType Load' => 1
  133. }
  134. )
  135. expect(uncached_queries).to include(
  136. {
  137. 'Overview Load' => 1,
  138. 'ObjectLookup Load' => 1,
  139. 'Permission Load' => 5,
  140. 'Group Load' => 3,
  141. 'UserGroup Exists?' => 4,
  142. 'Ticket Load' => 1,
  143. # 'Ticket Exists?' => 1,
  144. 'User Load' => 2,
  145. 'Organization Load' => 1,
  146. 'Ticket::State Load' => 1,
  147. 'Ticket::Priority Load' => 1,
  148. 'Ticket::StateType Load' => 1
  149. }
  150. )
  151. end
  152. end
  153. context 'when querying one ticket with many articles' do
  154. before do
  155. create_list(:user, 10, organization: create(:organization))
  156. end
  157. let(:organization_id) { gql.id(Organization.last) }
  158. let(:query) do
  159. <<~QUERY
  160. query organization($organizationId: ID, $organizationInternalId: Int) {
  161. organization( organization: { organizationId: $organizationId, organizationInternalId: $organizationInternalId } ) {
  162. id
  163. name
  164. shared
  165. domain
  166. domainAssignment
  167. active
  168. note
  169. ticketsCount {
  170. open
  171. closed
  172. }
  173. members{
  174. edges {
  175. node {
  176. firstname
  177. lastname
  178. }
  179. }
  180. }
  181. }
  182. }
  183. QUERY
  184. end
  185. it 'performs only the expected amount of DB queries' do
  186. variables = { organizationId: organization_id }
  187. total_queries = {}
  188. uncached_queries = {}
  189. trace_queries(total_queries, uncached_queries) do
  190. gql.execute(query, variables: variables)
  191. end
  192. expect(gql.result.data['id']).to eq(organization_id)
  193. expect(total_queries).to include(
  194. {
  195. 'Permission Load' => 3,
  196. 'User Load' => 2,
  197. 'Organization Load' => 1,
  198. }
  199. )
  200. expect(uncached_queries).to include(
  201. {
  202. 'Permission Load' => 3,
  203. 'User Load' => 2,
  204. 'Organization Load' => 1,
  205. }
  206. )
  207. end
  208. end
  209. end