customer.e2e-spec.ts 18 KB

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