shop-order.e2e-spec.ts 119 KB

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