shop-order.e2e-spec.ts 100 KB

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