shop-order.e2e-spec.ts 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890
  1. /* tslint:disable:no-non-null-assertion */
  2. import gql from 'graphql-tag';
  3. import path from 'path';
  4. import {
  5. GET_CUSTOMER,
  6. GET_CUSTOMER_LIST,
  7. } from '../../admin-ui/src/app/data/definitions/customer-definitions';
  8. import {
  9. GET_COUNTRY_LIST,
  10. UPDATE_COUNTRY,
  11. } from '../../admin-ui/src/app/data/definitions/settings-definitions';
  12. import {
  13. CreateAddressInput,
  14. GetCountryList,
  15. GetCustomer,
  16. GetCustomerList,
  17. UpdateCountry,
  18. } from '../../shared/generated-types';
  19. import { PaymentMethodHandler } from '../src/config/payment-method/payment-method-handler';
  20. import { TEST_SETUP_TIMEOUT_MS } from './config/test-config';
  21. import { TestAdminClient, TestShopClient } from './test-client';
  22. import { TestServer } from './test-server';
  23. import { assertThrowsWithMessage } from './test-utils';
  24. describe('Shop orders', () => {
  25. const adminClient = new TestAdminClient();
  26. const shopClient = new TestShopClient();
  27. const server = new TestServer();
  28. beforeAll(async () => {
  29. const token = await server.init(
  30. {
  31. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-full.csv'),
  32. customerCount: 2,
  33. },
  34. {
  35. paymentOptions: {
  36. paymentMethodHandlers: [testPaymentMethod, testFailingPaymentMethod],
  37. },
  38. orderOptions: {
  39. orderItemsLimit: 99,
  40. },
  41. },
  42. );
  43. await shopClient.init();
  44. await adminClient.init();
  45. }, TEST_SETUP_TIMEOUT_MS);
  46. afterAll(async () => {
  47. await server.destroy();
  48. });
  49. it('availableCountries returns enabled countries', async () => {
  50. // disable Austria
  51. const { countries } = await adminClient.query<GetCountryList.Query>(GET_COUNTRY_LIST, {});
  52. const AT = countries.items.find(c => c.code === 'AT')!;
  53. await adminClient.query<UpdateCountry.Mutation, UpdateCountry.Variables>(UPDATE_COUNTRY, {
  54. input: {
  55. id: AT.id,
  56. enabled: false,
  57. },
  58. });
  59. const result = await shopClient.query(GET_AVAILABLE_COUNTRIES);
  60. expect(result.availableCountries.length).toBe(countries.items.length - 1);
  61. expect(result.availableCountries.find(c => c.id === AT.id)).toBeUndefined();
  62. });
  63. describe('ordering as anonymous user', () => {
  64. let firstOrderItemId: string;
  65. let createdCustomerId: string;
  66. let orderCode: string;
  67. it('addItemToOrder starts with no session token', () => {
  68. expect(shopClient.getAuthToken()).toBeFalsy();
  69. });
  70. it('activeOrder returns null before any items have been added', async () => {
  71. const result = await shopClient.query(GET_ACTIVE_ORDER);
  72. expect(result.activeOrder).toBeNull();
  73. });
  74. it('activeOrder creates an anonymous session', () => {
  75. expect(shopClient.getAuthToken()).not.toBe('');
  76. });
  77. it('addItemToOrder creates a new Order with an item', async () => {
  78. const result = await shopClient.query(ADD_ITEM_TO_ORDER, {
  79. productVariantId: 'T_1',
  80. quantity: 1,
  81. });
  82. expect(result.addItemToOrder.lines.length).toBe(1);
  83. expect(result.addItemToOrder.lines[0].quantity).toBe(1);
  84. expect(result.addItemToOrder.lines[0].productVariant.id).toBe('T_1');
  85. expect(result.addItemToOrder.lines[0].id).toBe('T_1');
  86. firstOrderItemId = result.addItemToOrder.lines[0].id;
  87. orderCode = result.addItemToOrder.code;
  88. });
  89. it(
  90. 'addItemToOrder errors with an invalid productVariantId',
  91. assertThrowsWithMessage(
  92. () =>
  93. shopClient.query(ADD_ITEM_TO_ORDER, {
  94. productVariantId: 'T_999',
  95. quantity: 1,
  96. }),
  97. `No ProductVariant with the id '999' could be found`,
  98. ),
  99. );
  100. it(
  101. 'addItemToOrder errors with a negative quantity',
  102. assertThrowsWithMessage(
  103. () =>
  104. shopClient.query(ADD_ITEM_TO_ORDER, {
  105. productVariantId: 'T_999',
  106. quantity: -3,
  107. }),
  108. `-3 is not a valid quantity for an OrderItem`,
  109. ),
  110. );
  111. it('addItemToOrder with an existing productVariantId adds quantity to the existing OrderLine', async () => {
  112. const result = await shopClient.query(ADD_ITEM_TO_ORDER, {
  113. productVariantId: 'T_1',
  114. quantity: 2,
  115. });
  116. expect(result.addItemToOrder.lines.length).toBe(1);
  117. expect(result.addItemToOrder.lines[0].quantity).toBe(3);
  118. });
  119. it(
  120. 'addItemToOrder errors when going beyond orderItemsLimit',
  121. assertThrowsWithMessage(async () => {
  122. await shopClient.query(ADD_ITEM_TO_ORDER, {
  123. productVariantId: 'T_1',
  124. quantity: 100,
  125. });
  126. }, 'Cannot add items. An order may consist of a maximum of 99 items'),
  127. );
  128. it('adjustItemQuantity adjusts the quantity', async () => {
  129. const result = await shopClient.query(ADJUST_ITEM_QUENTITY, {
  130. orderItemId: firstOrderItemId,
  131. quantity: 50,
  132. });
  133. expect(result.adjustItemQuantity.lines.length).toBe(1);
  134. expect(result.adjustItemQuantity.lines[0].quantity).toBe(50);
  135. });
  136. it(
  137. 'adjustItemQuantity errors when going beyond orderItemsLimit',
  138. assertThrowsWithMessage(async () => {
  139. await shopClient.query(ADJUST_ITEM_QUENTITY, {
  140. orderItemId: firstOrderItemId,
  141. quantity: 100,
  142. });
  143. }, 'Cannot add items. An order may consist of a maximum of 99 items'),
  144. );
  145. it(
  146. 'adjustItemQuantity errors with a negative quantity',
  147. assertThrowsWithMessage(
  148. () =>
  149. shopClient.query(ADJUST_ITEM_QUENTITY, {
  150. orderItemId: firstOrderItemId,
  151. quantity: -3,
  152. }),
  153. `-3 is not a valid quantity for an OrderItem`,
  154. ),
  155. );
  156. it(
  157. 'adjustItemQuantity errors with an invalid orderItemId',
  158. assertThrowsWithMessage(
  159. () =>
  160. shopClient.query(ADJUST_ITEM_QUENTITY, {
  161. orderItemId: 'T_999',
  162. quantity: 5,
  163. }),
  164. `This order does not contain an OrderLine with the id 999`,
  165. ),
  166. );
  167. it('removeItemFromOrder removes the correct item', async () => {
  168. const result1 = await shopClient.query(ADD_ITEM_TO_ORDER, {
  169. productVariantId: 'T_3',
  170. quantity: 3,
  171. });
  172. expect(result1.addItemToOrder.lines.length).toBe(2);
  173. expect(result1.addItemToOrder.lines.map(i => i.productVariant.id)).toEqual(['T_1', 'T_3']);
  174. const result2 = await shopClient.query(REMOVE_ITEM_FROM_ORDER, {
  175. orderItemId: firstOrderItemId,
  176. });
  177. expect(result2.removeItemFromOrder.lines.length).toBe(1);
  178. expect(result2.removeItemFromOrder.lines.map(i => i.productVariant.id)).toEqual(['T_3']);
  179. });
  180. it(
  181. 'removeItemFromOrder errors with an invalid orderItemId',
  182. assertThrowsWithMessage(
  183. () =>
  184. shopClient.query(REMOVE_ITEM_FROM_ORDER, {
  185. orderItemId: 'T_999',
  186. }),
  187. `This order does not contain an OrderLine with the id 999`,
  188. ),
  189. );
  190. it('nextOrderStates returns next valid states', async () => {
  191. const result = await shopClient.query(gql`
  192. query {
  193. nextOrderStates
  194. }
  195. `);
  196. expect(result.nextOrderStates).toEqual(['ArrangingPayment']);
  197. });
  198. it(
  199. 'transitionOrderToState throws for an invalid state',
  200. assertThrowsWithMessage(
  201. () => shopClient.query(TRANSITION_TO_STATE, { state: 'Completed' }),
  202. `Cannot transition Order from "AddingItems" to "Completed"`,
  203. ),
  204. );
  205. it(
  206. 'attempting to transition to ArrangingPayment throws when Order has no Customer',
  207. assertThrowsWithMessage(
  208. () => shopClient.query(TRANSITION_TO_STATE, { state: 'ArrangingPayment' }),
  209. `Cannot transition Order to the "ArrangingPayment" state without Customer details`,
  210. ),
  211. );
  212. it('setCustomerForOrder creates a new Customer and associates it with the Order', async () => {
  213. const result = await shopClient.query(SET_CUSTOMER, {
  214. input: {
  215. emailAddress: 'test@test.com',
  216. firstName: 'Test',
  217. lastName: 'Person',
  218. },
  219. });
  220. const customer = result.setCustomerForOrder.customer;
  221. expect(customer.firstName).toBe('Test');
  222. expect(customer.lastName).toBe('Person');
  223. expect(customer.emailAddress).toBe('test@test.com');
  224. createdCustomerId = customer.id;
  225. });
  226. it('setCustomerForOrder updates the existing customer if Customer already set', async () => {
  227. const result = await shopClient.query(SET_CUSTOMER, {
  228. input: {
  229. emailAddress: 'test@test.com',
  230. firstName: 'Changed',
  231. lastName: 'Person',
  232. },
  233. });
  234. const customer = result.setCustomerForOrder.customer;
  235. expect(customer.firstName).toBe('Changed');
  236. expect(customer.lastName).toBe('Person');
  237. expect(customer.emailAddress).toBe('test@test.com');
  238. expect(customer.id).toBe(createdCustomerId);
  239. });
  240. it('setOrderShippingAddress sets shipping address', async () => {
  241. const address: CreateAddressInput = {
  242. fullName: 'name',
  243. company: 'company',
  244. streetLine1: '12 the street',
  245. streetLine2: 'line 2',
  246. city: 'foo',
  247. province: 'bar',
  248. postalCode: '123456',
  249. countryCode: 'US',
  250. phoneNumber: '4444444',
  251. };
  252. const result = await shopClient.query(SET_SHIPPING_ADDRESS, {
  253. input: address,
  254. });
  255. expect(result.setOrderShippingAddress.shippingAddress).toEqual({
  256. fullName: 'name',
  257. company: 'company',
  258. streetLine1: '12 the street',
  259. streetLine2: 'line 2',
  260. city: 'foo',
  261. province: 'bar',
  262. postalCode: '123456',
  263. country: 'United States of America',
  264. phoneNumber: '4444444',
  265. });
  266. });
  267. it('customer default Addresses are not updated before payment', async () => {
  268. const result = await shopClient.query(gql`
  269. query {
  270. activeOrder {
  271. customer {
  272. addresses {
  273. id
  274. }
  275. }
  276. }
  277. }
  278. `);
  279. expect(result.activeOrder.customer.addresses).toEqual([]);
  280. });
  281. it('can transition to ArrangingPayment once Customer has been set', async () => {
  282. const result = await shopClient.query(TRANSITION_TO_STATE, { state: 'ArrangingPayment' });
  283. expect(result.transitionOrderToState).toEqual({ id: 'T_1', state: 'ArrangingPayment' });
  284. });
  285. it('adds a successful payment and transitions Order state', async () => {
  286. const result = await shopClient.query(ADD_PAYMENT, {
  287. input: {
  288. method: testPaymentMethod.code,
  289. metadata: {},
  290. },
  291. });
  292. const payment = result.addPaymentToOrder.payments[0];
  293. expect(result.addPaymentToOrder.state).toBe('PaymentSettled');
  294. expect(result.addPaymentToOrder.active).toBe(false);
  295. expect(result.addPaymentToOrder.payments.length).toBe(1);
  296. expect(payment.method).toBe(testPaymentMethod.code);
  297. expect(payment.state).toBe('Settled');
  298. });
  299. it('activeOrder is null after payment', async () => {
  300. const result = await shopClient.query(GET_ACTIVE_ORDER);
  301. expect(result.activeOrder).toBeNull();
  302. });
  303. it('customer default Addresses are updated after payment', async () => {
  304. const result = await adminClient.query<GetCustomer.Query, GetCustomer.Variables>(GET_CUSTOMER, {
  305. id: createdCustomerId,
  306. });
  307. // tslint:disable-next-line:no-non-null-assertion
  308. const address = result.customer!.addresses![0];
  309. expect(address.streetLine1).toBe('12 the street');
  310. expect(address.postalCode).toBe('123456');
  311. expect(address.defaultBillingAddress).toBe(true);
  312. expect(address.defaultShippingAddress).toBe(true);
  313. });
  314. });
  315. describe('ordering as authenticated user', () => {
  316. let firstOrderItemId: string;
  317. let activeOrder: any;
  318. let authenticatedUserEmailAddress: string;
  319. let customers: GetCustomerList.Items[];
  320. const password = 'test';
  321. beforeAll(async () => {
  322. await adminClient.asSuperAdmin();
  323. const result = await adminClient.query<GetCustomerList.Query, GetCustomerList.Variables>(
  324. GET_CUSTOMER_LIST,
  325. {
  326. options: {
  327. take: 2,
  328. },
  329. },
  330. );
  331. customers = result.customers.items;
  332. authenticatedUserEmailAddress = customers[0].emailAddress;
  333. await shopClient.asUserWithCredentials(authenticatedUserEmailAddress, password);
  334. });
  335. it('activeOrder returns null before any items have been added', async () => {
  336. const result = await shopClient.query(GET_ACTIVE_ORDER);
  337. expect(result.activeOrder).toBeNull();
  338. });
  339. it('addItemToOrder creates a new Order with an item', async () => {
  340. const result = await shopClient.query(ADD_ITEM_TO_ORDER, {
  341. productVariantId: 'T_1',
  342. quantity: 1,
  343. });
  344. expect(result.addItemToOrder.lines.length).toBe(1);
  345. expect(result.addItemToOrder.lines[0].quantity).toBe(1);
  346. expect(result.addItemToOrder.lines[0].productVariant.id).toBe('T_1');
  347. activeOrder = result.addItemToOrder;
  348. firstOrderItemId = result.addItemToOrder.lines[0].id;
  349. });
  350. it('activeOrder returns order after item has been added', async () => {
  351. const result = await shopClient.query(GET_ACTIVE_ORDER);
  352. expect(result.activeOrder.id).toBe(activeOrder.id);
  353. expect(result.activeOrder.state).toBe('AddingItems');
  354. });
  355. it('addItemToOrder with an existing productVariantId adds quantity to the existing OrderLine', async () => {
  356. const result = await shopClient.query(ADD_ITEM_TO_ORDER, {
  357. productVariantId: 'T_1',
  358. quantity: 2,
  359. });
  360. expect(result.addItemToOrder.lines.length).toBe(1);
  361. expect(result.addItemToOrder.lines[0].quantity).toBe(3);
  362. });
  363. it('adjustItemQuantity adjusts the quantity', async () => {
  364. const result = await shopClient.query(ADJUST_ITEM_QUENTITY, {
  365. orderItemId: firstOrderItemId,
  366. quantity: 50,
  367. });
  368. expect(result.adjustItemQuantity.lines.length).toBe(1);
  369. expect(result.adjustItemQuantity.lines[0].quantity).toBe(50);
  370. });
  371. it('removeItemFromOrder removes the correct item', async () => {
  372. const result1 = await shopClient.query(ADD_ITEM_TO_ORDER, {
  373. productVariantId: 'T_3',
  374. quantity: 3,
  375. });
  376. expect(result1.addItemToOrder.lines.length).toBe(2);
  377. expect(result1.addItemToOrder.lines.map(i => i.productVariant.id)).toEqual(['T_1', 'T_3']);
  378. const result2 = await shopClient.query(REMOVE_ITEM_FROM_ORDER, {
  379. orderItemId: firstOrderItemId,
  380. });
  381. expect(result2.removeItemFromOrder.lines.length).toBe(1);
  382. expect(result2.removeItemFromOrder.lines.map(i => i.productVariant.id)).toEqual(['T_3']);
  383. });
  384. it('nextOrderStates returns next valid states', async () => {
  385. const result = await shopClient.query(GET_NEXT_STATES);
  386. expect(result.nextOrderStates).toEqual(['ArrangingPayment']);
  387. });
  388. it('logging out and back in again resumes the last active order', async () => {
  389. await shopClient.asAnonymousUser();
  390. const result1 = await shopClient.query(GET_ACTIVE_ORDER);
  391. expect(result1.activeOrder).toBeNull();
  392. await shopClient.asUserWithCredentials(authenticatedUserEmailAddress, password);
  393. const result2 = await shopClient.query(GET_ACTIVE_ORDER);
  394. expect(result2.activeOrder.id).toBe(activeOrder.id);
  395. });
  396. describe('shipping', () => {
  397. let shippingMethods: any;
  398. it(
  399. 'setOrderShippingAddress throws with invalid countryCode',
  400. assertThrowsWithMessage(() => {
  401. const address: CreateAddressInput = {
  402. streetLine1: '12 the street',
  403. countryCode: 'INVALID',
  404. };
  405. return shopClient.query(SET_SHIPPING_ADDRESS, {
  406. input: address,
  407. });
  408. }, `The countryCode "INVALID" was not recognized`),
  409. );
  410. it('setOrderShippingAddress sets shipping address', async () => {
  411. const address: CreateAddressInput = {
  412. fullName: 'name',
  413. company: 'company',
  414. streetLine1: '12 the street',
  415. streetLine2: 'line 2',
  416. city: 'foo',
  417. province: 'bar',
  418. postalCode: '123456',
  419. countryCode: 'US',
  420. phoneNumber: '4444444',
  421. };
  422. const result = await shopClient.query(SET_SHIPPING_ADDRESS, {
  423. input: address,
  424. });
  425. expect(result.setOrderShippingAddress.shippingAddress).toEqual({
  426. fullName: 'name',
  427. company: 'company',
  428. streetLine1: '12 the street',
  429. streetLine2: 'line 2',
  430. city: 'foo',
  431. province: 'bar',
  432. postalCode: '123456',
  433. country: 'United States of America',
  434. phoneNumber: '4444444',
  435. });
  436. });
  437. it('eligibleShippingMethods lists shipping methods', async () => {
  438. const result = await shopClient.query(GET_ELIGIBLE_SHIPPING_METHODS);
  439. shippingMethods = result.eligibleShippingMethods;
  440. expect(shippingMethods).toEqual([
  441. { id: 'T_1', price: 500, description: 'Standard Shipping' },
  442. { id: 'T_2', price: 1000, description: 'Express Shipping' },
  443. ]);
  444. });
  445. it('shipping is initially unset', async () => {
  446. const result = await shopClient.query(GET_ACTIVE_ORDER);
  447. expect(result.activeOrder.shipping).toEqual(0);
  448. expect(result.activeOrder.shippingMethod).toEqual(null);
  449. });
  450. it('setOrderShippingMethod sets the shipping method', async () => {
  451. const result = await shopClient.query(SET_SHIPPING_METHOD, {
  452. id: shippingMethods[1].id,
  453. });
  454. const activeOrderResult = await shopClient.query(GET_ACTIVE_ORDER);
  455. const order = activeOrderResult.activeOrder;
  456. expect(order.shipping).toBe(shippingMethods[1].price);
  457. expect(order.shippingMethod.id).toBe(shippingMethods[1].id);
  458. expect(order.shippingMethod.description).toBe(shippingMethods[1].description);
  459. });
  460. it('shipping method is preserved after adjustItemQuantity', async () => {
  461. const activeOrderResult = await shopClient.query(GET_ACTIVE_ORDER);
  462. activeOrder = activeOrderResult.activeOrder;
  463. const result = await shopClient.query(ADJUST_ITEM_QUENTITY, {
  464. orderItemId: activeOrder.lines[0].id,
  465. quantity: 10,
  466. });
  467. expect(result.adjustItemQuantity.shipping).toBe(shippingMethods[1].price);
  468. expect(result.adjustItemQuantity.shippingMethod.id).toBe(shippingMethods[1].id);
  469. expect(result.adjustItemQuantity.shippingMethod.description).toBe(
  470. shippingMethods[1].description,
  471. );
  472. });
  473. });
  474. describe('payment', () => {
  475. it(
  476. 'attempting add a Payment throws error when in AddingItems state',
  477. assertThrowsWithMessage(
  478. () =>
  479. shopClient.query(ADD_PAYMENT, {
  480. input: {
  481. method: testPaymentMethod.code,
  482. metadata: {},
  483. },
  484. }),
  485. `A Payment may only be added when Order is in "ArrangingPayment" state`,
  486. ),
  487. );
  488. it('transitions to the ArrangingPayment state', async () => {
  489. const result = await shopClient.query(TRANSITION_TO_STATE, { state: 'ArrangingPayment' });
  490. expect(result.transitionOrderToState).toEqual({
  491. id: activeOrder.id,
  492. state: 'ArrangingPayment',
  493. });
  494. });
  495. it(
  496. 'attempting to add an item throws error when in ArrangingPayment state',
  497. assertThrowsWithMessage(
  498. () =>
  499. shopClient.query(ADD_ITEM_TO_ORDER, {
  500. productVariantId: 'T_4',
  501. quantity: 1,
  502. }),
  503. `Order contents may only be modified when in the "AddingItems" state`,
  504. ),
  505. );
  506. it(
  507. 'attempting to modify item quantity throws error when in ArrangingPayment state',
  508. assertThrowsWithMessage(
  509. () =>
  510. shopClient.query(ADJUST_ITEM_QUENTITY, {
  511. orderItemId: activeOrder.lines[0].id,
  512. quantity: 12,
  513. }),
  514. `Order contents may only be modified when in the "AddingItems" state`,
  515. ),
  516. );
  517. it(
  518. 'attempting to remove an item throws error when in ArrangingPayment state',
  519. assertThrowsWithMessage(
  520. () =>
  521. shopClient.query(REMOVE_ITEM_FROM_ORDER, {
  522. orderItemId: activeOrder.lines[0].id,
  523. }),
  524. `Order contents may only be modified when in the "AddingItems" state`,
  525. ),
  526. );
  527. it(
  528. 'attempting to setOrderShippingMethod throws error when in ArrangingPayment state',
  529. assertThrowsWithMessage(async () => {
  530. const shippingMethodsResult = await shopClient.query(GET_ELIGIBLE_SHIPPING_METHODS);
  531. const shippingMethods = shippingMethodsResult.eligibleShippingMethods;
  532. return shopClient.query(SET_SHIPPING_METHOD, {
  533. id: shippingMethods[0].id,
  534. });
  535. }, `Order contents may only be modified when in the "AddingItems" state`),
  536. );
  537. it('adds a declined payment', async () => {
  538. const result = await shopClient.query(ADD_PAYMENT, {
  539. input: {
  540. method: testFailingPaymentMethod.code,
  541. metadata: {
  542. foo: 'bar',
  543. },
  544. },
  545. });
  546. const payment = result.addPaymentToOrder.payments[0];
  547. expect(result.addPaymentToOrder.payments.length).toBe(1);
  548. expect(payment.method).toBe(testFailingPaymentMethod.code);
  549. expect(payment.state).toBe('Declined');
  550. expect(payment.transactionId).toBe(null);
  551. expect(payment.metadata).toEqual({
  552. foo: 'bar',
  553. });
  554. });
  555. it('adds a successful payment and transitions Order state', async () => {
  556. const result = await shopClient.query(ADD_PAYMENT, {
  557. input: {
  558. method: testPaymentMethod.code,
  559. metadata: {
  560. baz: 'quux',
  561. },
  562. },
  563. });
  564. const payment = result.addPaymentToOrder.payments[0];
  565. expect(result.addPaymentToOrder.state).toBe('PaymentSettled');
  566. expect(result.addPaymentToOrder.active).toBe(false);
  567. expect(result.addPaymentToOrder.payments.length).toBe(1);
  568. expect(payment.method).toBe(testPaymentMethod.code);
  569. expect(payment.state).toBe('Settled');
  570. expect(payment.transactionId).toBe('12345');
  571. expect(payment.metadata).toEqual({
  572. baz: 'quux',
  573. });
  574. });
  575. });
  576. describe('orderByCode', () => {
  577. describe('immediately after Order is placed', () => {
  578. it('works when authenticated', async () => {
  579. const result = await shopClient.query(GET_ORDER_BY_CODE, {
  580. code: activeOrder.code,
  581. });
  582. expect(result.orderByCode.id).toBe(activeOrder.id);
  583. });
  584. it('works when anonymous', async () => {
  585. await shopClient.asAnonymousUser();
  586. const result = await shopClient.query(GET_ORDER_BY_CODE, {
  587. code: activeOrder.code,
  588. });
  589. expect(result.orderByCode.id).toBe(activeOrder.id);
  590. });
  591. it(
  592. `throws error for another user's Order`,
  593. assertThrowsWithMessage(async () => {
  594. authenticatedUserEmailAddress = customers[1].emailAddress;
  595. await shopClient.asUserWithCredentials(authenticatedUserEmailAddress, password);
  596. return shopClient.query(GET_ORDER_BY_CODE, {
  597. code: activeOrder.code,
  598. });
  599. }, `You are not currently authorized to perform this action`),
  600. );
  601. });
  602. });
  603. });
  604. });
  605. const testPaymentMethod = new PaymentMethodHandler({
  606. code: 'test-payment-method',
  607. description: 'Test Payment Method',
  608. args: {},
  609. createPayment: (order, args, metadata) => {
  610. return {
  611. amount: order.total,
  612. state: 'Settled',
  613. transactionId: '12345',
  614. metadata,
  615. };
  616. },
  617. });
  618. const testFailingPaymentMethod = new PaymentMethodHandler({
  619. code: 'test-failing-payment-method',
  620. description: 'Test Failing Payment Method',
  621. args: {},
  622. createPayment: (order, args, metadata) => {
  623. return {
  624. amount: order.total,
  625. state: 'Declined',
  626. metadata,
  627. };
  628. },
  629. });
  630. const TEST_ORDER_FRAGMENT = gql`
  631. fragment TestOrderFragment on Order {
  632. id
  633. code
  634. state
  635. active
  636. lines {
  637. id
  638. quantity
  639. productVariant {
  640. id
  641. }
  642. }
  643. shipping
  644. shippingMethod {
  645. id
  646. code
  647. description
  648. }
  649. customer {
  650. id
  651. }
  652. }
  653. `;
  654. const GET_ACTIVE_ORDER = gql`
  655. query {
  656. activeOrder {
  657. ...TestOrderFragment
  658. }
  659. }
  660. ${TEST_ORDER_FRAGMENT}
  661. `;
  662. const ADD_ITEM_TO_ORDER = gql`
  663. mutation AddItemToOrder($productVariantId: ID!, $quantity: Int!) {
  664. addItemToOrder(productVariantId: $productVariantId, quantity: $quantity) {
  665. ...TestOrderFragment
  666. }
  667. }
  668. ${TEST_ORDER_FRAGMENT}
  669. `;
  670. const ADJUST_ITEM_QUENTITY = gql`
  671. mutation AdjustItemQuantity($orderItemId: ID!, $quantity: Int!) {
  672. adjustItemQuantity(orderItemId: $orderItemId, quantity: $quantity) {
  673. ...TestOrderFragment
  674. }
  675. }
  676. ${TEST_ORDER_FRAGMENT}
  677. `;
  678. const REMOVE_ITEM_FROM_ORDER = gql`
  679. mutation RemoveItemFromOrder($orderItemId: ID!) {
  680. removeItemFromOrder(orderItemId: $orderItemId) {
  681. ...TestOrderFragment
  682. }
  683. }
  684. ${TEST_ORDER_FRAGMENT}
  685. `;
  686. const GET_NEXT_STATES = gql`
  687. query {
  688. nextOrderStates
  689. }
  690. `;
  691. const TRANSITION_TO_STATE = gql`
  692. mutation TransitionToState($state: String!) {
  693. transitionOrderToState(state: $state) {
  694. id
  695. state
  696. }
  697. }
  698. `;
  699. const GET_ELIGIBLE_SHIPPING_METHODS = gql`
  700. query {
  701. eligibleShippingMethods {
  702. id
  703. price
  704. description
  705. }
  706. }
  707. `;
  708. const SET_SHIPPING_ADDRESS = gql`
  709. mutation SetShippingAddress($input: CreateAddressInput!) {
  710. setOrderShippingAddress(input: $input) {
  711. shippingAddress {
  712. fullName
  713. company
  714. streetLine1
  715. streetLine2
  716. city
  717. province
  718. postalCode
  719. country
  720. phoneNumber
  721. }
  722. }
  723. }
  724. `;
  725. const SET_SHIPPING_METHOD = gql`
  726. mutation SetShippingMethod($id: ID!) {
  727. setOrderShippingMethod(shippingMethodId: $id) {
  728. shipping
  729. shippingMethod {
  730. id
  731. code
  732. description
  733. }
  734. }
  735. }
  736. `;
  737. const ADD_PAYMENT = gql`
  738. mutation AddPaymentToOrder($input: PaymentInput!) {
  739. addPaymentToOrder(input: $input) {
  740. ...TestOrderFragment
  741. payments {
  742. id
  743. transactionId
  744. method
  745. amount
  746. state
  747. metadata
  748. }
  749. }
  750. }
  751. ${TEST_ORDER_FRAGMENT}
  752. `;
  753. const SET_CUSTOMER = gql`
  754. mutation SetCustomerForOrder($input: CreateCustomerInput!) {
  755. setCustomerForOrder(input: $input) {
  756. id
  757. customer {
  758. id
  759. emailAddress
  760. firstName
  761. lastName
  762. }
  763. }
  764. }
  765. `;
  766. const GET_ORDER_BY_CODE = gql`
  767. query GetOrderByCode($code: String!) {
  768. orderByCode(code: $code) {
  769. ...TestOrderFragment
  770. }
  771. }
  772. ${TEST_ORDER_FRAGMENT}
  773. `;
  774. const GET_AVAILABLE_COUNTRIES = gql`
  775. query {
  776. availableCountries {
  777. id
  778. code
  779. }
  780. }
  781. `;