customer.e2e-spec.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548
  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 { TEST_SETUP_TIMEOUT_MS, testConfig } 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. });
  366. describe('deletion', () => {
  367. it('deletes a customer', async () => {
  368. const result = await adminClient.query<DeleteCustomer.Mutation, DeleteCustomer.Variables>(
  369. DELETE_CUSTOMER,
  370. { id: thirdCustomer.id },
  371. );
  372. expect(result.deleteCustomer).toEqual({ result: DeletionResult.DELETED });
  373. });
  374. it('cannot get a deleted customer', async () => {
  375. const result = await adminClient.query<GetCustomer.Query, GetCustomer.Variables>(GET_CUSTOMER, {
  376. id: thirdCustomer.id,
  377. });
  378. expect(result.customer).toBe(null);
  379. });
  380. it('deleted customer omitted from list', async () => {
  381. const result = await adminClient.query<GetCustomerList.Query, GetCustomerList.Variables>(
  382. GET_CUSTOMER_LIST,
  383. );
  384. expect(result.customers.items.map(c => c.id).includes(thirdCustomer.id)).toBe(false);
  385. });
  386. it(
  387. 'updateCustomer throws for deleted customer',
  388. assertThrowsWithMessage(
  389. () =>
  390. adminClient.query<UpdateCustomer.Mutation, UpdateCustomer.Variables>(UPDATE_CUSTOMER, {
  391. input: {
  392. id: thirdCustomer.id,
  393. firstName: 'updated',
  394. },
  395. }),
  396. `No Customer with the id '3' could be found`,
  397. ),
  398. );
  399. it(
  400. 'createCustomerAddress throws for deleted customer',
  401. assertThrowsWithMessage(
  402. () =>
  403. adminClient.query<CreateAddress.Mutation, CreateAddress.Variables>(CREATE_ADDRESS, {
  404. id: thirdCustomer.id,
  405. input: {
  406. streetLine1: 'test',
  407. countryCode: 'GB',
  408. },
  409. }),
  410. `No Customer with the id '3' could be found`,
  411. ),
  412. );
  413. });
  414. });
  415. const GET_CUSTOMER_WITH_USER = gql`
  416. query GetCustomerWithUser($id: ID!) {
  417. customer(id: $id) {
  418. id
  419. user {
  420. id
  421. identifier
  422. verified
  423. }
  424. }
  425. }
  426. `;
  427. const CREATE_ADDRESS = gql`
  428. mutation CreateAddress($id: ID!, $input: CreateAddressInput!) {
  429. createCustomerAddress(customerId: $id, input: $input) {
  430. id
  431. fullName
  432. company
  433. streetLine1
  434. streetLine2
  435. city
  436. province
  437. postalCode
  438. country {
  439. code
  440. name
  441. }
  442. phoneNumber
  443. defaultShippingAddress
  444. defaultBillingAddress
  445. }
  446. }
  447. `;
  448. const UPDATE_ADDRESS = gql`
  449. mutation UpdateAddress($input: UpdateAddressInput!) {
  450. updateCustomerAddress(input: $input) {
  451. id
  452. defaultShippingAddress
  453. defaultBillingAddress
  454. country {
  455. code
  456. name
  457. }
  458. }
  459. }
  460. `;
  461. const GET_CUSTOMER_ORDERS = gql`
  462. query GetCustomerOrders($id: ID!) {
  463. customer(id: $id) {
  464. orders {
  465. items {
  466. id
  467. }
  468. totalItems
  469. }
  470. }
  471. }
  472. `;
  473. export const CREATE_CUSTOMER = gql`
  474. mutation CreateCustomer($input: CreateCustomerInput!, $password: String) {
  475. createCustomer(input: $input, password: $password) {
  476. ...Customer
  477. }
  478. }
  479. ${CUSTOMER_FRAGMENT}
  480. `;
  481. export const UPDATE_CUSTOMER = gql`
  482. mutation UpdateCustomer($input: UpdateCustomerInput!) {
  483. updateCustomer(input: $input) {
  484. ...Customer
  485. }
  486. }
  487. ${CUSTOMER_FRAGMENT}
  488. `;
  489. const DELETE_CUSTOMER = gql`
  490. mutation DeleteCustomer($id: ID!) {
  491. deleteCustomer(id: $id) {
  492. result
  493. }
  494. }
  495. `;