mutation-calls.spec.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. // Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. import { faker } from '@faker-js/faker'
  3. import UserError from '#shared/errors/UserError.ts'
  4. import { LoginDocument } from '#shared/graphql/mutations/login.api.ts'
  5. import type { MutationsUserCurrentAvatarAddArgs } from '#shared/graphql/types.ts'
  6. import { convertToGraphQLId } from '#shared/graphql/utils.ts'
  7. import { getGraphQLMockCalls, mockGraphQLResult } from '../mocks.ts'
  8. import {
  9. type TestAvatarMutation,
  10. TestAvatarActiveMutationDocument,
  11. TestUserUpdateDocument,
  12. type TestUserUpdateMutation,
  13. type TestUserUpdateVariables,
  14. TestUserDocument,
  15. type TestUserQuery,
  16. type TestUserQueryVariables,
  17. type TestUserSignupMutationQuery,
  18. TestUserSignupMutationDocument,
  19. type TestUserSignupArgs,
  20. } from './queries.ts'
  21. import { getMutationHandler, getQueryHandler } from './utils.ts'
  22. describe('calling mutation without mocking document works correctly', () => {
  23. it('mutation correctly returns data', async () => {
  24. expect(getGraphQLMockCalls(TestAvatarActiveMutationDocument)).toHaveLength(
  25. 0,
  26. )
  27. const handler = getMutationHandler<
  28. TestAvatarMutation,
  29. MutationsUserCurrentAvatarAddArgs
  30. >(TestAvatarActiveMutationDocument)
  31. const data = await handler.send({
  32. images: {
  33. original: { name: faker.word.noun() },
  34. resized: { name: faker.word.noun() },
  35. },
  36. })
  37. const { data: mocked } = handler.getMockedData()
  38. // errors is always null by default
  39. expect(data?.userCurrentAvatarAdd?.errors).toBeNull()
  40. expect(mocked).toMatchObject(data!)
  41. expect(data).not.toMatchObject(mocked)
  42. expect(mocked).toHaveProperty('userCurrentAvatarAdd.avatar.createdAt')
  43. expect(data).not.toHaveProperty('userCurrentAvatarAdd.avatar.createdAt')
  44. })
  45. it('mutation correctly processed data with arrays', async () => {
  46. expect(getGraphQLMockCalls(TestUserUpdateDocument)).toHaveLength(0)
  47. const handler = getMutationHandler<
  48. TestUserUpdateMutation,
  49. TestUserUpdateVariables
  50. >(TestUserUpdateDocument)
  51. const userId = convertToGraphQLId('User', 42)
  52. const data = await handler.send({
  53. userId,
  54. input: {
  55. vip: false,
  56. },
  57. })
  58. const { data: mocked } = handler.getMockedData()
  59. expect(data?.userUpdate.user).toEqual({
  60. __typename: 'User',
  61. id: userId,
  62. fullname: mocked.userUpdate.user.fullname,
  63. authorizations: mocked.userUpdate.user.authorizations.map((auth) => ({
  64. id: auth.id,
  65. provider: auth.provider,
  66. __typename: 'Authorization',
  67. })),
  68. })
  69. })
  70. it('returns the same object if it already exists, but values are updated', async () => {
  71. expect(getGraphQLMockCalls(TestUserDocument)).toHaveLength(0)
  72. const queryHandler = getQueryHandler<TestUserQuery, TestUserQueryVariables>(
  73. TestUserDocument,
  74. )
  75. const userId = convertToGraphQLId('User', 42)
  76. const { data } = await queryHandler.query({
  77. variables: {
  78. userId,
  79. },
  80. })
  81. const currentFullname = data?.user?.fullname
  82. expect(data?.user).toEqual({
  83. __typename: 'User',
  84. id: userId,
  85. fullname: expect.any(String),
  86. })
  87. const mutationHandler = getMutationHandler<
  88. TestUserUpdateMutation,
  89. TestUserUpdateVariables
  90. >(TestUserUpdateDocument)
  91. const mutationData = await mutationHandler.send({
  92. userId,
  93. input: {
  94. vip: false,
  95. phone: '1234567890',
  96. },
  97. })
  98. const mutationUser = mutationData?.userUpdate.user
  99. expect(mutationUser?.id).toBe(userId)
  100. expect(mutationUser?.fullname).toBe(currentFullname)
  101. const { data: mockedQuery } = queryHandler.getMockedData()
  102. const { data: mockedMutation } = mutationHandler.getMockedData()
  103. expect(mockedQuery?.user).toBe(mockedMutation?.userUpdate.user)
  104. expect(mockedQuery?.user).toHaveProperty('vip', false)
  105. expect(mockedQuery?.user).toHaveProperty('phone', '1234567890')
  106. })
  107. })
  108. describe('calling mutation with mocked return data correctly returns data', () => {
  109. it('returns mocked data when mutation is mocked and then called', async () => {
  110. expect(getGraphQLMockCalls(TestAvatarActiveMutationDocument)).toHaveLength(
  111. 0,
  112. )
  113. const avatarId = convertToGraphQLId('Avatar', 42)
  114. const imageFull = 'https://example.com/image.png'
  115. mockGraphQLResult(TestAvatarActiveMutationDocument, {
  116. userCurrentAvatarAdd: {
  117. avatar: {
  118. id: avatarId,
  119. imageFull,
  120. },
  121. },
  122. })
  123. const handler = getMutationHandler<
  124. TestAvatarMutation,
  125. MutationsUserCurrentAvatarAddArgs
  126. >(TestAvatarActiveMutationDocument)
  127. const data = await handler.send({
  128. images: {
  129. original: { name: faker.word.noun() },
  130. resized: { name: faker.word.noun() },
  131. },
  132. })
  133. expect(data?.userCurrentAvatarAdd?.avatar).toEqual({
  134. __typename: 'Avatar',
  135. id: avatarId,
  136. imageFull,
  137. })
  138. const { data: mocked } = handler.getMockedData()
  139. expect(mocked.userCurrentAvatarAdd.avatar).toHaveProperty('id', avatarId)
  140. expect(mocked.userCurrentAvatarAdd.avatar).toHaveProperty(
  141. 'imageFull',
  142. imageFull,
  143. )
  144. })
  145. it('correctly returns errors if provided', async () => {
  146. mockGraphQLResult(TestAvatarActiveMutationDocument, {
  147. userCurrentAvatarAdd: {
  148. errors: [
  149. {
  150. message: 'Some error',
  151. },
  152. ],
  153. },
  154. })
  155. const handler = getMutationHandler<
  156. TestAvatarMutation,
  157. MutationsUserCurrentAvatarAddArgs
  158. >(TestAvatarActiveMutationDocument)
  159. const data = await handler
  160. .send({
  161. images: {
  162. original: { name: faker.word.noun() },
  163. resized: { name: faker.word.noun() },
  164. },
  165. })
  166. .catch((e) => e)
  167. expect(data).toBeInstanceOf(UserError)
  168. expect(data.errors).toHaveLength(1)
  169. expect(data.errors[0].message).toBe('Some error')
  170. })
  171. it('mutation is always successful by defualt', async () => {
  172. const handler = getMutationHandler<
  173. TestUserSignupMutationQuery,
  174. TestUserSignupArgs
  175. >(TestUserSignupMutationDocument)
  176. const data = await handler.send({
  177. input: {
  178. email: faker.internet.userName(),
  179. password: faker.internet.password(),
  180. },
  181. })
  182. expect(data?.userSignup.success).toBe(true)
  183. expect(data?.userSignup.errors).toBeNull()
  184. })
  185. describe('correctly validates variables', () => {
  186. it('throws an error if field is required, but not defined', async () => {
  187. const handler = getMutationHandler(TestUserSignupMutationDocument)
  188. await expect(() =>
  189. handler.send({}),
  190. ).rejects.toThrowErrorMatchingInlineSnapshot(
  191. `[ApolloError: (Variables error for mutation userSignup) non-nullable field "input" is not defined]`,
  192. )
  193. })
  194. it('throws an error if field is required inside the list, but not defined', async () => {
  195. const mutationHandler = getMutationHandler(TestUserUpdateDocument)
  196. await expect(() =>
  197. mutationHandler.send({
  198. userId: '1',
  199. input: {
  200. objectAttributeValues: [{}],
  201. },
  202. }),
  203. ).rejects.toThrowErrorMatchingInlineSnapshot(
  204. `[ApolloError: (Variables error for mutation userUpdate) non-nullable field "name" is not defined]`,
  205. )
  206. })
  207. it('throws an error if field is not defined on the inner type', async () => {
  208. const handler = getMutationHandler(TestUserSignupMutationDocument)
  209. await expect(() =>
  210. handler.send({
  211. input: { email: 'email', password: 'password', invalidField: true },
  212. }),
  213. ).rejects.toThrowErrorMatchingInlineSnapshot(
  214. `[ApolloError: (Variables error for mutation userSignup) field "invalidField" is not defined on UserSignupInput]`,
  215. )
  216. })
  217. it('throws an error if field is not defined on the outer type', async () => {
  218. const handler = getMutationHandler(TestUserSignupMutationDocument)
  219. await expect(() =>
  220. handler.send({
  221. invalidField: true,
  222. input: { email: 'email', password: 'password' },
  223. }),
  224. ).rejects.toThrowErrorMatchingInlineSnapshot(
  225. `[ApolloError: (Variables error for mutation userSignup) field "invalidField" is not defined on mutation userSignup]`,
  226. )
  227. })
  228. it('throws an error if field is not defined on the type inside the list', async () => {
  229. const mutationHandler = getMutationHandler(TestUserUpdateDocument)
  230. await expect(() =>
  231. mutationHandler.send({
  232. userId: '1',
  233. input: {
  234. objectAttributeValues: [{ invalidField: true }],
  235. },
  236. }),
  237. ).rejects.toThrowErrorMatchingInlineSnapshot(
  238. `[ApolloError: (Variables error for mutation userUpdate) field "invalidField" is not defined on ObjectAttributeValueInput]`,
  239. )
  240. })
  241. it('throws an error if field is not the correct scalar type', async () => {
  242. const handler = getMutationHandler(TestUserSignupMutationDocument)
  243. await expect(() =>
  244. handler.send({
  245. input: { email: 123, password: 'password' },
  246. }),
  247. ).rejects.toThrowErrorMatchingInlineSnapshot(
  248. `[ApolloError: (Variables error for mutation userSignup) expected string for "email", got number]`,
  249. )
  250. })
  251. it('throws an error if field is not the correct object type', async () => {
  252. const handler = getMutationHandler(TestUserSignupMutationDocument)
  253. await expect(() =>
  254. handler.send({
  255. input: 123,
  256. }),
  257. ).rejects.toThrowErrorMatchingInlineSnapshot(
  258. `[ApolloError: (Variables error for mutation userSignup) expected object for "input", got number]`,
  259. )
  260. })
  261. it('throws an error if field is not the correct type inside the list', async () => {
  262. const mutationHandler = getMutationHandler(TestUserUpdateDocument)
  263. await expect(() =>
  264. mutationHandler.send({
  265. userId: '1',
  266. input: {
  267. objectAttributeValues: [{ name: 123 }],
  268. },
  269. }),
  270. ).rejects.toThrowErrorMatchingInlineSnapshot(
  271. `[ApolloError: (Variables error for mutation userUpdate) expected string for "name", got number]`,
  272. )
  273. })
  274. it('throws an error if field is not in enum', async () => {
  275. const mutationHandler = getMutationHandler(LoginDocument)
  276. await expect(() =>
  277. mutationHandler.send({
  278. input: {
  279. login: 'login',
  280. password: 'password',
  281. rememberMe: true,
  282. twoFactorAuthentication: {
  283. twoFactorMethod: 'unknown_method_to_test_error',
  284. twoFactorPayload: 'some_payload',
  285. },
  286. },
  287. }),
  288. ).rejects.toThrowErrorMatchingInlineSnapshot(
  289. `[ApolloError: (Variables error for mutation login) twoFactorMethod should be one of "security_keys", "authenticator_app", but instead got "unknown_method_to_test_error"]`,
  290. )
  291. })
  292. })
  293. })