mock-graphql-api.ts 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. // Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. /* eslint-disable no-use-before-define */
  3. import { NetworkStatus } from '@apollo/client/core'
  4. import type { UserError } from '#shared/graphql/types.ts'
  5. import type { GraphQLErrorReport } from '#shared/types/error.ts'
  6. import { GraphQLErrorTypes } from '#shared/types/error.ts'
  7. import type { DocumentNode } from 'graphql'
  8. import {
  9. createMockSubscription,
  10. type IMockSubscription,
  11. type RequestHandlerResponse,
  12. } from 'mock-apollo-client'
  13. import type { SpyInstance } from 'vitest'
  14. import createMockClient from './mock-apollo-client.ts'
  15. import { waitForNextTick } from './utils.ts'
  16. interface Result {
  17. [key: string]: unknown
  18. }
  19. interface ResultWithUserError extends Result {
  20. errors: UserError[]
  21. }
  22. type OperationResultWithUserError = Record<string, ResultWithUserError>
  23. export interface MockGraphQLInstance {
  24. willBehave<T>(handler: (variables: any) => T): MockGraphQLInstance
  25. willResolve<T>(result: T): MockGraphQLInstance
  26. willFailWithError(
  27. errors: GraphQLErrorReport[],
  28. networkStatus?: NetworkStatus,
  29. ): MockGraphQLInstance
  30. willFailWithUserError(
  31. result: OperationResultWithUserError,
  32. ): MockGraphQLInstance
  33. willFailWithForbiddenError(message?: string): MockGraphQLInstance
  34. willFailWithNotFoundError(message?: string): MockGraphQLInstance
  35. willFailWithNetworkError(error: Error): MockGraphQLInstance
  36. spies: {
  37. behave: SpyInstance
  38. resolve: SpyInstance
  39. error: SpyInstance
  40. userError: SpyInstance
  41. networkError: SpyInstance
  42. }
  43. calls: {
  44. behave: number
  45. resolve: number
  46. error: number
  47. userError: number
  48. networkError: number
  49. }
  50. }
  51. export const mockGraphQLApi = (
  52. operationDocument: DocumentNode,
  53. ): MockGraphQLInstance => {
  54. const resolveSpy = vi.fn()
  55. const errorSpy = vi.fn()
  56. const userErrorSpy = vi.fn()
  57. const networkErrorSpy = vi.fn()
  58. const behaveSpy = vi.fn()
  59. const willBehave = (fn: (variables: any) => unknown) => {
  60. behaveSpy.mockImplementation(async (variables: any) => fn(variables))
  61. createMockClient([
  62. {
  63. operationDocument,
  64. handler: behaveSpy,
  65. },
  66. ])
  67. return instance
  68. }
  69. const willResolve = <T>(result: T | T[]) => {
  70. if (Array.isArray(result)) {
  71. result.forEach((singleResult) => {
  72. resolveSpy.mockResolvedValueOnce({ data: singleResult })
  73. })
  74. } else {
  75. resolveSpy.mockResolvedValue({ data: result })
  76. }
  77. createMockClient([
  78. {
  79. operationDocument,
  80. handler: resolveSpy,
  81. },
  82. ])
  83. return instance
  84. }
  85. const willFailWithError = (
  86. errors: GraphQLErrorReport[],
  87. networkStatus?: NetworkStatus,
  88. ) => {
  89. errorSpy.mockResolvedValue({
  90. networkStatus: networkStatus || NetworkStatus.error,
  91. errors,
  92. })
  93. createMockClient([
  94. {
  95. operationDocument,
  96. handler: errorSpy,
  97. },
  98. ])
  99. return instance
  100. }
  101. const willFailWithNotFoundError = (message = 'Not Found') => {
  102. errorSpy.mockResolvedValue({
  103. networkStatus: NetworkStatus.error,
  104. errors: [
  105. {
  106. extensions: {
  107. type: GraphQLErrorTypes.RecordNotFound,
  108. },
  109. message,
  110. },
  111. ],
  112. })
  113. createMockClient([
  114. {
  115. operationDocument,
  116. handler: errorSpy,
  117. },
  118. ])
  119. return instance
  120. }
  121. const willFailWithForbiddenError = (message = 'Forbidden') => {
  122. errorSpy.mockResolvedValue({
  123. networkStatus: NetworkStatus.error,
  124. errors: [
  125. {
  126. extensions: {
  127. type: GraphQLErrorTypes.Forbidden,
  128. },
  129. message,
  130. },
  131. ],
  132. })
  133. createMockClient([
  134. {
  135. operationDocument,
  136. handler: errorSpy,
  137. },
  138. ])
  139. return instance
  140. }
  141. const willFailWithUserError = (result: OperationResultWithUserError) => {
  142. userErrorSpy.mockResolvedValue({ data: result })
  143. createMockClient([
  144. {
  145. operationDocument,
  146. handler: userErrorSpy,
  147. },
  148. ])
  149. return instance
  150. }
  151. const willFailWithNetworkError = (error: Error) => {
  152. networkErrorSpy.mockRejectedValue(error)
  153. createMockClient([
  154. {
  155. operationDocument,
  156. handler: networkErrorSpy,
  157. },
  158. ])
  159. return instance
  160. }
  161. const instance = {
  162. willFailWithError,
  163. willFailWithUserError,
  164. willFailWithNotFoundError,
  165. willFailWithForbiddenError,
  166. willFailWithNetworkError,
  167. willResolve,
  168. willBehave,
  169. spies: {
  170. behave: behaveSpy,
  171. resolve: resolveSpy,
  172. error: errorSpy,
  173. userError: userErrorSpy,
  174. networkError: networkErrorSpy,
  175. },
  176. calls: {
  177. get behave() {
  178. return behaveSpy.mock.calls.length
  179. },
  180. get resolve() {
  181. return resolveSpy.mock.calls.length
  182. },
  183. get error() {
  184. return errorSpy.mock.calls.length
  185. },
  186. get userError() {
  187. return userErrorSpy.mock.calls.length
  188. },
  189. get networkError() {
  190. return networkErrorSpy.mock.calls.length
  191. },
  192. },
  193. }
  194. return instance
  195. }
  196. export interface ExtendedIMockSubscription<T = unknown>
  197. extends Omit<IMockSubscription, 'next' | 'closed'> {
  198. closed: () => boolean
  199. next: (result: RequestHandlerResponse<T>) => Promise<void>
  200. }
  201. export const mockGraphQLSubscription = <T>(
  202. operationDocument: DocumentNode,
  203. ): ExtendedIMockSubscription<T> => {
  204. const mockSubscription = createMockSubscription({ disableLogging: true })
  205. createMockClient([
  206. {
  207. operationDocument,
  208. handler: () => mockSubscription,
  209. },
  210. ])
  211. return {
  212. next: async (
  213. value: Parameters<typeof mockSubscription.next>[0],
  214. ): Promise<void> => {
  215. mockSubscription.next(value)
  216. await waitForNextTick(true)
  217. },
  218. error: mockSubscription.error.bind(mockSubscription),
  219. complete: mockSubscription.complete.bind(mockSubscription),
  220. closed: () => mockSubscription.closed,
  221. }
  222. }