customer.e2e-spec.ts 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582
  1. import { OnModuleInit } from '@nestjs/common';
  2. import { omit } from '@vendure/common/lib/omit';
  3. import {
  4. AccountRegistrationEvent,
  5. EventBus,
  6. EventBusModule,
  7. mergeConfig,
  8. VendurePlugin,
  9. } from '@vendure/core';
  10. import { createTestEnvironment } from '@vendure/testing';
  11. import gql from 'graphql-tag';
  12. import path from 'path';
  13. import { initialData } from '../../../e2e-common/e2e-initial-data';
  14. import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
  15. import { CUSTOMER_FRAGMENT } from './graphql/fragments';
  16. import {
  17. CreateAddress,
  18. CreateCustomer,
  19. DeleteCustomer,
  20. DeleteCustomerAddress,
  21. DeletionResult,
  22. GetCustomer,
  23. GetCustomerList,
  24. GetCustomerOrders,
  25. GetCustomerWithUser,
  26. UpdateAddress,
  27. UpdateCustomer,
  28. } from './graphql/generated-e2e-admin-types';
  29. import { AddItemToOrder } from './graphql/generated-e2e-shop-types';
  30. import { GET_CUSTOMER, GET_CUSTOMER_LIST } from './graphql/shared-definitions';
  31. import { ADD_ITEM_TO_ORDER } from './graphql/shop-definitions';
  32. import { assertThrowsWithMessage } from './utils/assert-throws-with-message';
  33. // tslint:disable:no-non-null-assertion
  34. let sendEmailFn: jest.Mock;
  35. /**
  36. * This mock plugin simulates an EmailPlugin which would send emails
  37. * on the registration & password reset events.
  38. */
  39. @VendurePlugin({
  40. imports: [EventBusModule],
  41. })
  42. class TestEmailPlugin implements OnModuleInit {
  43. constructor(private eventBus: EventBus) {}
  44. onModuleInit() {
  45. this.eventBus.ofType(AccountRegistrationEvent).subscribe(event => {
  46. sendEmailFn(event);
  47. });
  48. }
  49. }
  50. describe('Customer resolver', () => {
  51. const { server, adminClient, shopClient } = createTestEnvironment(
  52. mergeConfig(testConfig, { plugins: [TestEmailPlugin] }),
  53. );
  54. let firstCustomer: GetCustomerList.Items;
  55. let secondCustomer: GetCustomerList.Items;
  56. let thirdCustomer: GetCustomerList.Items;
  57. beforeAll(async () => {
  58. await server.init({
  59. initialData,
  60. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-minimal.csv'),
  61. customerCount: 5,
  62. });
  63. await adminClient.asSuperAdmin();
  64. }, TEST_SETUP_TIMEOUT_MS);
  65. afterAll(async () => {
  66. await server.destroy();
  67. });
  68. it('customers list', async () => {
  69. const result = await adminClient.query<GetCustomerList.Query, GetCustomerList.Variables>(
  70. GET_CUSTOMER_LIST,
  71. );
  72. expect(result.customers.items.length).toBe(5);
  73. expect(result.customers.totalItems).toBe(5);
  74. firstCustomer = result.customers.items[0];
  75. secondCustomer = result.customers.items[1];
  76. thirdCustomer = result.customers.items[2];
  77. });
  78. it('customer resolver resolves User', async () => {
  79. const { customer } = await adminClient.query<
  80. GetCustomerWithUser.Query,
  81. GetCustomerWithUser.Variables
  82. >(GET_CUSTOMER_WITH_USER, {
  83. id: firstCustomer.id,
  84. });
  85. expect(customer!.user).toEqual({
  86. id: 'T_2',
  87. identifier: firstCustomer.emailAddress,
  88. verified: true,
  89. });
  90. });
  91. describe('addresses', () => {
  92. let firstCustomerAddressIds: string[] = [];
  93. let firstCustomerThirdAddressId: string;
  94. it(
  95. 'createCustomerAddress throws on invalid countryCode',
  96. assertThrowsWithMessage(
  97. () =>
  98. adminClient.query<CreateAddress.Mutation, CreateAddress.Variables>(CREATE_ADDRESS, {
  99. id: firstCustomer.id,
  100. input: {
  101. streetLine1: 'streetLine1',
  102. countryCode: 'INVALID',
  103. },
  104. }),
  105. `The countryCode "INVALID" was not recognized`,
  106. ),
  107. );
  108. it('createCustomerAddress creates a new address', async () => {
  109. const result = await adminClient.query<CreateAddress.Mutation, CreateAddress.Variables>(
  110. CREATE_ADDRESS,
  111. {
  112. id: firstCustomer.id,
  113. input: {
  114. fullName: 'fullName',
  115. company: 'company',
  116. streetLine1: 'streetLine1',
  117. streetLine2: 'streetLine2',
  118. city: 'city',
  119. province: 'province',
  120. postalCode: 'postalCode',
  121. countryCode: 'GB',
  122. phoneNumber: 'phoneNumber',
  123. defaultShippingAddress: false,
  124. defaultBillingAddress: false,
  125. },
  126. },
  127. );
  128. expect(omit(result.createCustomerAddress, ['id'])).toEqual({
  129. fullName: 'fullName',
  130. company: 'company',
  131. streetLine1: 'streetLine1',
  132. streetLine2: 'streetLine2',
  133. city: 'city',
  134. province: 'province',
  135. postalCode: 'postalCode',
  136. country: {
  137. code: 'GB',
  138. name: 'United Kingdom',
  139. },
  140. phoneNumber: 'phoneNumber',
  141. defaultShippingAddress: false,
  142. defaultBillingAddress: false,
  143. });
  144. });
  145. it('customer query returns addresses', async () => {
  146. const result = await adminClient.query<GetCustomer.Query, GetCustomer.Variables>(GET_CUSTOMER, {
  147. id: firstCustomer.id,
  148. });
  149. expect(result.customer!.addresses!.length).toBe(2);
  150. firstCustomerAddressIds = result.customer!.addresses!.map(a => a.id).sort();
  151. });
  152. it('updateCustomerAddress updates the country', async () => {
  153. const result = await adminClient.query<UpdateAddress.Mutation, UpdateAddress.Variables>(
  154. UPDATE_ADDRESS,
  155. {
  156. input: {
  157. id: firstCustomerAddressIds[0],
  158. countryCode: 'AT',
  159. },
  160. },
  161. );
  162. expect(result.updateCustomerAddress.country).toEqual({
  163. code: 'AT',
  164. name: 'Austria',
  165. });
  166. });
  167. it('updateCustomerAddress allows only a single default address', async () => {
  168. // set the first customer's second address to be default
  169. const result1 = await adminClient.query<UpdateAddress.Mutation, UpdateAddress.Variables>(
  170. UPDATE_ADDRESS,
  171. {
  172. input: {
  173. id: firstCustomerAddressIds[1],
  174. defaultShippingAddress: true,
  175. defaultBillingAddress: true,
  176. },
  177. },
  178. );
  179. expect(result1.updateCustomerAddress.defaultShippingAddress).toBe(true);
  180. expect(result1.updateCustomerAddress.defaultBillingAddress).toBe(true);
  181. // assert the first customer's other addresse is not default
  182. const result2 = await adminClient.query<GetCustomer.Query, GetCustomer.Variables>(GET_CUSTOMER, {
  183. id: firstCustomer.id,
  184. });
  185. const otherAddress = result2.customer!.addresses!.filter(
  186. a => a.id !== firstCustomerAddressIds[1],
  187. )[0]!;
  188. expect(otherAddress.defaultShippingAddress).toBe(false);
  189. expect(otherAddress.defaultBillingAddress).toBe(false);
  190. // set the first customer's first address to be default
  191. const result3 = await adminClient.query<UpdateAddress.Mutation, UpdateAddress.Variables>(
  192. UPDATE_ADDRESS,
  193. {
  194. input: {
  195. id: firstCustomerAddressIds[0],
  196. defaultShippingAddress: true,
  197. defaultBillingAddress: true,
  198. },
  199. },
  200. );
  201. expect(result3.updateCustomerAddress.defaultShippingAddress).toBe(true);
  202. expect(result3.updateCustomerAddress.defaultBillingAddress).toBe(true);
  203. // assert the first customer's second address is not default
  204. const result4 = await adminClient.query<GetCustomer.Query, GetCustomer.Variables>(GET_CUSTOMER, {
  205. id: firstCustomer.id,
  206. });
  207. const otherAddress2 = result4.customer!.addresses!.filter(
  208. a => a.id !== firstCustomerAddressIds[0],
  209. )[0]!;
  210. expect(otherAddress2.defaultShippingAddress).toBe(false);
  211. expect(otherAddress2.defaultBillingAddress).toBe(false);
  212. // get the second customer's address id
  213. const result5 = await adminClient.query<GetCustomer.Query, GetCustomer.Variables>(GET_CUSTOMER, {
  214. id: secondCustomer.id,
  215. });
  216. const secondCustomerAddressId = result5.customer!.addresses![0].id;
  217. // set the second customer's address to be default
  218. const result6 = await adminClient.query<UpdateAddress.Mutation, UpdateAddress.Variables>(
  219. UPDATE_ADDRESS,
  220. {
  221. input: {
  222. id: secondCustomerAddressId,
  223. defaultShippingAddress: true,
  224. defaultBillingAddress: true,
  225. },
  226. },
  227. );
  228. expect(result6.updateCustomerAddress.defaultShippingAddress).toBe(true);
  229. expect(result6.updateCustomerAddress.defaultBillingAddress).toBe(true);
  230. // assets the first customer's address defaults are unchanged
  231. const result7 = await adminClient.query<GetCustomer.Query, GetCustomer.Variables>(GET_CUSTOMER, {
  232. id: firstCustomer.id,
  233. });
  234. expect(result7.customer!.addresses![0].defaultShippingAddress).toBe(true);
  235. expect(result7.customer!.addresses![0].defaultBillingAddress).toBe(true);
  236. expect(result7.customer!.addresses![1].defaultShippingAddress).toBe(false);
  237. expect(result7.customer!.addresses![1].defaultBillingAddress).toBe(false);
  238. });
  239. it('createCustomerAddress with true defaults unsets existing defaults', async () => {
  240. const { createCustomerAddress } = await adminClient.query<
  241. CreateAddress.Mutation,
  242. CreateAddress.Variables
  243. >(CREATE_ADDRESS, {
  244. id: firstCustomer.id,
  245. input: {
  246. streetLine1: 'new default streetline',
  247. countryCode: 'GB',
  248. defaultShippingAddress: true,
  249. defaultBillingAddress: true,
  250. },
  251. });
  252. expect(omit(createCustomerAddress, ['id'])).toEqual({
  253. fullName: '',
  254. company: '',
  255. streetLine1: 'new default streetline',
  256. streetLine2: '',
  257. city: '',
  258. province: '',
  259. postalCode: '',
  260. country: {
  261. code: 'GB',
  262. name: 'United Kingdom',
  263. },
  264. phoneNumber: '',
  265. defaultShippingAddress: true,
  266. defaultBillingAddress: true,
  267. });
  268. const { customer } = await adminClient.query<GetCustomer.Query, GetCustomer.Variables>(
  269. GET_CUSTOMER,
  270. {
  271. id: firstCustomer.id,
  272. },
  273. );
  274. for (const address of customer!.addresses!) {
  275. const shouldBeDefault = address.id === createCustomerAddress.id;
  276. expect(address.defaultShippingAddress).toEqual(shouldBeDefault);
  277. expect(address.defaultBillingAddress).toEqual(shouldBeDefault);
  278. }
  279. firstCustomerThirdAddressId = createCustomerAddress.id;
  280. });
  281. it('deleteCustomerAddress on default address resets defaults', async () => {
  282. const { deleteCustomerAddress } = await adminClient.query<
  283. DeleteCustomerAddress.Mutation,
  284. DeleteCustomerAddress.Variables
  285. >(
  286. gql`
  287. mutation DeleteCustomerAddress($id: ID!) {
  288. deleteCustomerAddress(id: $id)
  289. }
  290. `,
  291. { id: firstCustomerThirdAddressId },
  292. );
  293. expect(deleteCustomerAddress).toBe(true);
  294. const { customer } = await adminClient.query<GetCustomer.Query, GetCustomer.Variables>(
  295. GET_CUSTOMER,
  296. {
  297. id: firstCustomer.id,
  298. },
  299. );
  300. expect(customer!.addresses!.length).toBe(2);
  301. const defaultAddress = customer!.addresses!.filter(
  302. a => a.defaultBillingAddress && a.defaultShippingAddress,
  303. );
  304. const otherAddress = customer!.addresses!.filter(
  305. a => !a.defaultBillingAddress && !a.defaultShippingAddress,
  306. );
  307. expect(defaultAddress.length).toBe(1);
  308. expect(otherAddress.length).toBe(1);
  309. });
  310. });
  311. describe('orders', () => {
  312. it(`lists that user\'s orders`, async () => {
  313. // log in as first customer
  314. await shopClient.asUserWithCredentials(firstCustomer.emailAddress, 'test');
  315. // add an item to the order to create an order
  316. const { addItemToOrder } = await shopClient.query<
  317. AddItemToOrder.Mutation,
  318. AddItemToOrder.Variables
  319. >(ADD_ITEM_TO_ORDER, {
  320. productVariantId: 'T_1',
  321. quantity: 1,
  322. });
  323. const { customer } = await adminClient.query<
  324. GetCustomerOrders.Query,
  325. GetCustomerOrders.Variables
  326. >(GET_CUSTOMER_ORDERS, { id: firstCustomer.id });
  327. expect(customer!.orders.totalItems).toBe(1);
  328. expect(customer!.orders.items[0].id).toBe(addItemToOrder!.id);
  329. });
  330. });
  331. describe('creation', () => {
  332. it('triggers verification event if no password supplied', async () => {
  333. sendEmailFn = jest.fn();
  334. const { createCustomer } = await adminClient.query<
  335. CreateCustomer.Mutation,
  336. CreateCustomer.Variables
  337. >(CREATE_CUSTOMER, {
  338. input: {
  339. emailAddress: 'test1@test.com',
  340. firstName: 'New',
  341. lastName: 'Customer',
  342. },
  343. });
  344. expect(createCustomer.user!.verified).toBe(false);
  345. expect(sendEmailFn).toHaveBeenCalledTimes(1);
  346. expect(sendEmailFn.mock.calls[0][0] instanceof AccountRegistrationEvent).toBe(true);
  347. expect(sendEmailFn.mock.calls[0][0].user.identifier).toBe('test1@test.com');
  348. });
  349. it('creates a verified Customer', async () => {
  350. sendEmailFn = jest.fn();
  351. const { createCustomer } = await adminClient.query<
  352. CreateCustomer.Mutation,
  353. CreateCustomer.Variables
  354. >(CREATE_CUSTOMER, {
  355. input: {
  356. emailAddress: 'test2@test.com',
  357. firstName: 'New',
  358. lastName: 'Customer',
  359. },
  360. password: 'test',
  361. });
  362. expect(createCustomer.user!.verified).toBe(true);
  363. expect(sendEmailFn).toHaveBeenCalledTimes(0);
  364. });
  365. it(
  366. 'throws when using an existing, non-deleted emailAddress',
  367. assertThrowsWithMessage(async () => {
  368. const { createCustomer } = await adminClient.query<
  369. CreateCustomer.Mutation,
  370. CreateCustomer.Variables
  371. >(CREATE_CUSTOMER, {
  372. input: {
  373. emailAddress: 'test2@test.com',
  374. firstName: 'New',
  375. lastName: 'Customer',
  376. },
  377. password: 'test',
  378. });
  379. }, 'The email address must be unique'),
  380. );
  381. });
  382. describe('deletion', () => {
  383. it('deletes a customer', async () => {
  384. const result = await adminClient.query<DeleteCustomer.Mutation, DeleteCustomer.Variables>(
  385. DELETE_CUSTOMER,
  386. { id: thirdCustomer.id },
  387. );
  388. expect(result.deleteCustomer).toEqual({ result: DeletionResult.DELETED });
  389. });
  390. it('cannot get a deleted customer', async () => {
  391. const result = await adminClient.query<GetCustomer.Query, GetCustomer.Variables>(GET_CUSTOMER, {
  392. id: thirdCustomer.id,
  393. });
  394. expect(result.customer).toBe(null);
  395. });
  396. it('deleted customer omitted from list', async () => {
  397. const result = await adminClient.query<GetCustomerList.Query, GetCustomerList.Variables>(
  398. GET_CUSTOMER_LIST,
  399. );
  400. expect(result.customers.items.map(c => c.id).includes(thirdCustomer.id)).toBe(false);
  401. });
  402. it(
  403. 'updateCustomer throws for deleted customer',
  404. assertThrowsWithMessage(
  405. () =>
  406. adminClient.query<UpdateCustomer.Mutation, UpdateCustomer.Variables>(UPDATE_CUSTOMER, {
  407. input: {
  408. id: thirdCustomer.id,
  409. firstName: 'updated',
  410. },
  411. }),
  412. `No Customer with the id '3' could be found`,
  413. ),
  414. );
  415. it(
  416. 'createCustomerAddress throws for deleted customer',
  417. assertThrowsWithMessage(
  418. () =>
  419. adminClient.query<CreateAddress.Mutation, CreateAddress.Variables>(CREATE_ADDRESS, {
  420. id: thirdCustomer.id,
  421. input: {
  422. streetLine1: 'test',
  423. countryCode: 'GB',
  424. },
  425. }),
  426. `No Customer with the id '3' could be found`,
  427. ),
  428. );
  429. it('new Customer can be created with same emailAddress as a deleted Customer', async () => {
  430. const { createCustomer } = await adminClient.query<
  431. CreateCustomer.Mutation,
  432. CreateCustomer.Variables
  433. >(CREATE_CUSTOMER, {
  434. input: {
  435. emailAddress: thirdCustomer.emailAddress,
  436. firstName: 'Reusing Email',
  437. lastName: 'Customer',
  438. },
  439. });
  440. expect(createCustomer.emailAddress).toBe(thirdCustomer.emailAddress);
  441. expect(createCustomer.firstName).toBe('Reusing Email');
  442. expect(createCustomer.user?.identifier).toBe(thirdCustomer.emailAddress);
  443. });
  444. });
  445. });
  446. const GET_CUSTOMER_WITH_USER = gql`
  447. query GetCustomerWithUser($id: ID!) {
  448. customer(id: $id) {
  449. id
  450. user {
  451. id
  452. identifier
  453. verified
  454. }
  455. }
  456. }
  457. `;
  458. const CREATE_ADDRESS = gql`
  459. mutation CreateAddress($id: ID!, $input: CreateAddressInput!) {
  460. createCustomerAddress(customerId: $id, input: $input) {
  461. id
  462. fullName
  463. company
  464. streetLine1
  465. streetLine2
  466. city
  467. province
  468. postalCode
  469. country {
  470. code
  471. name
  472. }
  473. phoneNumber
  474. defaultShippingAddress
  475. defaultBillingAddress
  476. }
  477. }
  478. `;
  479. const UPDATE_ADDRESS = gql`
  480. mutation UpdateAddress($input: UpdateAddressInput!) {
  481. updateCustomerAddress(input: $input) {
  482. id
  483. defaultShippingAddress
  484. defaultBillingAddress
  485. country {
  486. code
  487. name
  488. }
  489. }
  490. }
  491. `;
  492. const GET_CUSTOMER_ORDERS = gql`
  493. query GetCustomerOrders($id: ID!) {
  494. customer(id: $id) {
  495. orders {
  496. items {
  497. id
  498. }
  499. totalItems
  500. }
  501. }
  502. }
  503. `;
  504. export const CREATE_CUSTOMER = gql`
  505. mutation CreateCustomer($input: CreateCustomerInput!, $password: String) {
  506. createCustomer(input: $input, password: $password) {
  507. ...Customer
  508. }
  509. }
  510. ${CUSTOMER_FRAGMENT}
  511. `;
  512. export const UPDATE_CUSTOMER = gql`
  513. mutation UpdateCustomer($input: UpdateCustomerInput!) {
  514. updateCustomer(input: $input) {
  515. ...Customer
  516. }
  517. }
  518. ${CUSTOMER_FRAGMENT}
  519. `;
  520. const DELETE_CUSTOMER = gql`
  521. mutation DeleteCustomer($id: ID!) {
  522. deleteCustomer(id: $id) {
  523. result
  524. }
  525. }
  526. `;