shop-order.e2e-spec.ts 42 KB

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