shop-order.e2e-spec.ts 36 KB

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