shop-order.e2e-spec.ts 101 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373
  1. /* eslint-disable @typescript-eslint/no-non-null-assertion */
  2. import { ErrorCode } from '@vendure/common/lib/generated-shop-types';
  3. import { GlobalFlag, LanguageCode } from '@vendure/common/lib/generated-types';
  4. import { pick } from '@vendure/common/lib/pick';
  5. import {
  6. Asset,
  7. defaultShippingCalculator,
  8. defaultShippingEligibilityChecker,
  9. manualFulfillmentHandler,
  10. mergeConfig,
  11. } from '@vendure/core';
  12. import { createErrorResultGuard, createTestEnvironment, ErrorResultGuard } from '@vendure/testing';
  13. import { fail } from 'assert';
  14. import path from 'path';
  15. import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
  16. import { initialData } from '../../../e2e-common/e2e-initial-data';
  17. import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-config';
  18. import {
  19. testErrorPaymentMethod,
  20. testFailingPaymentMethod,
  21. testSuccessfulPaymentMethod,
  22. } from './fixtures/test-payment-methods';
  23. import {
  24. countryCodeShippingEligibilityChecker,
  25. hydratingShippingEligibilityChecker,
  26. } from './fixtures/test-shipping-eligibility-checkers';
  27. import { ResultOf, VariablesOf } from './graphql/graphql-admin';
  28. import { FragmentOf } from './graphql/graphql-shop';
  29. import {
  30. attemptLoginDocument,
  31. cancelOrderDocument,
  32. createShippingMethodDocument,
  33. deleteProductDocument,
  34. deleteProductVariantDocument,
  35. deleteShippingMethodDocument,
  36. getCountryListDocument,
  37. getCustomerDocument,
  38. getCustomerListDocument,
  39. getProductWithVariantsDocument,
  40. getShippingMethodListDocument,
  41. updateCountryDocument,
  42. updateProductDocument,
  43. updateProductVariantsDocument,
  44. } from './graphql/shared-definitions';
  45. import {
  46. activeOrderCustomerDocument,
  47. addItemToOrderDocument,
  48. addItemToOrderWithCustomFieldsDocument,
  49. addMultipleItemsToOrderWithCustomFieldsDocument,
  50. addPaymentDocument,
  51. adjustItemQuantityDocument,
  52. adjustOrderLineWithCustomFieldsDocument,
  53. currentUserFragment,
  54. getActiveOrderAddressesDocument,
  55. getActiveOrderDocument,
  56. getActiveOrderOrdersDocument,
  57. getActiveOrderPaymentsDocument,
  58. getActiveOrderShippingBillingDocument,
  59. getActiveOrderWithPaymentsDocument,
  60. getAvailableCountriesDocument,
  61. getEligibleShippingMethodsDocument,
  62. getNextStatesDocument,
  63. getOrderByCodeDocument,
  64. getOrderCustomFieldsDocument,
  65. getOrderWithOrderLineCustomFieldsDocument,
  66. logOutDocument,
  67. removeAllOrderLinesDocument,
  68. removeItemFromOrderDocument,
  69. setBillingAddressDocument,
  70. setCustomerDocument,
  71. setOrderCustomFieldsDocument,
  72. setShippingAddressDocument,
  73. setShippingMethodDocument,
  74. testOrderFragment,
  75. transitionToStateDocument,
  76. unsetBillingAddressDocument,
  77. unsetShippingAddressDocument,
  78. updatedOrderFragment,
  79. } from './graphql/shop-definitions';
  80. import { assertThrowsWithMessage } from './utils/assert-throws-with-message';
  81. type CreateAddressInput = VariablesOf<typeof setShippingAddressDocument>['input'];
  82. type CreateShippingMethodInput = VariablesOf<typeof createShippingMethodDocument>['input'];
  83. describe('Shop orders', () => {
  84. const { server, adminClient, shopClient } = createTestEnvironment(
  85. mergeConfig(testConfig(), {
  86. paymentOptions: {
  87. paymentMethodHandlers: [
  88. testSuccessfulPaymentMethod,
  89. testFailingPaymentMethod,
  90. testErrorPaymentMethod,
  91. ],
  92. },
  93. shippingOptions: {
  94. shippingEligibilityCheckers: [
  95. defaultShippingEligibilityChecker,
  96. countryCodeShippingEligibilityChecker,
  97. hydratingShippingEligibilityChecker,
  98. ],
  99. },
  100. customFields: {
  101. Order: [
  102. { name: 'giftWrap', type: 'boolean', defaultValue: false },
  103. { name: 'orderImage', type: 'relation', entity: Asset },
  104. ],
  105. OrderLine: [
  106. { name: 'notes', type: 'string' },
  107. { name: 'privateField', type: 'string', public: false },
  108. { name: 'lineImage', type: 'relation', entity: Asset },
  109. { name: 'lineImages', type: 'relation', list: true, entity: Asset },
  110. { name: 'dropShip', type: 'boolean', defaultValue: false },
  111. ],
  112. },
  113. orderOptions: {
  114. orderItemsLimit: 199,
  115. },
  116. }),
  117. );
  118. type OrderSuccessResult = FragmentOf<typeof updatedOrderFragment>;
  119. const orderResultGuard: ErrorResultGuard<OrderSuccessResult> = createErrorResultGuard(
  120. input => !!input.lines,
  121. );
  122. const activeOrderGuard: ErrorResultGuard<
  123. NonNullable<ResultOf<typeof getActiveOrderShippingBillingDocument>['activeOrder']>
  124. > = createErrorResultGuard(input => input !== null);
  125. const orderWithCustomFieldsGuard: ErrorResultGuard<
  126. NonNullable<ResultOf<typeof getOrderWithOrderLineCustomFieldsDocument>['activeOrder']>
  127. > = createErrorResultGuard(input => input !== null);
  128. type CurrentUserShopFragment = FragmentOf<typeof currentUserFragment>;
  129. const currentUserGuard: ErrorResultGuard<CurrentUserShopFragment> = createErrorResultGuard(
  130. input => input.identifier != null,
  131. );
  132. type ActiveOrderCustomerFragment = FragmentOf<typeof activeOrderCustomerDocument>;
  133. const setCustomerForOrderGuard: ErrorResultGuard<ActiveOrderCustomerFragment> = createErrorResultGuard(
  134. input => 'lines' in input && !!input.lines,
  135. );
  136. type TestOrderFragmentType = FragmentOf<typeof testOrderFragment>;
  137. const testOrderGuard: ErrorResultGuard<TestOrderFragmentType> = createErrorResultGuard(
  138. input => !!input.lines,
  139. );
  140. beforeAll(async () => {
  141. await server.init({
  142. initialData: {
  143. ...initialData,
  144. paymentMethods: [
  145. {
  146. name: testSuccessfulPaymentMethod.code,
  147. handler: { code: testSuccessfulPaymentMethod.code, arguments: [] },
  148. },
  149. {
  150. name: testFailingPaymentMethod.code,
  151. handler: { code: testFailingPaymentMethod.code, arguments: [] },
  152. },
  153. {
  154. name: testErrorPaymentMethod.code,
  155. handler: { code: testErrorPaymentMethod.code, arguments: [] },
  156. },
  157. ],
  158. },
  159. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-full.csv'),
  160. customerCount: 3,
  161. });
  162. await adminClient.asSuperAdmin();
  163. }, TEST_SETUP_TIMEOUT_MS);
  164. afterAll(async () => {
  165. await server.destroy();
  166. });
  167. it('availableCountries returns enabled countries', async () => {
  168. // disable Austria
  169. const { countries } = await adminClient.query(getCountryListDocument, {});
  170. const AT = countries.items.find(c => c.code === 'AT')!;
  171. await adminClient.query(updateCountryDocument, {
  172. input: {
  173. id: AT.id,
  174. enabled: false,
  175. },
  176. });
  177. const result = await shopClient.query(getAvailableCountriesDocument);
  178. expect(result.availableCountries.length).toBe(countries.items.length - 1);
  179. expect(result.availableCountries.find(c => c.id === AT.id)).toBeUndefined();
  180. });
  181. describe('ordering as anonymous user', () => {
  182. let firstOrderLineId: string;
  183. let createdCustomerId: string;
  184. let orderCode: string;
  185. it('addItemToOrder starts with no session token', () => {
  186. expect(shopClient.getAuthToken()).toBeFalsy();
  187. });
  188. it('activeOrder returns null before any items have been added', async () => {
  189. const result = await shopClient.query(getActiveOrderDocument);
  190. expect(result.activeOrder).toBeNull();
  191. });
  192. it('activeOrder creates an anonymous session', () => {
  193. expect(shopClient.getAuthToken()).not.toBe('');
  194. });
  195. it('addItemToOrder creates a new Order with an item', async () => {
  196. const { addItemToOrder } = await shopClient.query(addItemToOrderDocument, {
  197. productVariantId: 'T_1',
  198. quantity: 1,
  199. });
  200. orderResultGuard.assertSuccess(addItemToOrder);
  201. expect(addItemToOrder.lines.length).toBe(1);
  202. expect(addItemToOrder.lines[0].quantity).toBe(1);
  203. expect(addItemToOrder.lines[0].productVariant.id).toBe('T_1');
  204. expect(addItemToOrder.lines[0].id).toBe('T_1');
  205. firstOrderLineId = addItemToOrder.lines[0].id;
  206. orderCode = addItemToOrder.code;
  207. });
  208. it(
  209. 'addItemToOrder errors with an invalid productVariantId',
  210. assertThrowsWithMessage(
  211. () =>
  212. shopClient.query(addItemToOrderDocument, {
  213. productVariantId: 'T_999',
  214. quantity: 1,
  215. }),
  216. 'No ProductVariant with the id "999" could be found',
  217. ),
  218. );
  219. it('addItemToOrder errors with a negative quantity', async () => {
  220. const { addItemToOrder } = await shopClient.query(addItemToOrderDocument, {
  221. productVariantId: 'T_999',
  222. quantity: -3,
  223. });
  224. orderResultGuard.assertErrorResult(addItemToOrder);
  225. expect(addItemToOrder.message).toEqual('The quantity for an OrderItem cannot be negative');
  226. expect(addItemToOrder.errorCode).toEqual(ErrorCode.NEGATIVE_QUANTITY_ERROR);
  227. });
  228. it('addItemToOrder with an existing productVariantId adds quantity to the existing OrderLine', async () => {
  229. const { addItemToOrder } = await shopClient.query(addItemToOrderDocument, {
  230. productVariantId: 'T_1',
  231. quantity: 2,
  232. });
  233. orderResultGuard.assertSuccess(addItemToOrder);
  234. expect(addItemToOrder.lines.length).toBe(1);
  235. expect(addItemToOrder.lines[0].quantity).toBe(3);
  236. });
  237. describe('OrderLine customFields', () => {
  238. it('addItemToOrder with private customFields errors', async () => {
  239. try {
  240. await shopClient.query(addItemToOrderWithCustomFieldsDocument, {
  241. productVariantId: 'T_2',
  242. quantity: 1,
  243. customFields: {
  244. privateField: 'oh no!', // Testing that private fields are rejected
  245. },
  246. });
  247. fail('Should have thrown');
  248. } catch (e: any) {
  249. expect(e.response.errors[0].extensions.code).toBe('BAD_USER_INPUT');
  250. }
  251. });
  252. it('addItemToOrder with equal customFields adds quantity to the existing OrderLine', async () => {
  253. const { addItemToOrder: add1 } = await shopClient.query(
  254. addItemToOrderWithCustomFieldsDocument,
  255. {
  256. productVariantId: 'T_2',
  257. quantity: 1,
  258. customFields: {
  259. notes: 'note1',
  260. },
  261. },
  262. );
  263. orderResultGuard.assertSuccess(add1);
  264. expect(add1.lines.length).toBe(2);
  265. expect(add1.lines[1].quantity).toBe(1);
  266. const { addItemToOrder: add2 } = await shopClient.query(
  267. addItemToOrderWithCustomFieldsDocument,
  268. {
  269. productVariantId: 'T_2',
  270. quantity: 1,
  271. customFields: {
  272. notes: 'note1',
  273. },
  274. },
  275. );
  276. orderResultGuard.assertSuccess(add2);
  277. expect(add2.lines.length).toBe(2);
  278. expect(add2.lines[1].quantity).toBe(2);
  279. await shopClient.query(removeItemFromOrderDocument, {
  280. orderLineId: add2.lines[1].id,
  281. });
  282. });
  283. it('addItemToOrder with different customFields adds quantity to a new OrderLine', async () => {
  284. const { addItemToOrder: add1 } = await shopClient.query(
  285. addItemToOrderWithCustomFieldsDocument,
  286. {
  287. productVariantId: 'T_3',
  288. quantity: 1,
  289. customFields: {
  290. notes: 'note2',
  291. },
  292. },
  293. );
  294. orderResultGuard.assertSuccess(add1);
  295. expect(add1.lines.length).toBe(2);
  296. expect(add1.lines[1].quantity).toBe(1);
  297. const { addItemToOrder: add2 } = await shopClient.query(
  298. addItemToOrderWithCustomFieldsDocument,
  299. {
  300. productVariantId: 'T_3',
  301. quantity: 1,
  302. customFields: {
  303. notes: 'note3',
  304. },
  305. },
  306. );
  307. orderResultGuard.assertSuccess(add2);
  308. expect(add2.lines.length).toBe(3);
  309. expect(add2.lines[1].quantity).toBe(1);
  310. expect(add2.lines[2].quantity).toBe(1);
  311. await shopClient.query(removeItemFromOrderDocument, {
  312. orderLineId: add2.lines[1].id,
  313. });
  314. await shopClient.query(removeItemFromOrderDocument, {
  315. orderLineId: add2.lines[2].id,
  316. });
  317. });
  318. // https://github.com/vendure-ecommerce/vendure/issues/1670
  319. it('adding a second item after adjusting custom field adds new OrderLine', async () => {
  320. const { addItemToOrder: add1 } = await shopClient.query(
  321. addItemToOrderWithCustomFieldsDocument,
  322. {
  323. productVariantId: 'T_3',
  324. quantity: 1,
  325. },
  326. );
  327. orderResultGuard.assertSuccess(add1);
  328. expect(add1.lines.length).toBe(2);
  329. expect(add1.lines[1].quantity).toBe(1);
  330. const { adjustOrderLine } = await shopClient.query(adjustOrderLineWithCustomFieldsDocument, {
  331. orderLineId: add1.lines[1].id,
  332. quantity: 1,
  333. customFields: {
  334. notes: 'updated notes',
  335. },
  336. });
  337. orderResultGuard.assertSuccess(adjustOrderLine);
  338. expect(adjustOrderLine.lines[1].customFields).toEqual({
  339. lineImage: null,
  340. lineImages: [],
  341. notes: 'updated notes',
  342. });
  343. const { activeOrder } = await shopClient.query(getOrderWithOrderLineCustomFieldsDocument);
  344. orderWithCustomFieldsGuard.assertSuccess(activeOrder);
  345. expect(activeOrder.lines[1].customFields).toEqual({
  346. lineImage: null,
  347. lineImages: [],
  348. notes: 'updated notes',
  349. });
  350. const updatedNotesLineId = activeOrder.lines[1].id;
  351. const { addItemToOrder: add2 } = await shopClient.query(
  352. addItemToOrderWithCustomFieldsDocument,
  353. {
  354. productVariantId: 'T_3',
  355. quantity: 1,
  356. },
  357. );
  358. orderResultGuard.assertSuccess(add2);
  359. expect(add2.lines.length).toBe(3);
  360. expect(add2.lines[1].quantity).toBe(1);
  361. expect(add2.lines[2].quantity).toBe(1);
  362. const { activeOrder: activeOrder2 } = await shopClient.query(
  363. getOrderWithOrderLineCustomFieldsDocument,
  364. );
  365. orderWithCustomFieldsGuard.assertSuccess(activeOrder2);
  366. expect(
  367. activeOrder2.lines.find((l: any) => l.id === updatedNotesLineId)?.customFields,
  368. ).toEqual({
  369. lineImage: null,
  370. lineImages: [],
  371. notes: 'updated notes',
  372. });
  373. // clean up
  374. await shopClient.query(removeItemFromOrderDocument, {
  375. orderLineId: add2.lines[1].id,
  376. });
  377. const { removeOrderLine } = await shopClient.query(removeItemFromOrderDocument, {
  378. orderLineId: add2.lines[2].id,
  379. });
  380. orderResultGuard.assertSuccess(removeOrderLine);
  381. expect(removeOrderLine.lines.length).toBe(1);
  382. });
  383. it('addItemToOrder with relation customField', async () => {
  384. const { addItemToOrder } = await shopClient.query(addItemToOrderWithCustomFieldsDocument, {
  385. productVariantId: 'T_3',
  386. quantity: 1,
  387. customFields: {
  388. lineImageId: 'T_1',
  389. },
  390. });
  391. orderResultGuard.assertSuccess(addItemToOrder);
  392. expect(addItemToOrder.lines.length).toBe(2);
  393. expect(addItemToOrder.lines[1].quantity).toBe(1);
  394. const { activeOrder } = await shopClient.query(getOrderWithOrderLineCustomFieldsDocument);
  395. orderWithCustomFieldsGuard.assertSuccess(activeOrder);
  396. expect(activeOrder.lines[1].customFields.lineImage).toEqual({ id: 'T_1' });
  397. });
  398. it('addItemToOrder with equal relation customField adds to quantity', async () => {
  399. const { addItemToOrder } = await shopClient.query(addItemToOrderWithCustomFieldsDocument, {
  400. productVariantId: 'T_3',
  401. quantity: 1,
  402. customFields: {
  403. lineImageId: 'T_1',
  404. },
  405. });
  406. orderResultGuard.assertSuccess(addItemToOrder);
  407. expect(addItemToOrder.lines.length).toBe(2);
  408. expect(addItemToOrder.lines[1].quantity).toBe(2);
  409. const { activeOrder } = await shopClient.query(getOrderWithOrderLineCustomFieldsDocument);
  410. orderWithCustomFieldsGuard.assertSuccess(activeOrder);
  411. expect(activeOrder.lines[1].customFields.lineImage).toEqual({ id: 'T_1' });
  412. });
  413. it('addItemToOrder with different relation customField adds new line', async () => {
  414. const { addItemToOrder } = await shopClient.query(addItemToOrderWithCustomFieldsDocument, {
  415. productVariantId: 'T_3',
  416. quantity: 1,
  417. customFields: {
  418. lineImageId: 'T_2',
  419. },
  420. });
  421. orderResultGuard.assertSuccess(addItemToOrder);
  422. expect(addItemToOrder.lines.length).toBe(3);
  423. expect(addItemToOrder.lines[2].quantity).toBe(1);
  424. const { activeOrder } = await shopClient.query(getOrderWithOrderLineCustomFieldsDocument);
  425. orderWithCustomFieldsGuard.assertSuccess(activeOrder);
  426. expect(activeOrder.lines[2].customFields.lineImage).toEqual({ id: 'T_2' });
  427. });
  428. it('adjustOrderLine updates relation reference', async () => {
  429. const { activeOrder } = await shopClient.query(getOrderWithOrderLineCustomFieldsDocument);
  430. orderWithCustomFieldsGuard.assertSuccess(activeOrder);
  431. const { adjustOrderLine } = await shopClient.query(adjustOrderLineWithCustomFieldsDocument, {
  432. orderLineId: activeOrder.lines[2].id,
  433. quantity: 1,
  434. customFields: {
  435. lineImageId: 'T_1',
  436. },
  437. });
  438. orderResultGuard.assertSuccess(adjustOrderLine);
  439. expect(adjustOrderLine.lines[2].customFields.lineImage).toEqual({ id: 'T_1' });
  440. orderWithCustomFieldsGuard.assertSuccess(activeOrder);
  441. await shopClient.query(removeItemFromOrderDocument, {
  442. orderLineId: activeOrder.lines[2].id,
  443. });
  444. const { removeOrderLine } = await shopClient.query(removeItemFromOrderDocument, {
  445. orderLineId: activeOrder.lines[1].id,
  446. });
  447. orderResultGuard.assertSuccess(removeOrderLine);
  448. expect(removeOrderLine.lines.length).toBe(1);
  449. });
  450. it('addItemToOrder with list relation customField', async () => {
  451. const { addItemToOrder } = await shopClient.query(addItemToOrderWithCustomFieldsDocument, {
  452. productVariantId: 'T_3',
  453. quantity: 1,
  454. customFields: {
  455. lineImagesIds: ['T_1', 'T_2'],
  456. },
  457. });
  458. orderResultGuard.assertSuccess(addItemToOrder);
  459. expect(addItemToOrder.lines.length).toBe(2);
  460. expect(addItemToOrder.lines[1].quantity).toBe(1);
  461. const { activeOrder } = await shopClient.query(getOrderWithOrderLineCustomFieldsDocument);
  462. orderWithCustomFieldsGuard.assertSuccess(activeOrder);
  463. expect(activeOrder.lines[1].customFields.lineImages.length).toBe(2);
  464. expect(activeOrder.lines[1].customFields.lineImages).toContainEqual({ id: 'T_1' });
  465. expect(activeOrder.lines[1].customFields.lineImages).toContainEqual({ id: 'T_2' });
  466. });
  467. it('addItemToOrder with equal list relation customField adds to quantity', async () => {
  468. const { addItemToOrder } = await shopClient.query(addItemToOrderWithCustomFieldsDocument, {
  469. productVariantId: 'T_3',
  470. quantity: 1,
  471. customFields: {
  472. lineImagesIds: ['T_1', 'T_2'],
  473. },
  474. });
  475. orderResultGuard.assertSuccess(addItemToOrder);
  476. expect(addItemToOrder.lines.length).toBe(2);
  477. expect(addItemToOrder.lines[1].quantity).toBe(2);
  478. const { activeOrder } = await shopClient.query(getOrderWithOrderLineCustomFieldsDocument);
  479. orderWithCustomFieldsGuard.assertSuccess(activeOrder);
  480. expect(activeOrder.lines[1].customFields.lineImages.length).toBe(2);
  481. expect(activeOrder.lines[1].customFields.lineImages).toContainEqual({ id: 'T_1' });
  482. expect(activeOrder.lines[1].customFields.lineImages).toContainEqual({ id: 'T_2' });
  483. });
  484. it('addItemToOrder with different list relation customField adds new line', async () => {
  485. const { addItemToOrder } = await shopClient.query(addItemToOrderWithCustomFieldsDocument, {
  486. productVariantId: 'T_3',
  487. quantity: 1,
  488. customFields: {
  489. lineImagesIds: ['T_1'],
  490. },
  491. });
  492. orderResultGuard.assertSuccess(addItemToOrder);
  493. expect(addItemToOrder.lines.length).toBe(3);
  494. expect(addItemToOrder.lines[2].quantity).toBe(1);
  495. const { activeOrder } = await shopClient.query(getOrderWithOrderLineCustomFieldsDocument);
  496. orderWithCustomFieldsGuard.assertSuccess(activeOrder);
  497. expect(activeOrder.lines[2].customFields.lineImages).toEqual([{ id: 'T_1' }]);
  498. await shopClient.query(removeItemFromOrderDocument, {
  499. orderLineId: activeOrder.lines[2].id,
  500. });
  501. const { removeOrderLine } = await shopClient.query(removeItemFromOrderDocument, {
  502. orderLineId: activeOrder.lines[1].id,
  503. });
  504. orderResultGuard.assertSuccess(removeOrderLine);
  505. expect(removeOrderLine.lines.length).toBe(1);
  506. });
  507. });
  508. it('addItemToOrder errors when going beyond orderItemsLimit', async () => {
  509. const { addItemToOrder } = await shopClient.query(addItemToOrderDocument, {
  510. productVariantId: 'T_1',
  511. quantity: 200,
  512. });
  513. orderResultGuard.assertErrorResult(addItemToOrder);
  514. expect(addItemToOrder.message).toBe(
  515. 'Cannot add items. An order may consist of a maximum of 199 items',
  516. );
  517. expect(addItemToOrder.errorCode).toBe(ErrorCode.ORDER_LIMIT_ERROR);
  518. });
  519. it('adjustOrderLine adjusts the quantity', async () => {
  520. const { adjustOrderLine } = await shopClient.query(adjustItemQuantityDocument, {
  521. orderLineId: firstOrderLineId,
  522. quantity: 50,
  523. });
  524. orderResultGuard.assertSuccess(adjustOrderLine);
  525. expect(adjustOrderLine.lines.length).toBe(1);
  526. expect(adjustOrderLine.lines[0].quantity).toBe(50);
  527. });
  528. it('adjustOrderLine with quantity 0 removes the line', async () => {
  529. const { addItemToOrder } = await shopClient.query(addItemToOrderDocument, {
  530. productVariantId: 'T_3',
  531. quantity: 3,
  532. });
  533. orderResultGuard.assertSuccess(addItemToOrder);
  534. expect(addItemToOrder.lines.length).toBe(2);
  535. expect(addItemToOrder.lines.map(i => i.productVariant.id)).toEqual(['T_1', 'T_3']);
  536. const { adjustOrderLine } = await shopClient.query(adjustItemQuantityDocument, {
  537. orderLineId: addItemToOrder?.lines[1].id,
  538. quantity: 0,
  539. });
  540. orderResultGuard.assertSuccess(adjustOrderLine);
  541. expect(adjustOrderLine.lines.length).toBe(1);
  542. expect(adjustOrderLine.lines.map(i => i.productVariant.id)).toEqual(['T_1']);
  543. });
  544. it('adjustOrderLine with quantity > stockOnHand only allows user to have stock on hand', async () => {
  545. const { addItemToOrder } = await shopClient.query(addItemToOrderDocument, {
  546. productVariantId: 'T_3',
  547. quantity: 111,
  548. });
  549. orderResultGuard.assertErrorResult(addItemToOrder);
  550. // Insufficient stock error should return because there are only 100 available
  551. expect(addItemToOrder.errorCode).toBe('INSUFFICIENT_STOCK_ERROR');
  552. // But it should still add the item to the order
  553. if ('order' in addItemToOrder) {
  554. expect(addItemToOrder.order.lines[1].quantity).toBe(100);
  555. } else {
  556. fail('Expected InsufficientStockError to have order property');
  557. }
  558. const { adjustOrderLine } = await shopClient.query(adjustItemQuantityDocument, {
  559. orderLineId: 'order' in addItemToOrder ? addItemToOrder.order.lines[1].id : '',
  560. quantity: 101,
  561. });
  562. testOrderGuard.assertErrorResult(adjustOrderLine);
  563. expect(adjustOrderLine.errorCode).toBe('INSUFFICIENT_STOCK_ERROR');
  564. expect(adjustOrderLine.message).toBe(
  565. 'Only 100 items were added to the order due to insufficient stock',
  566. );
  567. const order = await shopClient.query(getActiveOrderDocument);
  568. expect(order.activeOrder?.lines[1].quantity).toBe(100);
  569. // clean up
  570. const { adjustOrderLine: adjustLine2 } = await shopClient.query(adjustItemQuantityDocument, {
  571. orderLineId: 'order' in addItemToOrder ? addItemToOrder.order.lines[1].id : '',
  572. quantity: 0,
  573. });
  574. orderResultGuard.assertSuccess(adjustLine2);
  575. expect(adjustLine2.lines.length).toBe(1);
  576. expect(adjustLine2.lines.map(i => i.productVariant.id)).toEqual(['T_1']);
  577. });
  578. // https://github.com/vendure-ecommerce/vendure/issues/2702
  579. it('stockOnHand check works with multiple order lines with different custom fields', async () => {
  580. const variantId = 'T_27';
  581. const { updateProductVariants } = await adminClient.query(updateProductVariantsDocument, {
  582. input: [
  583. {
  584. id: variantId,
  585. stockOnHand: 10,
  586. outOfStockThreshold: 0,
  587. useGlobalOutOfStockThreshold: false,
  588. trackInventory: GlobalFlag.TRUE,
  589. },
  590. ],
  591. });
  592. expect(updateProductVariants[0]?.stockOnHand).toBe(10);
  593. expect(updateProductVariants[0]?.id).toBe('T_27');
  594. expect(updateProductVariants[0]?.trackInventory).toBe(GlobalFlag.TRUE);
  595. const { addItemToOrder: add1 } = await shopClient.query(addItemToOrderWithCustomFieldsDocument, {
  596. productVariantId: variantId,
  597. quantity: 9,
  598. customFields: {
  599. notes: 'abc',
  600. },
  601. });
  602. orderResultGuard.assertSuccess(add1);
  603. expect(add1.lines.length).toBe(2);
  604. expect(add1.lines[1].quantity).toBe(9);
  605. expect(add1.lines[1].productVariant.id).toBe(variantId);
  606. const { addItemToOrder: add2 } = await shopClient.query(addItemToOrderWithCustomFieldsDocument, {
  607. productVariantId: variantId,
  608. quantity: 2,
  609. customFields: {
  610. notes: 'def',
  611. },
  612. });
  613. orderResultGuard.assertErrorResult(add2);
  614. expect(add2.errorCode).toBe('INSUFFICIENT_STOCK_ERROR');
  615. expect(add2.message).toBe('Only 1 item was added to the order due to insufficient stock');
  616. const { activeOrder } = await shopClient.query(getActiveOrderDocument);
  617. expect(activeOrder?.lines.length).toBe(3);
  618. expect(activeOrder?.lines[1].quantity).toBe(9);
  619. expect(activeOrder?.lines[2].quantity).toBe(1);
  620. // clean up
  621. await shopClient.query(removeItemFromOrderDocument, {
  622. orderLineId: activeOrder!.lines[1].id,
  623. });
  624. await shopClient.query(removeItemFromOrderDocument, {
  625. orderLineId: activeOrder!.lines[2].id,
  626. });
  627. });
  628. it('adjustOrderLine handles stockOnHand correctly with multiple order lines with different custom fields when out of stock', async () => {
  629. const variantId = 'T_27';
  630. const { updateProductVariants } = await adminClient.query(updateProductVariantsDocument, {
  631. input: [
  632. {
  633. id: variantId,
  634. stockOnHand: 10,
  635. outOfStockThreshold: 0,
  636. useGlobalOutOfStockThreshold: false,
  637. trackInventory: GlobalFlag.TRUE,
  638. },
  639. ],
  640. });
  641. expect(updateProductVariants[0]?.stockOnHand).toBe(10);
  642. expect(updateProductVariants[0]?.id).toBe('T_27');
  643. expect(updateProductVariants[0]?.trackInventory).toBe(GlobalFlag.TRUE);
  644. const { addItemToOrder: add1 } = await shopClient.query(addItemToOrderWithCustomFieldsDocument, {
  645. productVariantId: variantId,
  646. quantity: 5,
  647. customFields: {
  648. notes: 'abc',
  649. },
  650. });
  651. orderResultGuard.assertSuccess(add1);
  652. expect(add1.lines.length).toBe(2);
  653. expect(add1.lines[1].quantity).toBe(5);
  654. expect(add1.lines[1].productVariant.id).toBe(variantId);
  655. const { addItemToOrder: add2 } = await shopClient.query(addItemToOrderWithCustomFieldsDocument, {
  656. productVariantId: variantId,
  657. quantity: 5,
  658. customFields: {
  659. notes: 'def',
  660. },
  661. });
  662. orderResultGuard.assertSuccess(add2);
  663. expect(add2.lines.length).toBe(3);
  664. expect(add2.lines[2].quantity).toBe(5);
  665. expect(add2.lines[2].productVariant.id).toBe(variantId);
  666. const { adjustOrderLine } = await shopClient.query(adjustItemQuantityDocument, {
  667. orderLineId: add2.lines[1].id,
  668. quantity: 10,
  669. });
  670. testOrderGuard.assertErrorResult(adjustOrderLine);
  671. expect(adjustOrderLine.message).toBe(
  672. 'Only 5 items were added to the order due to insufficient stock',
  673. );
  674. expect(adjustOrderLine.errorCode).toBe(ErrorCode.INSUFFICIENT_STOCK_ERROR);
  675. const { activeOrder } = await shopClient.query(getActiveOrderDocument);
  676. expect(activeOrder?.lines.length).toBe(3);
  677. expect(activeOrder?.lines[1].quantity).toBe(5);
  678. expect(activeOrder?.lines[2].quantity).toBe(5);
  679. // clean up
  680. await shopClient.query(removeItemFromOrderDocument, {
  681. orderLineId: activeOrder!.lines[1].id,
  682. });
  683. await shopClient.query(removeItemFromOrderDocument, {
  684. orderLineId: activeOrder!.lines[2].id,
  685. });
  686. });
  687. it('adjustOrderLine handles stockOnHand correctly with multiple order lines with different custom fields', async () => {
  688. const variantId = 'T_27';
  689. const { updateProductVariants } = await adminClient.query(updateProductVariantsDocument, {
  690. input: [
  691. {
  692. id: variantId,
  693. stockOnHand: 10,
  694. outOfStockThreshold: 0,
  695. useGlobalOutOfStockThreshold: false,
  696. trackInventory: GlobalFlag.TRUE,
  697. },
  698. ],
  699. });
  700. expect(updateProductVariants[0]?.stockOnHand).toBe(10);
  701. expect(updateProductVariants[0]?.id).toBe('T_27');
  702. expect(updateProductVariants[0]?.trackInventory).toBe(GlobalFlag.TRUE);
  703. const { addItemToOrder: add1 } = await shopClient.query(addItemToOrderWithCustomFieldsDocument, {
  704. productVariantId: variantId,
  705. quantity: 5,
  706. customFields: {
  707. notes: 'abc',
  708. },
  709. });
  710. orderResultGuard.assertSuccess(add1);
  711. expect(add1.lines.length).toBe(2);
  712. expect(add1.lines[1].quantity).toBe(5);
  713. expect(add1.lines[1].productVariant.id).toBe(variantId);
  714. const { addItemToOrder: add2 } = await shopClient.query(addItemToOrderWithCustomFieldsDocument, {
  715. productVariantId: variantId,
  716. quantity: 5,
  717. customFields: {
  718. notes: 'def',
  719. },
  720. });
  721. orderResultGuard.assertSuccess(add2);
  722. expect(add2.lines.length).toBe(3);
  723. expect(add2.lines[2].quantity).toBe(5);
  724. expect(add2.lines[2].productVariant.id).toBe(variantId);
  725. const { adjustOrderLine } = await shopClient.query(adjustItemQuantityDocument, {
  726. orderLineId: add2.lines[1].id,
  727. quantity: 3,
  728. });
  729. orderResultGuard.assertSuccess(adjustOrderLine);
  730. expect(adjustOrderLine?.lines.length).toBe(3);
  731. expect(adjustOrderLine?.lines[1].quantity).toBe(3);
  732. expect(adjustOrderLine?.lines[2].quantity).toBe(5);
  733. // clean up
  734. await shopClient.query(removeItemFromOrderDocument, {
  735. orderLineId: adjustOrderLine.lines[1].id,
  736. });
  737. await shopClient.query(removeItemFromOrderDocument, {
  738. orderLineId: adjustOrderLine.lines[2].id,
  739. });
  740. });
  741. it('adjustOrderLine errors when going beyond orderItemsLimit', async () => {
  742. const { adjustOrderLine } = await shopClient.query(adjustItemQuantityDocument, {
  743. orderLineId: firstOrderLineId,
  744. quantity: 200,
  745. });
  746. testOrderGuard.assertErrorResult(adjustOrderLine);
  747. expect(adjustOrderLine.message).toBe(
  748. 'Cannot add items. An order may consist of a maximum of 199 items',
  749. );
  750. expect(adjustOrderLine.errorCode).toBe(ErrorCode.ORDER_LIMIT_ERROR);
  751. });
  752. it('adjustOrderLine errors with a negative quantity', async () => {
  753. const { adjustOrderLine } = await shopClient.query(adjustItemQuantityDocument, {
  754. orderLineId: firstOrderLineId,
  755. quantity: -3,
  756. });
  757. testOrderGuard.assertErrorResult(adjustOrderLine);
  758. expect(adjustOrderLine.message).toBe('The quantity for an OrderItem cannot be negative');
  759. expect(adjustOrderLine.errorCode).toBe(ErrorCode.NEGATIVE_QUANTITY_ERROR);
  760. });
  761. it(
  762. 'adjustOrderLine errors with an invalid orderLineId',
  763. assertThrowsWithMessage(
  764. () =>
  765. shopClient.query(adjustItemQuantityDocument, {
  766. orderLineId: 'T_999',
  767. quantity: 5,
  768. }),
  769. 'This order does not contain an OrderLine with the id 999',
  770. ),
  771. );
  772. it('removeItemFromOrder removes the correct item', async () => {
  773. const { addItemToOrder } = await shopClient.query(addItemToOrderDocument, {
  774. productVariantId: 'T_3',
  775. quantity: 3,
  776. });
  777. orderResultGuard.assertSuccess(addItemToOrder);
  778. expect(addItemToOrder.lines.length).toBe(2);
  779. expect(addItemToOrder.lines.map(i => i.productVariant.id)).toEqual(['T_1', 'T_3']);
  780. const { removeOrderLine } = await shopClient.query(removeItemFromOrderDocument, {
  781. orderLineId: firstOrderLineId,
  782. });
  783. orderResultGuard.assertSuccess(removeOrderLine);
  784. expect(removeOrderLine.lines.length).toBe(1);
  785. expect(removeOrderLine.lines.map(i => i.productVariant.id)).toEqual(['T_3']);
  786. });
  787. it(
  788. 'removeItemFromOrder errors with an invalid orderItemId',
  789. assertThrowsWithMessage(
  790. () =>
  791. shopClient.query(removeItemFromOrderDocument, {
  792. orderLineId: 'T_999',
  793. }),
  794. 'This order does not contain an OrderLine with the id 999',
  795. ),
  796. );
  797. it('nextOrderStates returns next valid states', async () => {
  798. const result = await shopClient.query(getNextStatesDocument);
  799. expect(result.nextOrderStates).toEqual(['ArrangingPayment', 'Cancelled']);
  800. });
  801. it('transitionOrderToState returns error result for invalid state', async () => {
  802. const { transitionOrderToState } = await shopClient.query(transitionToStateDocument, {
  803. state: 'Completed',
  804. });
  805. if (transitionOrderToState) {
  806. testOrderGuard.assertErrorResult(transitionOrderToState);
  807. } else {
  808. fail('Expected transitionOrderToState to exist');
  809. }
  810. expect(transitionOrderToState.message).toBe(
  811. 'Cannot transition Order from "AddingItems" to "Completed"',
  812. );
  813. expect(transitionOrderToState.errorCode).toBe(ErrorCode.ORDER_STATE_TRANSITION_ERROR);
  814. });
  815. it('attempting to transition to ArrangingPayment returns error result when Order has no Customer', async () => {
  816. const { transitionOrderToState } = await shopClient.query(transitionToStateDocument, {
  817. state: 'ArrangingPayment',
  818. });
  819. if (transitionOrderToState) {
  820. testOrderGuard.assertErrorResult(transitionOrderToState);
  821. } else {
  822. fail('Expected transitionOrderToState to exist');
  823. }
  824. expect(transitionOrderToState.transitionError).toBe(
  825. 'Cannot transition Order to the "ArrangingPayment" state without Customer details',
  826. );
  827. expect(transitionOrderToState.errorCode).toBe(ErrorCode.ORDER_STATE_TRANSITION_ERROR);
  828. });
  829. it('setCustomerForOrder returns error result on email address conflict', async () => {
  830. const { customers } = await adminClient.query(getCustomerListDocument);
  831. const { setCustomerForOrder } = await shopClient.query(setCustomerDocument, {
  832. input: {
  833. emailAddress: customers.items[0].emailAddress,
  834. firstName: 'Test',
  835. lastName: 'Person',
  836. },
  837. });
  838. setCustomerForOrderGuard.assertErrorResult(setCustomerForOrder);
  839. expect(setCustomerForOrder.message).toBe('The email address is not available.');
  840. expect(setCustomerForOrder.errorCode).toBe(ErrorCode.EMAIL_ADDRESS_CONFLICT_ERROR);
  841. });
  842. it('setCustomerForOrder creates a new Customer and associates it with the Order', async () => {
  843. const { setCustomerForOrder } = await shopClient.query(setCustomerDocument, {
  844. input: {
  845. emailAddress: 'test@test.com',
  846. firstName: 'Test',
  847. lastName: 'Person',
  848. },
  849. });
  850. orderResultGuard.assertSuccess(setCustomerForOrder);
  851. const customer = setCustomerForOrder.customer;
  852. if (!customer || !('firstName' in customer)) {
  853. fail('Expected customer with firstName to exist');
  854. }
  855. expect(customer.firstName).toBe('Test');
  856. expect(customer.lastName).toBe('Person');
  857. expect(customer.emailAddress).toBe('test@test.com');
  858. createdCustomerId = customer.id;
  859. });
  860. it('setCustomerForOrder updates the existing customer if Customer already set', async () => {
  861. const { setCustomerForOrder } = await shopClient.query(setCustomerDocument, {
  862. input: {
  863. emailAddress: 'test@test.com',
  864. firstName: 'Changed',
  865. lastName: 'Person',
  866. },
  867. });
  868. orderResultGuard.assertSuccess(setCustomerForOrder);
  869. const customer = setCustomerForOrder.customer;
  870. if (!customer || !('firstName' in customer)) {
  871. fail('Expected customer with firstName to exist');
  872. }
  873. expect(customer.firstName).toBe('Changed');
  874. expect(customer.lastName).toBe('Person');
  875. expect(customer.emailAddress).toBe('test@test.com');
  876. expect(customer.id).toBe(createdCustomerId);
  877. });
  878. describe('address handling', () => {
  879. const shippingAddress: CreateAddressInput = {
  880. fullName: 'name',
  881. company: 'company',
  882. streetLine1: '12 Shipping Street',
  883. streetLine2: null,
  884. city: 'foo',
  885. province: 'bar',
  886. postalCode: '123456',
  887. countryCode: 'US',
  888. phoneNumber: '4444444',
  889. };
  890. const billingAddress: CreateAddressInput = {
  891. fullName: 'name',
  892. company: 'company',
  893. streetLine1: '22 Billing Avenue',
  894. streetLine2: null,
  895. city: 'foo',
  896. province: 'bar',
  897. postalCode: '123456',
  898. countryCode: 'US',
  899. phoneNumber: '4444444',
  900. };
  901. it('setOrderShippingAddress sets shipping address', async () => {
  902. const { setOrderShippingAddress } = await shopClient.query(setShippingAddressDocument, {
  903. input: shippingAddress,
  904. });
  905. orderResultGuard.assertSuccess(setOrderShippingAddress);
  906. expect(setOrderShippingAddress.shippingAddress).toEqual({
  907. fullName: 'name',
  908. company: 'company',
  909. streetLine1: '12 Shipping Street',
  910. streetLine2: null,
  911. city: 'foo',
  912. province: 'bar',
  913. postalCode: '123456',
  914. country: 'United States of America',
  915. phoneNumber: '4444444',
  916. });
  917. });
  918. it('setOrderBillingAddress sets billing address', async () => {
  919. const { setOrderBillingAddress } = await shopClient.query(setBillingAddressDocument, {
  920. input: billingAddress,
  921. });
  922. orderResultGuard.assertSuccess(setOrderBillingAddress);
  923. expect(setOrderBillingAddress.billingAddress).toEqual({
  924. fullName: 'name',
  925. company: 'company',
  926. streetLine1: '22 Billing Avenue',
  927. streetLine2: null,
  928. city: 'foo',
  929. province: 'bar',
  930. postalCode: '123456',
  931. country: 'United States of America',
  932. phoneNumber: '4444444',
  933. });
  934. });
  935. it('unsetOrderShippingAddress unsets shipping address', async () => {
  936. const { unsetOrderShippingAddress } = await shopClient.query(unsetShippingAddressDocument);
  937. orderResultGuard.assertSuccess(unsetOrderShippingAddress);
  938. expect(unsetOrderShippingAddress.shippingAddress).toEqual({
  939. fullName: null,
  940. company: null,
  941. streetLine1: null,
  942. streetLine2: null,
  943. city: null,
  944. province: null,
  945. postalCode: null,
  946. country: null,
  947. phoneNumber: null,
  948. });
  949. // Reset the shipping address for subsequent tests
  950. await shopClient.query(setShippingAddressDocument, {
  951. input: shippingAddress,
  952. });
  953. });
  954. it('unsetOrderBillingAddress unsets billing address', async () => {
  955. const { unsetOrderBillingAddress } = await shopClient.query(unsetBillingAddressDocument);
  956. orderResultGuard.assertSuccess(unsetOrderBillingAddress);
  957. expect(unsetOrderBillingAddress.billingAddress).toEqual({
  958. fullName: null,
  959. company: null,
  960. streetLine1: null,
  961. streetLine2: null,
  962. city: null,
  963. province: null,
  964. postalCode: null,
  965. country: null,
  966. phoneNumber: null,
  967. });
  968. // Reset the billing address for subsequent tests
  969. await shopClient.query(setBillingAddressDocument, {
  970. input: billingAddress,
  971. });
  972. });
  973. });
  974. it('customer default Addresses are not updated before payment', async () => {
  975. const { activeOrder } = await shopClient.query(getActiveOrderDocument);
  976. const { customer } = await adminClient.query(getCustomerDocument, {
  977. id: activeOrder!.customer!.id,
  978. });
  979. expect(customer!.addresses).toEqual([]);
  980. });
  981. it('attempting to transition to ArrangingPayment returns error result when Order has no ShippingMethod', async () => {
  982. const { transitionOrderToState } = await shopClient.query(transitionToStateDocument, {
  983. state: 'ArrangingPayment',
  984. });
  985. if (transitionOrderToState) {
  986. testOrderGuard.assertErrorResult(transitionOrderToState);
  987. } else {
  988. fail('Expected transitionOrderToState to exist');
  989. }
  990. expect(transitionOrderToState.transitionError).toBe(
  991. 'Cannot transition Order to the "ArrangingPayment" state without a ShippingMethod',
  992. );
  993. expect(transitionOrderToState.errorCode).toBe(ErrorCode.ORDER_STATE_TRANSITION_ERROR);
  994. });
  995. it('can transition to ArrangingPayment once Customer and ShippingMethod has been set', async () => {
  996. const { eligibleShippingMethods } = await shopClient.query(getEligibleShippingMethodsDocument);
  997. const { setOrderShippingMethod } = await shopClient.query(setShippingMethodDocument, {
  998. id: [eligibleShippingMethods[0].id],
  999. });
  1000. testOrderGuard.assertSuccess(setOrderShippingMethod);
  1001. const { transitionOrderToState } = await shopClient.query(transitionToStateDocument, {
  1002. state: 'ArrangingPayment',
  1003. });
  1004. if (transitionOrderToState) {
  1005. testOrderGuard.assertSuccess(transitionOrderToState);
  1006. expect(pick(transitionOrderToState, ['id', 'state'])).toEqual({
  1007. id: 'T_1',
  1008. state: 'ArrangingPayment',
  1009. });
  1010. } else {
  1011. fail('Expected transitionOrderToState to exist');
  1012. }
  1013. });
  1014. it('adds a successful payment and transitions Order state', async () => {
  1015. const { addPaymentToOrder } = await shopClient.query(addPaymentDocument, {
  1016. input: {
  1017. method: testSuccessfulPaymentMethod.code,
  1018. metadata: {},
  1019. },
  1020. });
  1021. orderResultGuard.assertSuccess(addPaymentToOrder);
  1022. const payment = addPaymentToOrder.payments![0];
  1023. expect(addPaymentToOrder.state).toBe('PaymentSettled');
  1024. expect(addPaymentToOrder.active).toBe(false);
  1025. expect(addPaymentToOrder.payments!.length).toBe(1);
  1026. expect(payment.method).toBe(testSuccessfulPaymentMethod.code);
  1027. expect(payment.state).toBe('Settled');
  1028. });
  1029. it('activeOrder is null after payment', async () => {
  1030. const result = await shopClient.query(getActiveOrderDocument);
  1031. expect(result.activeOrder).toBeNull();
  1032. });
  1033. it('customer default Addresses are updated after payment', async () => {
  1034. const result = await adminClient.query(getCustomerDocument, {
  1035. id: createdCustomerId,
  1036. });
  1037. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
  1038. const shippingAddress = result.customer!.addresses!.find(a => a.defaultShippingAddress)!;
  1039. expect(shippingAddress.streetLine1).toBe('12 Shipping Street');
  1040. expect(shippingAddress.postalCode).toBe('123456');
  1041. expect(shippingAddress.defaultBillingAddress).toBe(false);
  1042. expect(shippingAddress.defaultShippingAddress).toBe(true);
  1043. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
  1044. const billingAddress = result.customer!.addresses!.find(a => a.defaultBillingAddress)!;
  1045. expect(billingAddress.streetLine1).toBe('22 Billing Avenue');
  1046. expect(billingAddress.postalCode).toBe('123456');
  1047. expect(billingAddress.defaultBillingAddress).toBe(true);
  1048. expect(billingAddress.defaultShippingAddress).toBe(false);
  1049. });
  1050. it('sets OrderLine.featuredAsset to that of ProductVariant if defined', async () => {
  1051. const { product } = await adminClient.query(getProductWithVariantsDocument, {
  1052. id: 'T_4',
  1053. });
  1054. const variantWithFeaturedAsset = product?.variants.find(v => !!v.featuredAsset);
  1055. if (!variantWithFeaturedAsset) {
  1056. fail(`Could not find expected variant with a featuredAsset`);
  1057. }
  1058. const { addItemToOrder } = await shopClient.query(addItemToOrderDocument, {
  1059. productVariantId: variantWithFeaturedAsset.id,
  1060. quantity: 1,
  1061. });
  1062. orderResultGuard.assertSuccess(addItemToOrder);
  1063. expect(addItemToOrder.lines.length).toBe(1);
  1064. expect(addItemToOrder.lines[0].productVariant.id).toBe(variantWithFeaturedAsset?.id);
  1065. expect(addItemToOrder.lines[0].featuredAsset?.id).toBe(
  1066. variantWithFeaturedAsset.featuredAsset?.id,
  1067. );
  1068. });
  1069. it('sets OrderLine.featuredAsset to that of Product if ProductVariant has no featuredAsset', async () => {
  1070. const { product } = await adminClient.query(getProductWithVariantsDocument, {
  1071. id: 'T_4',
  1072. });
  1073. const variantWithoutFeaturedAsset = product?.variants.find(v => !v.featuredAsset);
  1074. if (!variantWithoutFeaturedAsset) {
  1075. fail(`Could not find expected variant without a featuredAsset`);
  1076. }
  1077. const { addItemToOrder } = await shopClient.query(addItemToOrderDocument, {
  1078. productVariantId: variantWithoutFeaturedAsset.id,
  1079. quantity: 1,
  1080. });
  1081. orderResultGuard.assertSuccess(addItemToOrder);
  1082. expect(addItemToOrder.lines.length).toBe(2);
  1083. expect(addItemToOrder.lines[1].productVariant.id).toBe(variantWithoutFeaturedAsset?.id);
  1084. expect(addItemToOrder.lines[1].featuredAsset?.id).toBe(product?.featuredAsset?.id);
  1085. });
  1086. it('adds multiple items to order with different custom fields', async () => {
  1087. await shopClient.asAnonymousUser(); // New order
  1088. const { addItemsToOrder } = await shopClient.query(
  1089. addMultipleItemsToOrderWithCustomFieldsDocument,
  1090. {
  1091. inputs: [
  1092. {
  1093. productVariantId: 'T_1',
  1094. quantity: 1,
  1095. customFields: {
  1096. notes: 'Variant 1 note',
  1097. },
  1098. },
  1099. {
  1100. productVariantId: 'T_2',
  1101. quantity: 2,
  1102. customFields: {
  1103. notes: 'Variant 2 note',
  1104. },
  1105. },
  1106. {
  1107. productVariantId: 'T_3',
  1108. quantity: 3,
  1109. // no custom field
  1110. },
  1111. ],
  1112. },
  1113. );
  1114. const order = addItemsToOrder.order;
  1115. expect(order.lines.length).toBe(3);
  1116. expect(order.lines[0].customFields.notes).toBe('Variant 1 note');
  1117. expect(order.lines[1].quantity).toBe(2);
  1118. expect(order.lines[1].customFields.notes).toBe('Variant 2 note');
  1119. expect(order.lines[2].quantity).toBe(3);
  1120. expect(order.lines[2].customFields.notes).toBeNull();
  1121. });
  1122. });
  1123. describe('ordering as authenticated user', () => {
  1124. let firstOrderLineId: string;
  1125. let activeOrder: FragmentOf<typeof testOrderFragment>;
  1126. let authenticatedUserEmailAddress: string;
  1127. let customers: ResultOf<typeof getCustomerListDocument>['customers']['items'];
  1128. const password = 'test';
  1129. beforeAll(async () => {
  1130. await adminClient.asSuperAdmin();
  1131. const result = await adminClient.query(getCustomerListDocument, {
  1132. options: {
  1133. take: 2,
  1134. },
  1135. });
  1136. customers = result.customers.items;
  1137. authenticatedUserEmailAddress = customers[0].emailAddress;
  1138. await shopClient.asUserWithCredentials(authenticatedUserEmailAddress, password);
  1139. });
  1140. it('activeOrder returns null before any items have been added', async () => {
  1141. const result = await shopClient.query(getActiveOrderDocument);
  1142. expect(result.activeOrder).toBeNull();
  1143. });
  1144. it('addItemToOrder creates a new Order with an item', async () => {
  1145. const { addItemToOrder } = await shopClient.query(addItemToOrderDocument, {
  1146. productVariantId: 'T_1',
  1147. quantity: 1,
  1148. });
  1149. orderResultGuard.assertSuccess(addItemToOrder);
  1150. expect(addItemToOrder.lines.length).toBe(1);
  1151. expect(addItemToOrder.lines[0].quantity).toBe(1);
  1152. expect(addItemToOrder.lines[0].productVariant.id).toBe('T_1');
  1153. activeOrder = addItemToOrder as unknown as FragmentOf<typeof testOrderFragment>;
  1154. firstOrderLineId = addItemToOrder.lines[0].id;
  1155. });
  1156. it('activeOrder returns order after item has been added', async () => {
  1157. const result = await shopClient.query(getActiveOrderDocument);
  1158. expect(result.activeOrder!.id).toBe(activeOrder.id);
  1159. expect(result.activeOrder!.state).toBe('AddingItems');
  1160. });
  1161. it('activeOrder resolves customer user', async () => {
  1162. const result = await shopClient.query(getActiveOrderDocument);
  1163. expect(result.activeOrder!.customer!.user).toEqual({
  1164. id: 'T_2',
  1165. identifier: 'hayden.zieme12@hotmail.com',
  1166. });
  1167. });
  1168. it('addItemToOrder with an existing productVariantId adds quantity to the existing OrderLine', async () => {
  1169. const { addItemToOrder } = await shopClient.query(addItemToOrderDocument, {
  1170. productVariantId: 'T_1',
  1171. quantity: 2,
  1172. });
  1173. orderResultGuard.assertSuccess(addItemToOrder);
  1174. expect(addItemToOrder.lines.length).toBe(1);
  1175. expect(addItemToOrder.lines[0].quantity).toBe(3);
  1176. });
  1177. it('adjustOrderLine adjusts the quantity', async () => {
  1178. const { adjustOrderLine } = await shopClient.query(adjustItemQuantityDocument, {
  1179. orderLineId: firstOrderLineId,
  1180. quantity: 50,
  1181. });
  1182. orderResultGuard.assertSuccess(adjustOrderLine);
  1183. expect(adjustOrderLine.lines.length).toBe(1);
  1184. expect(adjustOrderLine.lines[0].quantity).toBe(50);
  1185. });
  1186. it('removeItemFromOrder removes the correct item', async () => {
  1187. const { addItemToOrder } = await shopClient.query(addItemToOrderDocument, {
  1188. productVariantId: 'T_3',
  1189. quantity: 3,
  1190. });
  1191. orderResultGuard.assertSuccess(addItemToOrder);
  1192. expect(addItemToOrder.lines.length).toBe(2);
  1193. expect(addItemToOrder.lines.map(i => i.productVariant.id)).toEqual(['T_1', 'T_3']);
  1194. const { removeOrderLine } = await shopClient.query(removeItemFromOrderDocument, {
  1195. orderLineId: firstOrderLineId,
  1196. });
  1197. orderResultGuard.assertSuccess(removeOrderLine);
  1198. expect(removeOrderLine.lines.length).toBe(1);
  1199. expect(removeOrderLine.lines.map(i => i.productVariant.id)).toEqual(['T_3']);
  1200. });
  1201. it('nextOrderStates returns next valid states', async () => {
  1202. const result = await shopClient.query(getNextStatesDocument);
  1203. expect(result.nextOrderStates).toEqual(['ArrangingPayment', 'Cancelled']);
  1204. });
  1205. it('logging out and back in again resumes the last active order', async () => {
  1206. await shopClient.asAnonymousUser();
  1207. const result1 = await shopClient.query(getActiveOrderDocument);
  1208. expect(result1.activeOrder).toBeNull();
  1209. await shopClient.asUserWithCredentials(authenticatedUserEmailAddress, password);
  1210. const result2 = await shopClient.query(getActiveOrderDocument);
  1211. expect(result2.activeOrder!.id).toBe(activeOrder.id);
  1212. });
  1213. it('cannot setCustomerForOrder when already logged in', async () => {
  1214. const { setCustomerForOrder } = await shopClient.query(setCustomerDocument, {
  1215. input: {
  1216. emailAddress: 'newperson@email.com',
  1217. firstName: 'New',
  1218. lastName: 'Person',
  1219. },
  1220. });
  1221. setCustomerForOrderGuard.assertErrorResult(setCustomerForOrder);
  1222. expect(setCustomerForOrder.message).toBe(
  1223. 'Cannot set a Customer for the Order when already logged in',
  1224. );
  1225. expect(setCustomerForOrder.errorCode).toBe(ErrorCode.ALREADY_LOGGED_IN_ERROR);
  1226. });
  1227. describe('shipping', () => {
  1228. let shippingMethods: ResultOf<
  1229. typeof getEligibleShippingMethodsDocument
  1230. >['eligibleShippingMethods'];
  1231. it(
  1232. 'setOrderShippingAddress throws with invalid countryCode',
  1233. assertThrowsWithMessage(() => {
  1234. const address: CreateAddressInput = {
  1235. streetLine1: '12 the street',
  1236. countryCode: 'INVALID',
  1237. };
  1238. return shopClient.query(setShippingAddressDocument, {
  1239. input: address,
  1240. });
  1241. }, 'The countryCode "INVALID" was not recognized'),
  1242. );
  1243. it('setOrderShippingAddress sets shipping address', async () => {
  1244. const address: CreateAddressInput = {
  1245. fullName: 'name',
  1246. company: 'company',
  1247. streetLine1: '12 the street',
  1248. streetLine2: null,
  1249. city: 'foo',
  1250. province: 'bar',
  1251. postalCode: '123456',
  1252. countryCode: 'US',
  1253. phoneNumber: '4444444',
  1254. };
  1255. const { setOrderShippingAddress } = await shopClient.query(setShippingAddressDocument, {
  1256. input: address,
  1257. });
  1258. orderResultGuard.assertSuccess(setOrderShippingAddress);
  1259. expect(setOrderShippingAddress.shippingAddress).toEqual({
  1260. fullName: 'name',
  1261. company: 'company',
  1262. streetLine1: '12 the street',
  1263. streetLine2: null,
  1264. city: 'foo',
  1265. province: 'bar',
  1266. postalCode: '123456',
  1267. country: 'United States of America',
  1268. phoneNumber: '4444444',
  1269. });
  1270. });
  1271. it('eligibleShippingMethods lists shipping methods', async () => {
  1272. const result = await shopClient.query(getEligibleShippingMethodsDocument);
  1273. shippingMethods = result.eligibleShippingMethods;
  1274. expect(shippingMethods).toEqual([
  1275. {
  1276. id: 'T_1',
  1277. price: 500,
  1278. code: 'standard-shipping',
  1279. name: 'Standard Shipping',
  1280. description: '',
  1281. },
  1282. {
  1283. id: 'T_2',
  1284. price: 1000,
  1285. code: 'express-shipping',
  1286. name: 'Express Shipping',
  1287. description: '',
  1288. },
  1289. {
  1290. id: 'T_3',
  1291. price: 1000,
  1292. code: 'express-shipping-taxed',
  1293. name: 'Express Shipping (Taxed)',
  1294. description: '',
  1295. },
  1296. ]);
  1297. });
  1298. it('shipping is initially unset', async () => {
  1299. const result = await shopClient.query(getActiveOrderDocument);
  1300. expect(result.activeOrder!.shipping).toEqual(0);
  1301. expect(result.activeOrder!.shippingLines).toEqual([]);
  1302. });
  1303. it('setOrderShippingMethod sets the shipping method', async () => {
  1304. await shopClient.query(setShippingMethodDocument, {
  1305. id: [shippingMethods[1].id],
  1306. });
  1307. const activeOrderResult = await shopClient.query(getActiveOrderDocument);
  1308. const order = activeOrderResult.activeOrder!;
  1309. expect(order.shipping).toBe(shippingMethods[1].price);
  1310. expect(order.shippingLines[0].shippingMethod.id).toBe(shippingMethods[1].id);
  1311. expect(order.shippingLines[0].shippingMethod.description).toBe(
  1312. shippingMethods[1].description,
  1313. );
  1314. });
  1315. it('shipping method is preserved after adjustOrderLine', async () => {
  1316. const activeOrderResult = await shopClient.query(getActiveOrderDocument);
  1317. activeOrder = activeOrderResult.activeOrder!;
  1318. const { adjustOrderLine } = await shopClient.query(adjustItemQuantityDocument, {
  1319. orderLineId: activeOrder.lines[0].id,
  1320. quantity: 10,
  1321. });
  1322. testOrderGuard.assertSuccess(adjustOrderLine);
  1323. expect(adjustOrderLine.shipping).toBe(shippingMethods[1].price);
  1324. expect(adjustOrderLine.shippingLines[0].shippingMethod.id).toBe(shippingMethods[1].id);
  1325. expect(adjustOrderLine.shippingLines[0].shippingMethod.description).toBe(
  1326. shippingMethods[1].description,
  1327. );
  1328. });
  1329. });
  1330. describe('payment', () => {
  1331. it('attempting add a Payment returns error result when in AddingItems state', async () => {
  1332. const { addPaymentToOrder } = await shopClient.query(addPaymentDocument, {
  1333. input: {
  1334. method: testSuccessfulPaymentMethod.code,
  1335. metadata: {},
  1336. },
  1337. });
  1338. testOrderGuard.assertErrorResult(addPaymentToOrder);
  1339. expect(addPaymentToOrder.message).toBe(
  1340. 'A Payment may only be added when Order is in "ArrangingPayment" state',
  1341. );
  1342. expect(addPaymentToOrder.errorCode).toBe(ErrorCode.ORDER_PAYMENT_STATE_ERROR);
  1343. });
  1344. it('transitions to the ArrangingPayment state', async () => {
  1345. const { transitionOrderToState } = await shopClient.query(transitionToStateDocument, {
  1346. state: 'ArrangingPayment',
  1347. });
  1348. if (transitionOrderToState) {
  1349. testOrderGuard.assertSuccess(transitionOrderToState);
  1350. expect(pick(transitionOrderToState, ['id', 'state'])).toEqual({
  1351. id: activeOrder.id,
  1352. state: 'ArrangingPayment',
  1353. });
  1354. } else {
  1355. fail('Expected transitionOrderToState to exist');
  1356. }
  1357. });
  1358. it('attempting to add an item returns error result when in ArrangingPayment state', async () => {
  1359. const { addItemToOrder } = await shopClient.query(addItemToOrderDocument, {
  1360. productVariantId: 'T_4',
  1361. quantity: 1,
  1362. });
  1363. orderResultGuard.assertErrorResult(addItemToOrder);
  1364. expect(addItemToOrder.message).toBe(
  1365. 'Order contents may only be modified when in the "AddingItems" state',
  1366. );
  1367. expect(addItemToOrder.errorCode).toBe(ErrorCode.ORDER_MODIFICATION_ERROR);
  1368. });
  1369. it('attempting to modify item quantity returns error result when in ArrangingPayment state', async () => {
  1370. const { adjustOrderLine } = await shopClient.query(adjustItemQuantityDocument, {
  1371. orderLineId: activeOrder.lines[0].id,
  1372. quantity: 12,
  1373. });
  1374. testOrderGuard.assertErrorResult(adjustOrderLine);
  1375. expect(adjustOrderLine.message).toBe(
  1376. 'Order contents may only be modified when in the "AddingItems" state',
  1377. );
  1378. expect(adjustOrderLine.errorCode).toBe(ErrorCode.ORDER_MODIFICATION_ERROR);
  1379. });
  1380. it('attempting to remove an item returns error result when in ArrangingPayment state', async () => {
  1381. const { removeOrderLine } = await shopClient.query(removeItemFromOrderDocument, {
  1382. orderLineId: activeOrder.lines[0].id,
  1383. });
  1384. testOrderGuard.assertErrorResult(removeOrderLine);
  1385. expect(removeOrderLine.message).toBe(
  1386. 'Order contents may only be modified when in the "AddingItems" state',
  1387. );
  1388. expect(removeOrderLine.errorCode).toBe(ErrorCode.ORDER_MODIFICATION_ERROR);
  1389. });
  1390. it('attempting to remove all items returns error result when in ArrangingPayment state', async () => {
  1391. const { removeAllOrderLines } = await shopClient.query(removeAllOrderLinesDocument);
  1392. testOrderGuard.assertErrorResult(removeAllOrderLines);
  1393. expect(removeAllOrderLines.message).toBe(
  1394. 'Order contents may only be modified when in the "AddingItems" state',
  1395. );
  1396. expect(removeAllOrderLines.errorCode).toBe(ErrorCode.ORDER_MODIFICATION_ERROR);
  1397. });
  1398. it('attempting to setOrderShippingMethod returns error result when in ArrangingPayment state', async () => {
  1399. const shippingMethodsResult = await shopClient.query(getEligibleShippingMethodsDocument);
  1400. const shippingMethods = shippingMethodsResult.eligibleShippingMethods;
  1401. const { setOrderShippingMethod } = await shopClient.query(setShippingMethodDocument, {
  1402. id: [shippingMethods[0].id],
  1403. });
  1404. testOrderGuard.assertErrorResult(setOrderShippingMethod);
  1405. expect(setOrderShippingMethod.message).toBe(
  1406. 'Order contents may only be modified when in the "AddingItems" state',
  1407. );
  1408. expect(setOrderShippingMethod.errorCode).toBe(ErrorCode.ORDER_MODIFICATION_ERROR);
  1409. });
  1410. it('adds a declined payment', async () => {
  1411. const { addPaymentToOrder } = await shopClient.query(addPaymentDocument, {
  1412. input: {
  1413. method: testFailingPaymentMethod.code,
  1414. metadata: {
  1415. foo: 'bar',
  1416. },
  1417. },
  1418. });
  1419. testOrderGuard.assertErrorResult(addPaymentToOrder);
  1420. expect(addPaymentToOrder.message).toBe('The payment was declined');
  1421. expect(addPaymentToOrder.errorCode).toBe(ErrorCode.PAYMENT_DECLINED_ERROR);
  1422. expect((addPaymentToOrder as any).paymentErrorMessage).toBe('Insufficient funds');
  1423. const { activeOrder: order } = await shopClient.query(getActiveOrderWithPaymentsDocument);
  1424. const payment = order!.payments![0];
  1425. expect(order!.state).toBe('ArrangingPayment');
  1426. expect(order!.payments!.length).toBe(1);
  1427. expect(payment.method).toBe(testFailingPaymentMethod.code);
  1428. expect(payment.state).toBe('Declined');
  1429. expect(payment.transactionId).toBe(null);
  1430. expect(payment.metadata).toEqual({
  1431. public: { foo: 'bar' },
  1432. });
  1433. });
  1434. it('adds an error payment and returns error result', async () => {
  1435. const { addPaymentToOrder } = await shopClient.query(addPaymentDocument, {
  1436. input: {
  1437. method: testErrorPaymentMethod.code,
  1438. metadata: {
  1439. foo: 'bar',
  1440. },
  1441. },
  1442. });
  1443. testOrderGuard.assertErrorResult(addPaymentToOrder);
  1444. expect(addPaymentToOrder.message).toBe('The payment failed');
  1445. expect(addPaymentToOrder.errorCode).toBe(ErrorCode.PAYMENT_FAILED_ERROR);
  1446. expect((addPaymentToOrder as any).paymentErrorMessage).toBe('Something went horribly wrong');
  1447. const result = await shopClient.query(getActiveOrderPaymentsDocument);
  1448. const payment = result.activeOrder!.payments![1];
  1449. expect(result.activeOrder!.payments!.length).toBe(2);
  1450. expect(payment.method).toBe(testErrorPaymentMethod.code);
  1451. expect(payment.state).toBe('Error');
  1452. expect(payment.errorMessage).toBe('Something went horribly wrong');
  1453. });
  1454. it('adds a successful payment and transitions Order state', async () => {
  1455. const { addPaymentToOrder } = await shopClient.query(addPaymentDocument, {
  1456. input: {
  1457. method: testSuccessfulPaymentMethod.code,
  1458. metadata: {
  1459. baz: 'quux',
  1460. },
  1461. },
  1462. });
  1463. orderResultGuard.assertSuccess(addPaymentToOrder);
  1464. const payment = addPaymentToOrder.payments!.find(p => p.transactionId === '12345')!;
  1465. expect(addPaymentToOrder.state).toBe('PaymentSettled');
  1466. expect(addPaymentToOrder.active).toBe(false);
  1467. expect(addPaymentToOrder.payments!.length).toBe(3);
  1468. expect(payment.method).toBe(testSuccessfulPaymentMethod.code);
  1469. expect(payment.state).toBe('Settled');
  1470. expect(payment.transactionId).toBe('12345');
  1471. expect(payment.metadata).toEqual({
  1472. public: { baz: 'quux' },
  1473. });
  1474. });
  1475. it('does not create new address when Customer already has address', async () => {
  1476. const { customer } = await adminClient.query(getCustomerDocument, { id: customers[0].id });
  1477. expect(customer!.addresses!.length).toBe(1);
  1478. });
  1479. });
  1480. describe('orderByCode', () => {
  1481. describe('immediately after Order is placed', () => {
  1482. it('works when authenticated', async () => {
  1483. const result = await shopClient.query(getOrderByCodeDocument, {
  1484. code: activeOrder.code,
  1485. });
  1486. expect(result.orderByCode!.id).toBe(activeOrder.id);
  1487. });
  1488. it('works when anonymous', async () => {
  1489. await shopClient.asAnonymousUser();
  1490. const result = await shopClient.query(getOrderByCodeDocument, {
  1491. code: activeOrder.code,
  1492. });
  1493. expect(result.orderByCode!.id).toBe(activeOrder.id);
  1494. });
  1495. it(
  1496. "throws error for another user's Order",
  1497. assertThrowsWithMessage(async () => {
  1498. authenticatedUserEmailAddress = customers[1].emailAddress;
  1499. await shopClient.asUserWithCredentials(authenticatedUserEmailAddress, password);
  1500. return shopClient.query(getOrderByCodeDocument, {
  1501. code: activeOrder.code,
  1502. });
  1503. }, 'You are not currently authorized to perform this action'),
  1504. );
  1505. });
  1506. describe('3 hours after the Order has been placed', () => {
  1507. let dateNowMock: any;
  1508. beforeAll(() => {
  1509. // mock Date.now: add 3 hours
  1510. const nowIn3H = Date.now() + 3 * 3600 * 1000;
  1511. dateNowMock = vi.spyOn(global.Date, 'now').mockImplementation(() => nowIn3H);
  1512. });
  1513. it('still works when authenticated as owner', async () => {
  1514. authenticatedUserEmailAddress = customers[0].emailAddress;
  1515. await shopClient.asUserWithCredentials(authenticatedUserEmailAddress, password);
  1516. const result = await shopClient.query(getOrderByCodeDocument, {
  1517. code: activeOrder.code,
  1518. });
  1519. expect(result.orderByCode!.id).toBe(activeOrder.id);
  1520. });
  1521. it(
  1522. 'access denied when anonymous',
  1523. assertThrowsWithMessage(async () => {
  1524. await shopClient.asAnonymousUser();
  1525. await shopClient.query(getOrderByCodeDocument, {
  1526. code: activeOrder.code,
  1527. });
  1528. }, 'You are not currently authorized to perform this action'),
  1529. );
  1530. afterAll(() => {
  1531. // restore Date.now
  1532. dateNowMock.mockRestore();
  1533. });
  1534. });
  1535. });
  1536. });
  1537. describe('order merging', () => {
  1538. let customers: ResultOf<typeof getCustomerListDocument>['customers']['items'];
  1539. beforeAll(async () => {
  1540. const result = await adminClient.query(getCustomerListDocument);
  1541. customers = result.customers.items;
  1542. });
  1543. it('merges guest order with no existing order', async () => {
  1544. await shopClient.asAnonymousUser();
  1545. const { addItemToOrder } = await shopClient.query(addItemToOrderDocument, {
  1546. productVariantId: 'T_1',
  1547. quantity: 1,
  1548. });
  1549. orderResultGuard.assertSuccess(addItemToOrder);
  1550. expect(addItemToOrder.lines.length).toBe(1);
  1551. expect(addItemToOrder.lines[0].productVariant.id).toBe('T_1');
  1552. await shopClient.query(attemptLoginDocument, {
  1553. username: customers[1].emailAddress,
  1554. password: 'test',
  1555. });
  1556. const { activeOrder } = await shopClient.query(getActiveOrderDocument);
  1557. expect(activeOrder!.lines.length).toBe(1);
  1558. expect(activeOrder!.lines[0].productVariant.id).toBe('T_1');
  1559. });
  1560. it('merges guest order with existing order', async () => {
  1561. await shopClient.asAnonymousUser();
  1562. const { addItemToOrder } = await shopClient.query(addItemToOrderDocument, {
  1563. productVariantId: 'T_2',
  1564. quantity: 1,
  1565. });
  1566. orderResultGuard.assertSuccess(addItemToOrder);
  1567. expect(addItemToOrder.lines.length).toBe(1);
  1568. expect(addItemToOrder.lines[0].productVariant.id).toBe('T_2');
  1569. await shopClient.query(attemptLoginDocument, {
  1570. username: customers[1].emailAddress,
  1571. password: 'test',
  1572. });
  1573. const { activeOrder } = await shopClient.query(getActiveOrderDocument);
  1574. expect(activeOrder!.lines.length).toBe(2);
  1575. expect(activeOrder!.lines[0].productVariant.id).toBe('T_1');
  1576. expect(activeOrder!.lines[1].productVariant.id).toBe('T_2');
  1577. });
  1578. /**
  1579. * See https://github.com/vendure-ecommerce/vendure/issues/263
  1580. */
  1581. it('does not merge when logging in to a different account (issue #263)', async () => {
  1582. await shopClient.query(attemptLoginDocument, {
  1583. username: customers[2].emailAddress,
  1584. password: 'test',
  1585. });
  1586. const { activeOrder } = await shopClient.query(getActiveOrderDocument);
  1587. expect(activeOrder).toBeNull();
  1588. });
  1589. it('does not merge when logging back to other account (issue #263)', async () => {
  1590. await shopClient.query(addItemToOrderDocument, {
  1591. productVariantId: 'T_3',
  1592. quantity: 1,
  1593. });
  1594. await shopClient.query(attemptLoginDocument, {
  1595. username: customers[1].emailAddress,
  1596. password: 'test',
  1597. });
  1598. const { activeOrder } = await shopClient.query(getActiveOrderDocument);
  1599. expect(activeOrder!.lines.length).toBe(2);
  1600. expect(activeOrder!.lines[0].productVariant.id).toBe('T_1');
  1601. expect(activeOrder!.lines[1].productVariant.id).toBe('T_2');
  1602. });
  1603. // https://github.com/vendure-ecommerce/vendure/issues/754
  1604. it('handles merging when an existing order has OrderLines', async () => {
  1605. async function setShippingOnActiveOrder() {
  1606. await shopClient.query(setShippingAddressDocument, {
  1607. input: {
  1608. streetLine1: '12 the street',
  1609. countryCode: 'US',
  1610. },
  1611. });
  1612. const { eligibleShippingMethods } = await shopClient.query(
  1613. getEligibleShippingMethodsDocument,
  1614. );
  1615. await shopClient.query(setShippingMethodDocument, {
  1616. id: [eligibleShippingMethods[1].id],
  1617. });
  1618. }
  1619. // Set up an existing order and add a ShippingLine
  1620. await shopClient.asUserWithCredentials(customers[2].emailAddress, 'test');
  1621. await shopClient.query(addItemToOrderDocument, {
  1622. productVariantId: 'T_3',
  1623. quantity: 1,
  1624. });
  1625. await setShippingOnActiveOrder();
  1626. // Now start a new guest order
  1627. await shopClient.query(logOutDocument);
  1628. await shopClient.query(addItemToOrderDocument, {
  1629. productVariantId: 'T_4',
  1630. quantity: 1,
  1631. });
  1632. await setShippingOnActiveOrder();
  1633. // attempt to log in and merge the guest order with the existing order
  1634. const { login } = await shopClient.query(attemptLoginDocument, {
  1635. username: customers[2].emailAddress,
  1636. password: 'test',
  1637. });
  1638. currentUserGuard.assertSuccess(login);
  1639. expect(login.identifier).toBe(customers[2].emailAddress);
  1640. });
  1641. });
  1642. describe('security of customer data', () => {
  1643. let customers: ResultOf<typeof getCustomerListDocument>['customers']['items'];
  1644. beforeAll(async () => {
  1645. const result = await adminClient.query(getCustomerListDocument);
  1646. customers = result.customers.items;
  1647. });
  1648. it('cannot setCustomOrder to existing non-guest Customer', async () => {
  1649. await shopClient.asAnonymousUser();
  1650. await shopClient.query(addItemToOrderDocument, {
  1651. productVariantId: 'T_1',
  1652. quantity: 1,
  1653. });
  1654. const { setCustomerForOrder } = await shopClient.query(setCustomerDocument, {
  1655. input: {
  1656. emailAddress: customers[0].emailAddress,
  1657. firstName: 'Evil',
  1658. lastName: 'Hacker',
  1659. },
  1660. });
  1661. setCustomerForOrderGuard.assertErrorResult(setCustomerForOrder);
  1662. expect(setCustomerForOrder.message).toBe('The email address is not available.');
  1663. expect(setCustomerForOrder.errorCode).toBe(ErrorCode.EMAIL_ADDRESS_CONFLICT_ERROR);
  1664. const { customer } = await adminClient.query(getCustomerDocument, {
  1665. id: customers[0].id,
  1666. });
  1667. expect(customer!.firstName).not.toBe('Evil');
  1668. expect(customer!.lastName).not.toBe('Hacker');
  1669. });
  1670. it('guest cannot access Addresses of guest customer', async () => {
  1671. await shopClient.asAnonymousUser();
  1672. await shopClient.query(addItemToOrderDocument, {
  1673. productVariantId: 'T_1',
  1674. quantity: 1,
  1675. });
  1676. await shopClient.query(setCustomerDocument, {
  1677. input: {
  1678. emailAddress: 'test@test.com',
  1679. firstName: 'Evil',
  1680. lastName: 'Hacker',
  1681. },
  1682. });
  1683. const { activeOrder } = await shopClient.query(getActiveOrderAddressesDocument);
  1684. expect(activeOrder!.customer!.addresses).toEqual([]);
  1685. });
  1686. it('guest cannot access Orders of guest customer', async () => {
  1687. await shopClient.asAnonymousUser();
  1688. await shopClient.query(addItemToOrderDocument, {
  1689. productVariantId: 'T_1',
  1690. quantity: 1,
  1691. });
  1692. await shopClient.query(setCustomerDocument, {
  1693. input: {
  1694. emailAddress: 'test@test.com',
  1695. firstName: 'Evil',
  1696. lastName: 'Hacker',
  1697. },
  1698. });
  1699. const { activeOrder } = await shopClient.query(getActiveOrderOrdersDocument);
  1700. expect(activeOrder!.customer!.orders.items).toEqual([]);
  1701. });
  1702. });
  1703. describe('order custom fields', () => {
  1704. it('custom fields added to type', async () => {
  1705. await shopClient.asAnonymousUser();
  1706. await shopClient.query(addItemToOrderDocument, {
  1707. productVariantId: 'T_1',
  1708. quantity: 1,
  1709. });
  1710. const { activeOrder } = await shopClient.query(getOrderCustomFieldsDocument);
  1711. expect(activeOrder?.customFields).toEqual({
  1712. orderImage: null,
  1713. giftWrap: false,
  1714. });
  1715. });
  1716. it('setting order custom fields', async () => {
  1717. const { setOrderCustomFields } = await shopClient.query(setOrderCustomFieldsDocument, {
  1718. input: {
  1719. customFields: { giftWrap: true, orderImageId: 'T_1' },
  1720. },
  1721. });
  1722. if (!('customFields' in setOrderCustomFields)) {
  1723. fail('Expected setOrderCustomFields to have customFields');
  1724. }
  1725. expect(setOrderCustomFields.customFields).toEqual({
  1726. orderImage: { id: 'T_1' },
  1727. giftWrap: true,
  1728. });
  1729. const { activeOrder } = await shopClient.query(getOrderCustomFieldsDocument);
  1730. expect(activeOrder?.customFields).toEqual({
  1731. orderImage: { id: 'T_1' },
  1732. giftWrap: true,
  1733. });
  1734. });
  1735. });
  1736. describe('remove all order lines', () => {
  1737. beforeAll(async () => {
  1738. await shopClient.asAnonymousUser();
  1739. await shopClient.query(addItemToOrderDocument, {
  1740. productVariantId: 'T_1',
  1741. quantity: 1,
  1742. });
  1743. await shopClient.query(addItemToOrderDocument, {
  1744. productVariantId: 'T_2',
  1745. quantity: 3,
  1746. });
  1747. });
  1748. it('should remove all order lines', async () => {
  1749. const { removeAllOrderLines } = await shopClient.query(removeAllOrderLinesDocument);
  1750. orderResultGuard.assertSuccess(removeAllOrderLines);
  1751. expect(removeAllOrderLines?.total).toBe(0);
  1752. expect(removeAllOrderLines?.lines.length).toBe(0);
  1753. });
  1754. });
  1755. describe('validation of product variant availability', () => {
  1756. const bonsaiProductId = 'T_20';
  1757. const bonsaiVariantId = 'T_34';
  1758. beforeAll(async () => {
  1759. await shopClient.asAnonymousUser();
  1760. });
  1761. it(
  1762. 'addItemToOrder errors when product is disabled',
  1763. assertThrowsWithMessage(async () => {
  1764. await adminClient.query(updateProductDocument, {
  1765. input: {
  1766. id: bonsaiProductId,
  1767. enabled: false,
  1768. },
  1769. });
  1770. await shopClient.query(addItemToOrderDocument, {
  1771. productVariantId: bonsaiVariantId,
  1772. quantity: 1,
  1773. });
  1774. }, 'No ProductVariant with the id "34" could be found'),
  1775. );
  1776. it(
  1777. 'addItemToOrder errors when product variant is disabled',
  1778. assertThrowsWithMessage(async () => {
  1779. await adminClient.query(updateProductDocument, {
  1780. input: {
  1781. id: bonsaiProductId,
  1782. enabled: true,
  1783. },
  1784. });
  1785. await adminClient.query(updateProductVariantsDocument, {
  1786. input: [
  1787. {
  1788. id: bonsaiVariantId,
  1789. enabled: false,
  1790. },
  1791. ],
  1792. });
  1793. await shopClient.query(addItemToOrderDocument, {
  1794. productVariantId: bonsaiVariantId,
  1795. quantity: 1,
  1796. });
  1797. }, 'No ProductVariant with the id "34" could be found'),
  1798. );
  1799. it(
  1800. 'addItemToOrder errors when product is deleted',
  1801. assertThrowsWithMessage(async () => {
  1802. await adminClient.query(deleteProductDocument, {
  1803. id: bonsaiProductId,
  1804. });
  1805. await shopClient.query(addItemToOrderDocument, {
  1806. productVariantId: bonsaiVariantId,
  1807. quantity: 1,
  1808. });
  1809. }, 'No ProductVariant with the id "34" could be found'),
  1810. );
  1811. it(
  1812. 'addItemToOrder errors when product variant is deleted',
  1813. assertThrowsWithMessage(async () => {
  1814. await adminClient.query(deleteProductVariantDocument, {
  1815. id: bonsaiVariantId,
  1816. });
  1817. await shopClient.query(addItemToOrderDocument, {
  1818. productVariantId: bonsaiVariantId,
  1819. quantity: 1,
  1820. });
  1821. }, 'No ProductVariant with the id "34" could be found'),
  1822. );
  1823. let orderWithDeletedProductVariantId: string;
  1824. it('errors when transitioning to ArrangingPayment with deleted variant', async () => {
  1825. const orchidProductId = 'T_19';
  1826. const orchidVariantId = 'T_33';
  1827. await shopClient.asUserWithCredentials('marques.sawayn@hotmail.com', 'test');
  1828. const { addItemToOrder } = await shopClient.query(addItemToOrderDocument, {
  1829. productVariantId: orchidVariantId,
  1830. quantity: 1,
  1831. });
  1832. orderResultGuard.assertSuccess(addItemToOrder);
  1833. orderWithDeletedProductVariantId = addItemToOrder.id;
  1834. await adminClient.query(deleteProductDocument, {
  1835. id: orchidProductId,
  1836. });
  1837. const { transitionOrderToState } = await shopClient.query(transitionToStateDocument, {
  1838. state: 'ArrangingPayment',
  1839. });
  1840. if (transitionOrderToState) {
  1841. testOrderGuard.assertErrorResult(transitionOrderToState);
  1842. expect(transitionOrderToState.transitionError).toBe(
  1843. 'Cannot transition to "ArrangingPayment" because the Order contains ProductVariants which are no longer available',
  1844. );
  1845. } else {
  1846. fail('Expected transitionOrderToState to exist');
  1847. }
  1848. expect(transitionOrderToState.errorCode).toBe(ErrorCode.ORDER_STATE_TRANSITION_ERROR);
  1849. });
  1850. // https://github.com/vendure-ecommerce/vendure/issues/1567
  1851. it('allows transitioning to Cancelled with deleted variant', async () => {
  1852. const { cancelOrder } = await adminClient.query(cancelOrderDocument, {
  1853. input: {
  1854. orderId: orderWithDeletedProductVariantId,
  1855. },
  1856. });
  1857. orderResultGuard.assertSuccess(cancelOrder);
  1858. expect(cancelOrder.state).toBe('Cancelled');
  1859. });
  1860. });
  1861. // https://github.com/vendure-ecommerce/vendure/issues/1195
  1862. describe('shipping method invalidation', () => {
  1863. let GBShippingMethodId: string;
  1864. let ATShippingMethodId: string;
  1865. beforeAll(async () => {
  1866. // First we will remove all ShippingMethods and set up 2 specialized ones
  1867. const { shippingMethods } = await adminClient.query(getShippingMethodListDocument);
  1868. for (const method of shippingMethods.items) {
  1869. await adminClient.query(deleteShippingMethodDocument, {
  1870. id: method.id,
  1871. });
  1872. }
  1873. function createCountryCodeShippingMethodInput(countryCode: string): CreateShippingMethodInput {
  1874. return {
  1875. code: `${countryCode}-shipping`,
  1876. translations: [
  1877. { languageCode: LanguageCode.en, name: `${countryCode} shipping`, description: '' },
  1878. ],
  1879. fulfillmentHandler: manualFulfillmentHandler.code,
  1880. checker: {
  1881. code: countryCodeShippingEligibilityChecker.code,
  1882. arguments: [{ name: 'countryCode', value: countryCode }],
  1883. },
  1884. calculator: {
  1885. code: defaultShippingCalculator.code,
  1886. arguments: [
  1887. { name: 'rate', value: '1000' },
  1888. { name: 'taxRate', value: '0' },
  1889. { name: 'includesTax', value: 'auto' },
  1890. ],
  1891. },
  1892. };
  1893. }
  1894. // Now create 2 shipping methods, valid only for a single country
  1895. const result1 = await adminClient.query(createShippingMethodDocument, {
  1896. input: createCountryCodeShippingMethodInput('GB'),
  1897. });
  1898. GBShippingMethodId = result1.createShippingMethod.id;
  1899. const result2 = await adminClient.query(createShippingMethodDocument, {
  1900. input: createCountryCodeShippingMethodInput('AT'),
  1901. });
  1902. ATShippingMethodId = result2.createShippingMethod.id;
  1903. // Now create an order to GB and set the GB shipping method
  1904. await shopClient.query(addItemToOrderDocument, {
  1905. productVariantId: 'T_1',
  1906. quantity: 1,
  1907. });
  1908. await shopClient.query(setCustomerDocument, {
  1909. input: {
  1910. emailAddress: 'test-2@test.com',
  1911. firstName: 'Test',
  1912. lastName: 'Person 2',
  1913. },
  1914. });
  1915. await shopClient.query(setShippingAddressDocument, {
  1916. input: {
  1917. streetLine1: '12 the street',
  1918. countryCode: 'GB',
  1919. },
  1920. });
  1921. await shopClient.query(setShippingMethodDocument, {
  1922. id: [GBShippingMethodId],
  1923. });
  1924. });
  1925. it('if selected method no longer eligible, next best is set automatically', async () => {
  1926. const result1 = await shopClient.query(getActiveOrderDocument);
  1927. expect(result1.activeOrder?.shippingLines[0].shippingMethod.id).toBe(GBShippingMethodId);
  1928. await shopClient.query(setShippingAddressDocument, {
  1929. input: {
  1930. streetLine1: '12 the street',
  1931. countryCode: 'AT',
  1932. },
  1933. });
  1934. const result2 = await shopClient.query(getActiveOrderDocument);
  1935. expect(result2.activeOrder?.shippingLines[0].shippingMethod.id).toBe(ATShippingMethodId);
  1936. });
  1937. it('if no method is eligible, shipping lines are cleared', async () => {
  1938. await shopClient.query(setShippingAddressDocument, {
  1939. input: {
  1940. streetLine1: '12 the street',
  1941. countryCode: 'US',
  1942. },
  1943. });
  1944. const result = await shopClient.query(getActiveOrderDocument);
  1945. expect(result.activeOrder?.shippingLines).toEqual([]);
  1946. });
  1947. // https://github.com/vendure-ecommerce/vendure/issues/1441
  1948. it('shipping methods are re-evaluated when all OrderLines are removed', async () => {
  1949. const { createShippingMethod } = await adminClient.query(createShippingMethodDocument, {
  1950. input: {
  1951. code: 'min-price-shipping',
  1952. translations: [
  1953. { languageCode: LanguageCode.en, name: 'min price shipping', description: '' },
  1954. ],
  1955. fulfillmentHandler: manualFulfillmentHandler.code,
  1956. checker: {
  1957. code: defaultShippingEligibilityChecker.code,
  1958. arguments: [{ name: 'orderMinimum', value: '100' }],
  1959. },
  1960. calculator: {
  1961. code: defaultShippingCalculator.code,
  1962. arguments: [
  1963. { name: 'rate', value: '1000' },
  1964. { name: 'taxRate', value: '0' },
  1965. { name: 'includesTax', value: 'auto' },
  1966. ],
  1967. },
  1968. },
  1969. });
  1970. const minPriceShippingMethodId = createShippingMethod.id;
  1971. await shopClient.query(setShippingMethodDocument, {
  1972. id: [minPriceShippingMethodId],
  1973. });
  1974. const result1 = await shopClient.query(getActiveOrderDocument);
  1975. expect(result1.activeOrder?.shippingLines[0].shippingMethod.id).toBe(minPriceShippingMethodId);
  1976. const { removeAllOrderLines } = await shopClient.query(removeAllOrderLinesDocument);
  1977. orderResultGuard.assertSuccess(removeAllOrderLines);
  1978. expect(removeAllOrderLines.shippingLines.length).toBe(0);
  1979. expect(removeAllOrderLines.shippingWithTax).toBe(0);
  1980. });
  1981. });
  1982. describe('edge cases', () => {
  1983. it('calling setShippingMethod and setBillingMethod in parallel does not introduce race condition', async () => {
  1984. const shippingAddress: CreateAddressInput = {
  1985. fullName: 'name',
  1986. company: 'company',
  1987. streetLine1: '12 Shipping Street',
  1988. streetLine2: null,
  1989. city: 'foo',
  1990. province: 'bar',
  1991. postalCode: '123456',
  1992. countryCode: 'US',
  1993. phoneNumber: '4444444',
  1994. };
  1995. const billingAddress: CreateAddressInput = {
  1996. fullName: 'name',
  1997. company: 'company',
  1998. streetLine1: '22 Billing Avenue',
  1999. streetLine2: null,
  2000. city: 'foo',
  2001. province: 'bar',
  2002. postalCode: '123456',
  2003. countryCode: 'US',
  2004. phoneNumber: '4444444',
  2005. };
  2006. await Promise.all([
  2007. shopClient.query(setBillingAddressDocument, {
  2008. input: billingAddress,
  2009. }),
  2010. shopClient.query(setShippingAddressDocument, {
  2011. input: shippingAddress,
  2012. }),
  2013. ]);
  2014. const { activeOrder } = await shopClient.query(getActiveOrderShippingBillingDocument);
  2015. activeOrderGuard.assertSuccess(activeOrder);
  2016. expect(activeOrder.shippingAddress).toEqual(shippingAddress);
  2017. expect(activeOrder.billingAddress).toEqual(billingAddress);
  2018. });
  2019. // https://github.com/vendure-ecommerce/vendure/issues/2548
  2020. it('hydrating Order in the ShippingEligibilityChecker does not break order modification', async () => {
  2021. // First we'll create a ShippingMethod that uses the hydrating checker
  2022. await adminClient.query(createShippingMethodDocument, {
  2023. input: {
  2024. code: 'hydrating-checker',
  2025. translations: [
  2026. { languageCode: LanguageCode.en, name: 'hydrating checker', description: '' },
  2027. ],
  2028. fulfillmentHandler: manualFulfillmentHandler.code,
  2029. checker: {
  2030. code: hydratingShippingEligibilityChecker.code,
  2031. arguments: [],
  2032. },
  2033. calculator: {
  2034. code: defaultShippingCalculator.code,
  2035. arguments: [
  2036. { name: 'rate', value: '1000' },
  2037. { name: 'taxRate', value: '0' },
  2038. { name: 'includesTax', value: 'auto' },
  2039. ],
  2040. },
  2041. },
  2042. });
  2043. await shopClient.asAnonymousUser();
  2044. await shopClient.query(addItemToOrderDocument, {
  2045. productVariantId: 'T_1',
  2046. quantity: 1,
  2047. });
  2048. await shopClient.query(addItemToOrderDocument, {
  2049. productVariantId: 'T_2',
  2050. quantity: 3,
  2051. });
  2052. const result1 = await shopClient.query(getActiveOrderDocument);
  2053. expect(result1.activeOrder?.lines.map(l => l.linePriceWithTax).sort()).toEqual([155880, 503640]);
  2054. expect(result1.activeOrder?.subTotalWithTax).toBe(659520);
  2055. // set the shipping method that uses the hydrating checker
  2056. const { eligibleShippingMethods } = await shopClient.query(getEligibleShippingMethodsDocument);
  2057. const { setOrderShippingMethod } = await shopClient.query(setShippingMethodDocument, {
  2058. id: [eligibleShippingMethods.find(m => m.code === 'hydrating-checker')!.id],
  2059. });
  2060. orderResultGuard.assertSuccess(setOrderShippingMethod);
  2061. // Remove an item from the order
  2062. const { removeOrderLine } = await shopClient.query(removeItemFromOrderDocument, {
  2063. orderLineId: result1.activeOrder!.lines[0].id,
  2064. });
  2065. orderResultGuard.assertSuccess(removeOrderLine);
  2066. expect(removeOrderLine.lines.length).toBe(1);
  2067. const result2 = await shopClient.query(getActiveOrderDocument);
  2068. expect(result2.activeOrder?.lines.map(l => l.linePriceWithTax).sort()).toEqual([503640]);
  2069. expect(result2.activeOrder?.subTotalWithTax).toBe(503640);
  2070. });
  2071. });
  2072. });