shop-customer.e2e-spec.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. /* eslint-disable @typescript-eslint/no-non-null-assertion */
  2. import { pick } from '@vendure/common/lib/pick';
  3. import { createErrorResultGuard, createTestEnvironment, ErrorResultGuard } from '@vendure/testing';
  4. import gql from 'graphql-tag';
  5. import path from 'path';
  6. import { afterAll, beforeAll, describe, expect, it } from 'vitest';
  7. import { initialData } from '../../../e2e-common/e2e-initial-data';
  8. import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
  9. import * as Codegen from './graphql/generated-e2e-admin-types';
  10. import { HistoryEntryType } from './graphql/generated-e2e-admin-types';
  11. import * as CodegenShop from './graphql/generated-e2e-shop-types';
  12. import { CreateAddressInput, ErrorCode, UpdateAddressInput } from './graphql/generated-e2e-shop-types';
  13. import { ATTEMPT_LOGIN, GET_CUSTOMER, GET_CUSTOMER_HISTORY } from './graphql/shared-definitions';
  14. import {
  15. CREATE_ADDRESS,
  16. DELETE_ADDRESS,
  17. UPDATE_ADDRESS,
  18. UPDATE_CUSTOMER,
  19. UPDATE_PASSWORD,
  20. } from './graphql/shop-definitions';
  21. import { assertThrowsWithMessage } from './utils/assert-throws-with-message';
  22. describe('Shop customers', () => {
  23. const { server, adminClient, shopClient } = createTestEnvironment(testConfig());
  24. let customer: NonNullable<Codegen.GetCustomerQuery['customer']>;
  25. const successErrorGuard: ErrorResultGuard<{ success: boolean }> = createErrorResultGuard(
  26. input => input.success != null,
  27. );
  28. beforeAll(async () => {
  29. await server.init({
  30. initialData,
  31. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-full.csv'),
  32. customerCount: 2,
  33. });
  34. await adminClient.asSuperAdmin();
  35. // Fetch the first Customer and store it as the `customer` variable.
  36. const { customers } = await adminClient.query<Codegen.GetCustomerIdsQuery>(gql`
  37. query GetCustomerIds {
  38. customers {
  39. items {
  40. id
  41. }
  42. }
  43. }
  44. `);
  45. const result = await adminClient.query<Codegen.GetCustomerQuery, Codegen.GetCustomerQueryVariables>(
  46. GET_CUSTOMER,
  47. {
  48. id: customers.items[0].id,
  49. },
  50. );
  51. customer = result.customer!;
  52. }, TEST_SETUP_TIMEOUT_MS);
  53. afterAll(async () => {
  54. await server.destroy();
  55. });
  56. it(
  57. 'updateCustomer throws if not logged in',
  58. assertThrowsWithMessage(async () => {
  59. const input: CodegenShop.UpdateCustomerInput = {
  60. firstName: 'xyz',
  61. };
  62. await shopClient.query<
  63. CodegenShop.UpdateCustomerMutation,
  64. CodegenShop.UpdateCustomerMutationVariables
  65. >(UPDATE_CUSTOMER, {
  66. input,
  67. });
  68. }, 'You are not currently authorized to perform this action'),
  69. );
  70. it(
  71. 'createCustomerAddress throws if not logged in',
  72. assertThrowsWithMessage(async () => {
  73. const input: CreateAddressInput = {
  74. streetLine1: '1 Test Street',
  75. countryCode: 'GB',
  76. };
  77. await shopClient.query<
  78. CodegenShop.CreateAddressShopMutation,
  79. CodegenShop.CreateAddressShopMutationVariables
  80. >(CREATE_ADDRESS, {
  81. input,
  82. });
  83. }, 'You are not currently authorized to perform this action'),
  84. );
  85. it(
  86. 'updateCustomerAddress throws if not logged in',
  87. assertThrowsWithMessage(async () => {
  88. const input: UpdateAddressInput = {
  89. id: 'T_1',
  90. streetLine1: 'zxc',
  91. };
  92. await shopClient.query<
  93. CodegenShop.UpdateAddressShopMutation,
  94. CodegenShop.UpdateAddressShopMutationVariables
  95. >(UPDATE_ADDRESS, {
  96. input,
  97. });
  98. }, 'You are not currently authorized to perform this action'),
  99. );
  100. it(
  101. 'deleteCustomerAddress throws if not logged in',
  102. assertThrowsWithMessage(async () => {
  103. await shopClient.query<
  104. CodegenShop.DeleteAddressShopMutation,
  105. CodegenShop.DeleteAddressShopMutationVariables
  106. >(DELETE_ADDRESS, {
  107. id: 'T_1',
  108. });
  109. }, 'You are not currently authorized to perform this action'),
  110. );
  111. describe('logged in Customer', () => {
  112. let addressId: string;
  113. beforeAll(async () => {
  114. await shopClient.query<Codegen.AttemptLoginMutation, Codegen.AttemptLoginMutationVariables>(
  115. ATTEMPT_LOGIN,
  116. {
  117. username: customer.emailAddress,
  118. password: 'test',
  119. rememberMe: false,
  120. },
  121. );
  122. });
  123. it('updateCustomer works', async () => {
  124. const input: CodegenShop.UpdateCustomerInput = {
  125. firstName: 'xyz',
  126. };
  127. const result = await shopClient.query<
  128. CodegenShop.UpdateCustomerMutation,
  129. CodegenShop.UpdateCustomerMutationVariables
  130. >(UPDATE_CUSTOMER, { input });
  131. expect(result.updateCustomer.firstName).toBe('xyz');
  132. });
  133. it('customer history for CUSTOMER_DETAIL_UPDATED', async () => {
  134. const result = await adminClient.query<
  135. Codegen.GetCustomerHistoryQuery,
  136. Codegen.GetCustomerHistoryQueryVariables
  137. >(GET_CUSTOMER_HISTORY, {
  138. id: customer.id,
  139. options: {
  140. // skip populated CUSTOMER_ADDRESS_CREATED entry
  141. skip: 3,
  142. },
  143. });
  144. expect(result.customer?.history.items.map(pick(['type', 'data']))).toEqual([
  145. {
  146. type: HistoryEntryType.CUSTOMER_DETAIL_UPDATED,
  147. data: {
  148. input: { firstName: 'xyz', id: 'T_1' },
  149. },
  150. },
  151. ]);
  152. });
  153. it('createCustomerAddress works', async () => {
  154. const input: CreateAddressInput = {
  155. streetLine1: '1 Test Street',
  156. countryCode: 'GB',
  157. };
  158. const { createCustomerAddress } = await shopClient.query<
  159. CodegenShop.CreateAddressShopMutation,
  160. CodegenShop.CreateAddressShopMutationVariables
  161. >(CREATE_ADDRESS, { input });
  162. expect(createCustomerAddress).toEqual({
  163. id: 'T_3',
  164. streetLine1: '1 Test Street',
  165. country: {
  166. code: 'GB',
  167. },
  168. });
  169. addressId = createCustomerAddress.id;
  170. });
  171. it('customer history for CUSTOMER_ADDRESS_CREATED', async () => {
  172. const result = await adminClient.query<
  173. Codegen.GetCustomerHistoryQuery,
  174. Codegen.GetCustomerHistoryQueryVariables
  175. >(GET_CUSTOMER_HISTORY, {
  176. id: customer.id,
  177. options: {
  178. // skip populated CUSTOMER_ADDRESS_CREATED, CUSTOMER_DETAIL_UPDATED entries
  179. skip: 4,
  180. },
  181. });
  182. expect(result.customer?.history.items.map(pick(['type', 'data']))).toEqual([
  183. {
  184. type: HistoryEntryType.CUSTOMER_ADDRESS_CREATED,
  185. data: {
  186. address: '1 Test Street, United Kingdom',
  187. },
  188. },
  189. ]);
  190. });
  191. it('updateCustomerAddress works', async () => {
  192. const input: UpdateAddressInput = {
  193. id: addressId,
  194. streetLine1: '5 Test Street',
  195. countryCode: 'AT',
  196. };
  197. const result = await shopClient.query<
  198. CodegenShop.UpdateAddressShopMutation,
  199. CodegenShop.UpdateAddressShopMutationVariables
  200. >(UPDATE_ADDRESS, { input });
  201. expect(result.updateCustomerAddress.streetLine1).toEqual('5 Test Street');
  202. expect(result.updateCustomerAddress.country.code).toEqual('AT');
  203. });
  204. it('customer history for CUSTOMER_ADDRESS_UPDATED', async () => {
  205. const result = await adminClient.query<
  206. Codegen.GetCustomerHistoryQuery,
  207. Codegen.GetCustomerHistoryQueryVariables
  208. >(GET_CUSTOMER_HISTORY, { id: customer.id, options: { skip: 5 } });
  209. expect(result.customer?.history.items.map(pick(['type', 'data']))).toEqual([
  210. {
  211. type: HistoryEntryType.CUSTOMER_ADDRESS_UPDATED,
  212. data: {
  213. address: '5 Test Street, Austria',
  214. input: {
  215. id: addressId,
  216. streetLine1: '5 Test Street',
  217. countryCode: 'AT',
  218. },
  219. },
  220. },
  221. ]);
  222. });
  223. it(
  224. 'updateCustomerAddress fails for address not owned by Customer',
  225. assertThrowsWithMessage(async () => {
  226. const input: UpdateAddressInput = {
  227. id: 'T_2',
  228. streetLine1: '1 Test Street',
  229. };
  230. await shopClient.query<
  231. CodegenShop.UpdateAddressShopMutation,
  232. CodegenShop.UpdateAddressShopMutationVariables
  233. >(UPDATE_ADDRESS, { input });
  234. }, 'You are not currently authorized to perform this action'),
  235. );
  236. it('deleteCustomerAddress works', async () => {
  237. const { deleteCustomerAddress } = await shopClient.query<
  238. CodegenShop.DeleteAddressShopMutation,
  239. CodegenShop.DeleteAddressShopMutationVariables
  240. >(DELETE_ADDRESS, { id: 'T_3' });
  241. expect(deleteCustomerAddress.success).toBe(true);
  242. });
  243. it('customer history for CUSTOMER_ADDRESS_DELETED', async () => {
  244. const result = await adminClient.query<
  245. Codegen.GetCustomerHistoryQuery,
  246. Codegen.GetCustomerHistoryQueryVariables
  247. >(GET_CUSTOMER_HISTORY, { id: customer!.id, options: { skip: 6 } });
  248. expect(result.customer?.history.items.map(pick(['type', 'data']))).toEqual([
  249. {
  250. type: HistoryEntryType.CUSTOMER_ADDRESS_DELETED,
  251. data: {
  252. address: '5 Test Street, Austria',
  253. },
  254. },
  255. ]);
  256. });
  257. it(
  258. 'deleteCustomerAddress fails for address not owned by Customer',
  259. assertThrowsWithMessage(async () => {
  260. await shopClient.query<
  261. CodegenShop.DeleteAddressShopMutation,
  262. CodegenShop.DeleteAddressShopMutationVariables
  263. >(DELETE_ADDRESS, { id: 'T_2' });
  264. }, 'You are not currently authorized to perform this action'),
  265. );
  266. it('updatePassword return error result with incorrect current password', async () => {
  267. const { updateCustomerPassword } = await shopClient.query<
  268. CodegenShop.UpdatePasswordMutation,
  269. CodegenShop.UpdatePasswordMutationVariables
  270. >(UPDATE_PASSWORD, {
  271. old: 'wrong',
  272. new: 'test2',
  273. });
  274. successErrorGuard.assertErrorResult(updateCustomerPassword);
  275. expect(updateCustomerPassword.message).toBe('The provided credentials are invalid');
  276. expect(updateCustomerPassword.errorCode).toBe(ErrorCode.INVALID_CREDENTIALS_ERROR);
  277. });
  278. it('updatePassword works', async () => {
  279. const { updateCustomerPassword } = await shopClient.query<
  280. CodegenShop.UpdatePasswordMutation,
  281. CodegenShop.UpdatePasswordMutationVariables
  282. >(UPDATE_PASSWORD, { old: 'test', new: 'test2' });
  283. successErrorGuard.assertSuccess(updateCustomerPassword);
  284. expect(updateCustomerPassword.success).toBe(true);
  285. // Log out and log in with new password
  286. const loginResult = await shopClient.asUserWithCredentials(customer.emailAddress, 'test2');
  287. expect(loginResult.identifier).toBe(customer.emailAddress);
  288. });
  289. it('customer history for CUSTOMER_PASSWORD_UPDATED', async () => {
  290. const result = await adminClient.query<
  291. Codegen.GetCustomerHistoryQuery,
  292. Codegen.GetCustomerHistoryQueryVariables
  293. >(GET_CUSTOMER_HISTORY, { id: customer.id, options: { skip: 7 } });
  294. expect(result.customer?.history.items.map(pick(['type', 'data']))).toEqual([
  295. {
  296. type: HistoryEntryType.CUSTOMER_PASSWORD_UPDATED,
  297. data: {},
  298. },
  299. ]);
  300. });
  301. });
  302. });