customer.e2e-spec.ts 19 KB

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