mock-graphql-api.ts 5.8 KB

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