record_loader_spec.rb 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  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. id
  89. name
  90. }
  91. }
  92. group {
  93. name
  94. }
  95. priority {
  96. name
  97. uiColor
  98. defaultCreate
  99. }
  100. }
  101. cursor
  102. }
  103. pageInfo {
  104. endCursor
  105. hasNextPage
  106. }
  107. }
  108. }
  109. QUERY
  110. end
  111. it 'performs only the expected amount of DB queries' do
  112. # Create variables here and not via let(), otherwise the objects would be instantiated later in the traced block.
  113. variables = { overviewId: overview_id }
  114. total_queries = {}
  115. uncached_queries = {}
  116. trace_queries(total_queries, uncached_queries) do
  117. gql.execute(query, variables: variables)
  118. end
  119. expect(gql.result.nodes.count).to eq(10)
  120. expect(total_queries).to include(
  121. {
  122. 'Overview Load' => 1,
  123. 'ObjectLookup Load' => 1,
  124. 'Permission Load' => 5,
  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' => 5,
  141. 'Group Load' => 3,
  142. 'UserGroup Exists?' => 4,
  143. 'Ticket Load' => 1,
  144. # 'Ticket Exists?' => 1,
  145. 'User Load' => 2,
  146. 'Organization Load' => 1,
  147. 'Ticket::State Load' => 1,
  148. 'Ticket::Priority Load' => 1,
  149. 'Ticket::StateType Load' => 1
  150. }
  151. )
  152. end
  153. end
  154. context 'when querying one ticket with many articles' do
  155. before do
  156. create_list(:user, 10, organization: create(:organization))
  157. end
  158. let(:organization_id) { gql.id(Organization.last) }
  159. let(:query) do
  160. <<~QUERY
  161. query organization($organizationId: ID, $organizationInternalId: Int) {
  162. organization( organization: { organizationId: $organizationId, organizationInternalId: $organizationInternalId } ) {
  163. id
  164. name
  165. shared
  166. domain
  167. domainAssignment
  168. active
  169. note
  170. ticketsCount {
  171. open
  172. closed
  173. }
  174. members{
  175. edges {
  176. node {
  177. firstname
  178. lastname
  179. }
  180. }
  181. }
  182. }
  183. }
  184. QUERY
  185. end
  186. it 'performs only the expected amount of DB queries' do
  187. variables = { organizationId: organization_id }
  188. total_queries = {}
  189. uncached_queries = {}
  190. trace_queries(total_queries, uncached_queries) do
  191. gql.execute(query, variables: variables)
  192. end
  193. expect(gql.result.data[:id]).to eq(organization_id)
  194. expect(total_queries).to include(
  195. {
  196. 'Permission Load' => 3,
  197. 'User Load' => 2,
  198. 'Organization Load' => 1,
  199. }
  200. )
  201. expect(uncached_queries).to include(
  202. {
  203. 'Permission Load' => 3,
  204. 'User Load' => 2,
  205. 'Organization Load' => 1,
  206. }
  207. )
  208. end
  209. end
  210. end