shop-order.e2e-spec.ts 121 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908
  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/vendurehq/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/vendurehq/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. it('adds multiple items to order with different custom fields', async () => {
  1257. await shopClient.asAnonymousUser(); // New order
  1258. const { addItemsToOrder } = await shopClient.query<CodegenShop.AddItemsToOrderMutation>(
  1259. ADD_MULTIPLE_ITEMS_TO_ORDER_WITH_CUSTOM_FIELDS,
  1260. {
  1261. inputs: [
  1262. {
  1263. productVariantId: 'T_1',
  1264. quantity: 1,
  1265. customFields: {
  1266. notes: 'Variant 1 note',
  1267. },
  1268. },
  1269. {
  1270. productVariantId: 'T_2',
  1271. quantity: 2,
  1272. customFields: {
  1273. notes: 'Variant 2 note',
  1274. },
  1275. },
  1276. {
  1277. productVariantId: 'T_3',
  1278. quantity: 3,
  1279. // no custom field
  1280. },
  1281. ],
  1282. },
  1283. );
  1284. const order = addItemsToOrder.order as any;
  1285. expect(order.lines.length).toBe(3);
  1286. expect(order.lines[0].customFields.notes).toBe('Variant 1 note');
  1287. expect(order.lines[1].quantity).toBe(2);
  1288. expect(order.lines[1].customFields.notes).toBe('Variant 2 note');
  1289. expect(order.lines[2].quantity).toBe(3);
  1290. expect(order.lines[2].customFields.notes).toBeNull();
  1291. });
  1292. });
  1293. describe('ordering as authenticated user', () => {
  1294. let firstOrderLineId: string;
  1295. let activeOrder: CodegenShop.UpdatedOrderFragment;
  1296. let authenticatedUserEmailAddress: string;
  1297. let customers: Codegen.GetCustomerListQuery['customers']['items'];
  1298. const password = 'test';
  1299. beforeAll(async () => {
  1300. await adminClient.asSuperAdmin();
  1301. const result = await adminClient.query<
  1302. Codegen.GetCustomerListQuery,
  1303. Codegen.GetCustomerListQueryVariables
  1304. >(GET_CUSTOMER_LIST, {
  1305. options: {
  1306. take: 2,
  1307. },
  1308. });
  1309. customers = result.customers.items;
  1310. authenticatedUserEmailAddress = customers[0].emailAddress;
  1311. await shopClient.asUserWithCredentials(authenticatedUserEmailAddress, password);
  1312. });
  1313. it('activeOrder returns null before any items have been added', async () => {
  1314. const result = await shopClient.query<CodegenShop.GetActiveOrderQuery>(GET_ACTIVE_ORDER);
  1315. expect(result.activeOrder).toBeNull();
  1316. });
  1317. it('addItemToOrder creates a new Order with an item', async () => {
  1318. const { addItemToOrder } = await shopClient.query<
  1319. CodegenShop.AddItemToOrderMutation,
  1320. CodegenShop.AddItemToOrderMutationVariables
  1321. >(ADD_ITEM_TO_ORDER, {
  1322. productVariantId: 'T_1',
  1323. quantity: 1,
  1324. });
  1325. orderResultGuard.assertSuccess(addItemToOrder);
  1326. expect(addItemToOrder.lines.length).toBe(1);
  1327. expect(addItemToOrder.lines[0].quantity).toBe(1);
  1328. expect(addItemToOrder.lines[0].productVariant.id).toBe('T_1');
  1329. activeOrder = addItemToOrder!;
  1330. firstOrderLineId = addItemToOrder.lines[0].id;
  1331. });
  1332. it('activeOrder returns order after item has been added', async () => {
  1333. const result = await shopClient.query<CodegenShop.GetActiveOrderQuery>(GET_ACTIVE_ORDER);
  1334. expect(result.activeOrder!.id).toBe(activeOrder.id);
  1335. expect(result.activeOrder!.state).toBe('AddingItems');
  1336. });
  1337. it('activeOrder resolves customer user', async () => {
  1338. const result = await shopClient.query<CodegenShop.GetActiveOrderQuery>(GET_ACTIVE_ORDER);
  1339. expect(result.activeOrder!.customer!.user).toEqual({
  1340. id: 'T_2',
  1341. identifier: 'hayden.zieme12@hotmail.com',
  1342. });
  1343. });
  1344. it('addItemToOrder with an existing productVariantId adds quantity to the existing OrderLine', async () => {
  1345. const { addItemToOrder } = await shopClient.query<
  1346. CodegenShop.AddItemToOrderMutation,
  1347. CodegenShop.AddItemToOrderMutationVariables
  1348. >(ADD_ITEM_TO_ORDER, {
  1349. productVariantId: 'T_1',
  1350. quantity: 2,
  1351. });
  1352. orderResultGuard.assertSuccess(addItemToOrder);
  1353. expect(addItemToOrder.lines.length).toBe(1);
  1354. expect(addItemToOrder.lines[0].quantity).toBe(3);
  1355. });
  1356. it('adjustOrderLine adjusts the quantity', async () => {
  1357. const { adjustOrderLine } = await shopClient.query<
  1358. CodegenShop.AdjustItemQuantityMutation,
  1359. CodegenShop.AdjustItemQuantityMutationVariables
  1360. >(ADJUST_ITEM_QUANTITY, {
  1361. orderLineId: firstOrderLineId,
  1362. quantity: 50,
  1363. });
  1364. orderResultGuard.assertSuccess(adjustOrderLine);
  1365. expect(adjustOrderLine.lines.length).toBe(1);
  1366. expect(adjustOrderLine.lines[0].quantity).toBe(50);
  1367. });
  1368. it('removeItemFromOrder removes the correct item', async () => {
  1369. const { addItemToOrder } = await shopClient.query<
  1370. CodegenShop.AddItemToOrderMutation,
  1371. CodegenShop.AddItemToOrderMutationVariables
  1372. >(ADD_ITEM_TO_ORDER, {
  1373. productVariantId: 'T_3',
  1374. quantity: 3,
  1375. });
  1376. orderResultGuard.assertSuccess(addItemToOrder);
  1377. expect(addItemToOrder.lines.length).toBe(2);
  1378. expect(addItemToOrder.lines.map(i => i.productVariant.id)).toEqual(['T_1', 'T_3']);
  1379. const { removeOrderLine } = await shopClient.query<
  1380. CodegenShop.RemoveItemFromOrderMutation,
  1381. CodegenShop.RemoveItemFromOrderMutationVariables
  1382. >(REMOVE_ITEM_FROM_ORDER, {
  1383. orderLineId: firstOrderLineId,
  1384. });
  1385. orderResultGuard.assertSuccess(removeOrderLine);
  1386. expect(removeOrderLine.lines.length).toBe(1);
  1387. expect(removeOrderLine.lines.map(i => i.productVariant.id)).toEqual(['T_3']);
  1388. });
  1389. it('nextOrderStates returns next valid states', async () => {
  1390. const result = await shopClient.query<CodegenShop.GetNextOrderStatesQuery>(GET_NEXT_STATES);
  1391. expect(result.nextOrderStates).toEqual(['ArrangingPayment', 'Cancelled']);
  1392. });
  1393. it('logging out and back in again resumes the last active order', async () => {
  1394. await shopClient.asAnonymousUser();
  1395. const result1 = await shopClient.query<CodegenShop.GetActiveOrderQuery>(GET_ACTIVE_ORDER);
  1396. expect(result1.activeOrder).toBeNull();
  1397. await shopClient.asUserWithCredentials(authenticatedUserEmailAddress, password);
  1398. const result2 = await shopClient.query<CodegenShop.GetActiveOrderQuery>(GET_ACTIVE_ORDER);
  1399. expect(result2.activeOrder!.id).toBe(activeOrder.id);
  1400. });
  1401. it('cannot setCustomerForOrder when already logged in', async () => {
  1402. const { setCustomerForOrder } = await shopClient.query<
  1403. CodegenShop.SetCustomerForOrderMutation,
  1404. CodegenShop.SetCustomerForOrderMutationVariables
  1405. >(SET_CUSTOMER, {
  1406. input: {
  1407. emailAddress: 'newperson@email.com',
  1408. firstName: 'New',
  1409. lastName: 'Person',
  1410. },
  1411. });
  1412. orderResultGuard.assertErrorResult(setCustomerForOrder);
  1413. expect(setCustomerForOrder.message).toBe(
  1414. 'Cannot set a Customer for the Order when already logged in',
  1415. );
  1416. expect(setCustomerForOrder.errorCode).toBe(ErrorCode.ALREADY_LOGGED_IN_ERROR);
  1417. });
  1418. describe('shipping', () => {
  1419. let shippingMethods: CodegenShop.GetShippingMethodsQuery['eligibleShippingMethods'];
  1420. it(
  1421. 'setOrderShippingAddress throws with invalid countryCode',
  1422. assertThrowsWithMessage(() => {
  1423. const address: CreateAddressInput = {
  1424. streetLine1: '12 the street',
  1425. countryCode: 'INVALID',
  1426. };
  1427. return shopClient.query<
  1428. CodegenShop.SetShippingAddressMutation,
  1429. CodegenShop.SetShippingAddressMutationVariables
  1430. >(SET_SHIPPING_ADDRESS, {
  1431. input: address,
  1432. });
  1433. }, 'The countryCode "INVALID" was not recognized'),
  1434. );
  1435. it('setOrderShippingAddress sets shipping address', async () => {
  1436. const address: CreateAddressInput = {
  1437. fullName: 'name',
  1438. company: 'company',
  1439. streetLine1: '12 the street',
  1440. streetLine2: null,
  1441. city: 'foo',
  1442. province: 'bar',
  1443. postalCode: '123456',
  1444. countryCode: 'US',
  1445. phoneNumber: '4444444',
  1446. };
  1447. const { setOrderShippingAddress } = await shopClient.query<
  1448. CodegenShop.SetShippingAddressMutation,
  1449. CodegenShop.SetShippingAddressMutationVariables
  1450. >(SET_SHIPPING_ADDRESS, {
  1451. input: address,
  1452. });
  1453. expect(setOrderShippingAddress.shippingAddress).toEqual({
  1454. fullName: 'name',
  1455. company: 'company',
  1456. streetLine1: '12 the street',
  1457. streetLine2: null,
  1458. city: 'foo',
  1459. province: 'bar',
  1460. postalCode: '123456',
  1461. country: 'United States of America',
  1462. phoneNumber: '4444444',
  1463. });
  1464. });
  1465. it('eligibleShippingMethods lists shipping methods', async () => {
  1466. const result = await shopClient.query<CodegenShop.GetShippingMethodsQuery>(
  1467. GET_ELIGIBLE_SHIPPING_METHODS,
  1468. );
  1469. shippingMethods = result.eligibleShippingMethods;
  1470. expect(shippingMethods).toEqual([
  1471. {
  1472. id: 'T_1',
  1473. price: 500,
  1474. code: 'standard-shipping',
  1475. name: 'Standard Shipping',
  1476. description: '',
  1477. },
  1478. {
  1479. id: 'T_2',
  1480. price: 1000,
  1481. code: 'express-shipping',
  1482. name: 'Express Shipping',
  1483. description: '',
  1484. },
  1485. {
  1486. id: 'T_3',
  1487. price: 1000,
  1488. code: 'express-shipping-taxed',
  1489. name: 'Express Shipping (Taxed)',
  1490. description: '',
  1491. },
  1492. ]);
  1493. });
  1494. it('shipping is initially unset', async () => {
  1495. const result = await shopClient.query<CodegenShop.GetActiveOrderQuery>(GET_ACTIVE_ORDER);
  1496. expect(result.activeOrder!.shipping).toEqual(0);
  1497. expect(result.activeOrder!.shippingLines).toEqual([]);
  1498. });
  1499. it('setOrderShippingMethod sets the shipping method', async () => {
  1500. const result = await shopClient.query<
  1501. CodegenShop.SetShippingMethodMutation,
  1502. CodegenShop.SetShippingMethodMutationVariables
  1503. >(SET_SHIPPING_METHOD, {
  1504. id: shippingMethods[1].id,
  1505. });
  1506. const activeOrderResult =
  1507. await shopClient.query<CodegenShop.GetActiveOrderQuery>(GET_ACTIVE_ORDER);
  1508. const order = activeOrderResult.activeOrder!;
  1509. expect(order.shipping).toBe(shippingMethods[1].price);
  1510. expect(order.shippingLines[0].shippingMethod.id).toBe(shippingMethods[1].id);
  1511. expect(order.shippingLines[0].shippingMethod.description).toBe(
  1512. shippingMethods[1].description,
  1513. );
  1514. });
  1515. it('shipping method is preserved after adjustOrderLine', async () => {
  1516. const activeOrderResult =
  1517. await shopClient.query<CodegenShop.GetActiveOrderQuery>(GET_ACTIVE_ORDER);
  1518. activeOrder = activeOrderResult.activeOrder!;
  1519. const { adjustOrderLine } = await shopClient.query<
  1520. CodegenShop.AdjustItemQuantityMutation,
  1521. CodegenShop.AdjustItemQuantityMutationVariables
  1522. >(ADJUST_ITEM_QUANTITY, {
  1523. orderLineId: activeOrder.lines[0].id,
  1524. quantity: 10,
  1525. });
  1526. orderResultGuard.assertSuccess(adjustOrderLine);
  1527. expect(adjustOrderLine.shipping).toBe(shippingMethods[1].price);
  1528. expect(adjustOrderLine.shippingLines[0].shippingMethod.id).toBe(shippingMethods[1].id);
  1529. expect(adjustOrderLine.shippingLines[0].shippingMethod.description).toBe(
  1530. shippingMethods[1].description,
  1531. );
  1532. });
  1533. });
  1534. describe('payment', () => {
  1535. it('attempting add a Payment returns error result when in AddingItems state', async () => {
  1536. const { addPaymentToOrder } = await shopClient.query<
  1537. CodegenShop.AddPaymentToOrderMutation,
  1538. CodegenShop.AddPaymentToOrderMutationVariables
  1539. >(ADD_PAYMENT, {
  1540. input: {
  1541. method: testSuccessfulPaymentMethod.code,
  1542. metadata: {},
  1543. },
  1544. });
  1545. orderResultGuard.assertErrorResult(addPaymentToOrder);
  1546. expect(addPaymentToOrder.message).toBe(
  1547. 'A Payment may only be added when Order is in "ArrangingPayment" state',
  1548. );
  1549. expect(addPaymentToOrder.errorCode).toBe(ErrorCode.ORDER_PAYMENT_STATE_ERROR);
  1550. });
  1551. it('transitions to the ArrangingPayment state', async () => {
  1552. const { transitionOrderToState } = await shopClient.query<
  1553. CodegenShop.TransitionToStateMutation,
  1554. CodegenShop.TransitionToStateMutationVariables
  1555. >(TRANSITION_TO_STATE, { state: 'ArrangingPayment' });
  1556. orderResultGuard.assertSuccess(transitionOrderToState);
  1557. expect(pick(transitionOrderToState, ['id', 'state'])).toEqual({
  1558. id: activeOrder.id,
  1559. state: 'ArrangingPayment',
  1560. });
  1561. });
  1562. it('attempting to add an item returns error result when in ArrangingPayment state', async () => {
  1563. const { addItemToOrder } = await shopClient.query<
  1564. CodegenShop.AddItemToOrderMutation,
  1565. CodegenShop.AddItemToOrderMutationVariables
  1566. >(ADD_ITEM_TO_ORDER, {
  1567. productVariantId: 'T_4',
  1568. quantity: 1,
  1569. });
  1570. orderResultGuard.assertErrorResult(addItemToOrder);
  1571. expect(addItemToOrder.message).toBe(
  1572. 'Order contents may only be modified when in the "AddingItems" state',
  1573. );
  1574. expect(addItemToOrder.errorCode).toBe(ErrorCode.ORDER_MODIFICATION_ERROR);
  1575. });
  1576. it('attempting to modify item quantity returns error result when in ArrangingPayment state', async () => {
  1577. const { adjustOrderLine } = await shopClient.query<
  1578. CodegenShop.AdjustItemQuantityMutation,
  1579. CodegenShop.AdjustItemQuantityMutationVariables
  1580. >(ADJUST_ITEM_QUANTITY, {
  1581. orderLineId: activeOrder.lines[0].id,
  1582. quantity: 12,
  1583. });
  1584. orderResultGuard.assertErrorResult(adjustOrderLine);
  1585. expect(adjustOrderLine.message).toBe(
  1586. 'Order contents may only be modified when in the "AddingItems" state',
  1587. );
  1588. expect(adjustOrderLine.errorCode).toBe(ErrorCode.ORDER_MODIFICATION_ERROR);
  1589. });
  1590. it('attempting to remove an item returns error result when in ArrangingPayment state', async () => {
  1591. const { removeOrderLine } = await shopClient.query<
  1592. CodegenShop.RemoveItemFromOrderMutation,
  1593. CodegenShop.RemoveItemFromOrderMutationVariables
  1594. >(REMOVE_ITEM_FROM_ORDER, {
  1595. orderLineId: activeOrder.lines[0].id,
  1596. });
  1597. orderResultGuard.assertErrorResult(removeOrderLine);
  1598. expect(removeOrderLine.message).toBe(
  1599. 'Order contents may only be modified when in the "AddingItems" state',
  1600. );
  1601. expect(removeOrderLine.errorCode).toBe(ErrorCode.ORDER_MODIFICATION_ERROR);
  1602. });
  1603. it('attempting to remove all items returns error result when in ArrangingPayment state', async () => {
  1604. const { removeAllOrderLines } =
  1605. await shopClient.query<CodegenShop.RemoveAllOrderLinesMutation>(REMOVE_ALL_ORDER_LINES);
  1606. orderResultGuard.assertErrorResult(removeAllOrderLines);
  1607. expect(removeAllOrderLines.message).toBe(
  1608. 'Order contents may only be modified when in the "AddingItems" state',
  1609. );
  1610. expect(removeAllOrderLines.errorCode).toBe(ErrorCode.ORDER_MODIFICATION_ERROR);
  1611. });
  1612. it('attempting to setOrderShippingMethod returns error result when in ArrangingPayment state', async () => {
  1613. const shippingMethodsResult = await shopClient.query<CodegenShop.GetShippingMethodsQuery>(
  1614. GET_ELIGIBLE_SHIPPING_METHODS,
  1615. );
  1616. const shippingMethods = shippingMethodsResult.eligibleShippingMethods;
  1617. const { setOrderShippingMethod } = await shopClient.query<
  1618. CodegenShop.SetShippingMethodMutation,
  1619. CodegenShop.SetShippingMethodMutationVariables
  1620. >(SET_SHIPPING_METHOD, {
  1621. id: shippingMethods[0].id,
  1622. });
  1623. orderResultGuard.assertErrorResult(setOrderShippingMethod);
  1624. expect(setOrderShippingMethod.message).toBe(
  1625. 'Order contents may only be modified when in the "AddingItems" state',
  1626. );
  1627. expect(setOrderShippingMethod.errorCode).toBe(ErrorCode.ORDER_MODIFICATION_ERROR);
  1628. });
  1629. it('adds a declined payment', async () => {
  1630. const { addPaymentToOrder } = await shopClient.query<
  1631. CodegenShop.AddPaymentToOrderMutation,
  1632. CodegenShop.AddPaymentToOrderMutationVariables
  1633. >(ADD_PAYMENT, {
  1634. input: {
  1635. method: testFailingPaymentMethod.code,
  1636. metadata: {
  1637. foo: 'bar',
  1638. },
  1639. },
  1640. });
  1641. orderResultGuard.assertErrorResult(addPaymentToOrder);
  1642. expect(addPaymentToOrder.message).toBe('The payment was declined');
  1643. expect(addPaymentToOrder.errorCode).toBe(ErrorCode.PAYMENT_DECLINED_ERROR);
  1644. expect((addPaymentToOrder as any).paymentErrorMessage).toBe('Insufficient funds');
  1645. const { activeOrder: order } =
  1646. await shopClient.query<CodegenShop.GetActiveOrderWithPaymentsQuery>(
  1647. GET_ACTIVE_ORDER_WITH_PAYMENTS,
  1648. );
  1649. const payment = order!.payments![0];
  1650. expect(order!.state).toBe('ArrangingPayment');
  1651. expect(order!.payments!.length).toBe(1);
  1652. expect(payment.method).toBe(testFailingPaymentMethod.code);
  1653. expect(payment.state).toBe('Declined');
  1654. expect(payment.transactionId).toBe(null);
  1655. expect(payment.metadata).toEqual({
  1656. public: { foo: 'bar' },
  1657. });
  1658. });
  1659. it('adds an error payment and returns error result', async () => {
  1660. const { addPaymentToOrder } = await shopClient.query<
  1661. CodegenShop.AddPaymentToOrderMutation,
  1662. CodegenShop.AddPaymentToOrderMutationVariables
  1663. >(ADD_PAYMENT, {
  1664. input: {
  1665. method: testErrorPaymentMethod.code,
  1666. metadata: {
  1667. foo: 'bar',
  1668. },
  1669. },
  1670. });
  1671. orderResultGuard.assertErrorResult(addPaymentToOrder);
  1672. expect(addPaymentToOrder.message).toBe('The payment failed');
  1673. expect(addPaymentToOrder.errorCode).toBe(ErrorCode.PAYMENT_FAILED_ERROR);
  1674. expect((addPaymentToOrder as any).paymentErrorMessage).toBe('Something went horribly wrong');
  1675. const result =
  1676. await shopClient.query<CodegenShop.GetActiveOrderPaymentsQuery>(
  1677. GET_ACTIVE_ORDER_PAYMENTS,
  1678. );
  1679. const payment = result.activeOrder!.payments![1];
  1680. expect(result.activeOrder!.payments!.length).toBe(2);
  1681. expect(payment.method).toBe(testErrorPaymentMethod.code);
  1682. expect(payment.state).toBe('Error');
  1683. expect(payment.errorMessage).toBe('Something went horribly wrong');
  1684. });
  1685. it('adds a successful payment and transitions Order state', async () => {
  1686. const { addPaymentToOrder } = await shopClient.query<
  1687. CodegenShop.AddPaymentToOrderMutation,
  1688. CodegenShop.AddPaymentToOrderMutationVariables
  1689. >(ADD_PAYMENT, {
  1690. input: {
  1691. method: testSuccessfulPaymentMethod.code,
  1692. metadata: {
  1693. baz: 'quux',
  1694. },
  1695. },
  1696. });
  1697. orderResultGuard.assertSuccess(addPaymentToOrder);
  1698. const payment = addPaymentToOrder.payments!.find(p => p.transactionId === '12345')!;
  1699. expect(addPaymentToOrder.state).toBe('PaymentSettled');
  1700. expect(addPaymentToOrder.active).toBe(false);
  1701. expect(addPaymentToOrder.payments!.length).toBe(3);
  1702. expect(payment.method).toBe(testSuccessfulPaymentMethod.code);
  1703. expect(payment.state).toBe('Settled');
  1704. expect(payment.transactionId).toBe('12345');
  1705. expect(payment.metadata).toEqual({
  1706. public: { baz: 'quux' },
  1707. });
  1708. });
  1709. it('does not create new address when Customer already has address', async () => {
  1710. const { customer } = await adminClient.query<
  1711. Codegen.GetCustomerQuery,
  1712. Codegen.GetCustomerQueryVariables
  1713. >(GET_CUSTOMER, { id: customers[0].id });
  1714. expect(customer!.addresses!.length).toBe(1);
  1715. });
  1716. });
  1717. describe('orderByCode', () => {
  1718. describe('immediately after Order is placed', () => {
  1719. it('works when authenticated', async () => {
  1720. const result = await shopClient.query<
  1721. CodegenShop.GetOrderByCodeQuery,
  1722. CodegenShop.GetOrderByCodeQueryVariables
  1723. >(GET_ORDER_BY_CODE, {
  1724. code: activeOrder.code,
  1725. });
  1726. expect(result.orderByCode!.id).toBe(activeOrder.id);
  1727. });
  1728. it('works when anonymous', async () => {
  1729. await shopClient.asAnonymousUser();
  1730. const result = await shopClient.query<
  1731. CodegenShop.GetOrderByCodeQuery,
  1732. CodegenShop.GetOrderByCodeQueryVariables
  1733. >(GET_ORDER_BY_CODE, {
  1734. code: activeOrder.code,
  1735. });
  1736. expect(result.orderByCode!.id).toBe(activeOrder.id);
  1737. });
  1738. it(
  1739. "throws error for another user's Order",
  1740. assertThrowsWithMessage(async () => {
  1741. authenticatedUserEmailAddress = customers[1].emailAddress;
  1742. await shopClient.asUserWithCredentials(authenticatedUserEmailAddress, password);
  1743. return shopClient.query<
  1744. CodegenShop.GetOrderByCodeQuery,
  1745. CodegenShop.GetOrderByCodeQueryVariables
  1746. >(GET_ORDER_BY_CODE, {
  1747. code: activeOrder.code,
  1748. });
  1749. }, 'You are not currently authorized to perform this action'),
  1750. );
  1751. });
  1752. describe('3 hours after the Order has been placed', () => {
  1753. let dateNowMock: any;
  1754. beforeAll(() => {
  1755. // mock Date.now: add 3 hours
  1756. const nowIn3H = Date.now() + 3 * 3600 * 1000;
  1757. dateNowMock = vi.spyOn(global.Date, 'now').mockImplementation(() => nowIn3H);
  1758. });
  1759. it('still works when authenticated as owner', async () => {
  1760. authenticatedUserEmailAddress = customers[0].emailAddress;
  1761. await shopClient.asUserWithCredentials(authenticatedUserEmailAddress, password);
  1762. const result = await shopClient.query<
  1763. CodegenShop.GetOrderByCodeQuery,
  1764. CodegenShop.GetOrderByCodeQueryVariables
  1765. >(GET_ORDER_BY_CODE, {
  1766. code: activeOrder.code,
  1767. });
  1768. expect(result.orderByCode!.id).toBe(activeOrder.id);
  1769. });
  1770. it(
  1771. 'access denied when anonymous',
  1772. assertThrowsWithMessage(async () => {
  1773. await shopClient.asAnonymousUser();
  1774. await shopClient.query<
  1775. CodegenShop.GetOrderByCodeQuery,
  1776. CodegenShop.GetOrderByCodeQueryVariables
  1777. >(GET_ORDER_BY_CODE, {
  1778. code: activeOrder.code,
  1779. });
  1780. }, 'You are not currently authorized to perform this action'),
  1781. );
  1782. afterAll(() => {
  1783. // restore Date.now
  1784. dateNowMock.mockRestore();
  1785. });
  1786. });
  1787. });
  1788. });
  1789. describe('order merging', () => {
  1790. let customers: Codegen.GetCustomerListQuery['customers']['items'];
  1791. beforeAll(async () => {
  1792. const result = await adminClient.query<Codegen.GetCustomerListQuery>(GET_CUSTOMER_LIST);
  1793. customers = result.customers.items;
  1794. });
  1795. it('merges guest order with no existing order', async () => {
  1796. await shopClient.asAnonymousUser();
  1797. const { addItemToOrder } = await shopClient.query<
  1798. CodegenShop.AddItemToOrderMutation,
  1799. CodegenShop.AddItemToOrderMutationVariables
  1800. >(ADD_ITEM_TO_ORDER, {
  1801. productVariantId: 'T_1',
  1802. quantity: 1,
  1803. });
  1804. orderResultGuard.assertSuccess(addItemToOrder);
  1805. expect(addItemToOrder.lines.length).toBe(1);
  1806. expect(addItemToOrder.lines[0].productVariant.id).toBe('T_1');
  1807. await shopClient.query<Codegen.AttemptLoginMutation, Codegen.AttemptLoginMutationVariables>(
  1808. ATTEMPT_LOGIN,
  1809. {
  1810. username: customers[1].emailAddress,
  1811. password: 'test',
  1812. },
  1813. );
  1814. const { activeOrder } = await shopClient.query<CodegenShop.GetActiveOrderQuery>(GET_ACTIVE_ORDER);
  1815. expect(activeOrder!.lines.length).toBe(1);
  1816. expect(activeOrder!.lines[0].productVariant.id).toBe('T_1');
  1817. });
  1818. it('merges guest order with existing order', async () => {
  1819. await shopClient.asAnonymousUser();
  1820. const { addItemToOrder } = await shopClient.query<
  1821. CodegenShop.AddItemToOrderMutation,
  1822. CodegenShop.AddItemToOrderMutationVariables
  1823. >(ADD_ITEM_TO_ORDER, {
  1824. productVariantId: 'T_2',
  1825. quantity: 1,
  1826. });
  1827. orderResultGuard.assertSuccess(addItemToOrder);
  1828. expect(addItemToOrder.lines.length).toBe(1);
  1829. expect(addItemToOrder.lines[0].productVariant.id).toBe('T_2');
  1830. await shopClient.query<Codegen.AttemptLoginMutation, Codegen.AttemptLoginMutationVariables>(
  1831. ATTEMPT_LOGIN,
  1832. {
  1833. username: customers[1].emailAddress,
  1834. password: 'test',
  1835. },
  1836. );
  1837. const { activeOrder } = await shopClient.query<CodegenShop.GetActiveOrderQuery>(GET_ACTIVE_ORDER);
  1838. expect(activeOrder!.lines.length).toBe(2);
  1839. expect(activeOrder!.lines[0].productVariant.id).toBe('T_1');
  1840. expect(activeOrder!.lines[1].productVariant.id).toBe('T_2');
  1841. });
  1842. /**
  1843. * See https://github.com/vendurehq/vendure/issues/263
  1844. */
  1845. it('does not merge when logging in to a different account (issue #263)', async () => {
  1846. await shopClient.query<Codegen.AttemptLoginMutation, Codegen.AttemptLoginMutationVariables>(
  1847. ATTEMPT_LOGIN,
  1848. {
  1849. username: customers[2].emailAddress,
  1850. password: 'test',
  1851. },
  1852. );
  1853. const { activeOrder } = await shopClient.query<CodegenShop.GetActiveOrderQuery>(GET_ACTIVE_ORDER);
  1854. expect(activeOrder).toBeNull();
  1855. });
  1856. it('does not merge when logging back to other account (issue #263)', async () => {
  1857. const { addItemToOrder } = await shopClient.query<
  1858. CodegenShop.AddItemToOrderMutation,
  1859. CodegenShop.AddItemToOrderMutationVariables
  1860. >(ADD_ITEM_TO_ORDER, {
  1861. productVariantId: 'T_3',
  1862. quantity: 1,
  1863. });
  1864. await shopClient.query<Codegen.AttemptLoginMutation, Codegen.AttemptLoginMutationVariables>(
  1865. ATTEMPT_LOGIN,
  1866. {
  1867. username: customers[1].emailAddress,
  1868. password: 'test',
  1869. },
  1870. );
  1871. const { activeOrder } = await shopClient.query<CodegenShop.GetActiveOrderQuery>(GET_ACTIVE_ORDER);
  1872. expect(activeOrder!.lines.length).toBe(2);
  1873. expect(activeOrder!.lines[0].productVariant.id).toBe('T_1');
  1874. expect(activeOrder!.lines[1].productVariant.id).toBe('T_2');
  1875. });
  1876. // https://github.com/vendurehq/vendure/issues/754
  1877. it('handles merging when an existing order has OrderLines', async () => {
  1878. async function setShippingOnActiveOrder() {
  1879. await shopClient.query<
  1880. CodegenShop.SetShippingAddressMutation,
  1881. CodegenShop.SetShippingAddressMutationVariables
  1882. >(SET_SHIPPING_ADDRESS, {
  1883. input: {
  1884. streetLine1: '12 the street',
  1885. countryCode: 'US',
  1886. },
  1887. });
  1888. const { eligibleShippingMethods } =
  1889. await shopClient.query<CodegenShop.GetShippingMethodsQuery>(
  1890. GET_ELIGIBLE_SHIPPING_METHODS,
  1891. );
  1892. await shopClient.query<
  1893. CodegenShop.SetShippingMethodMutation,
  1894. CodegenShop.SetShippingMethodMutationVariables
  1895. >(SET_SHIPPING_METHOD, {
  1896. id: eligibleShippingMethods[1].id,
  1897. });
  1898. }
  1899. // Set up an existing order and add a ShippingLine
  1900. await shopClient.asUserWithCredentials(customers[2].emailAddress, 'test');
  1901. await shopClient.query<
  1902. CodegenShop.AddItemToOrderMutation,
  1903. CodegenShop.AddItemToOrderMutationVariables
  1904. >(ADD_ITEM_TO_ORDER, {
  1905. productVariantId: 'T_3',
  1906. quantity: 1,
  1907. });
  1908. await setShippingOnActiveOrder();
  1909. // Now start a new guest order
  1910. await shopClient.query(LOG_OUT);
  1911. await shopClient.query<
  1912. CodegenShop.AddItemToOrderMutation,
  1913. CodegenShop.AddItemToOrderMutationVariables
  1914. >(ADD_ITEM_TO_ORDER, {
  1915. productVariantId: 'T_4',
  1916. quantity: 1,
  1917. });
  1918. await setShippingOnActiveOrder();
  1919. // attempt to log in and merge the guest order with the existing order
  1920. const { login } = await shopClient.query<
  1921. Codegen.AttemptLoginMutation,
  1922. Codegen.AttemptLoginMutationVariables
  1923. >(ATTEMPT_LOGIN, {
  1924. username: customers[2].emailAddress,
  1925. password: 'test',
  1926. });
  1927. expect(login.identifier).toBe(customers[2].emailAddress);
  1928. });
  1929. });
  1930. describe('security of customer data', () => {
  1931. let customers: Codegen.GetCustomerListQuery['customers']['items'];
  1932. beforeAll(async () => {
  1933. const result = await adminClient.query<Codegen.GetCustomerListQuery>(GET_CUSTOMER_LIST);
  1934. customers = result.customers.items;
  1935. });
  1936. it('cannot setCustomOrder to existing non-guest Customer', async () => {
  1937. await shopClient.asAnonymousUser();
  1938. const { addItemToOrder } = await shopClient.query<
  1939. CodegenShop.AddItemToOrderMutation,
  1940. CodegenShop.AddItemToOrderMutationVariables
  1941. >(ADD_ITEM_TO_ORDER, {
  1942. productVariantId: 'T_1',
  1943. quantity: 1,
  1944. });
  1945. const { setCustomerForOrder } = await shopClient.query<
  1946. CodegenShop.SetCustomerForOrderMutation,
  1947. CodegenShop.SetCustomerForOrderMutationVariables
  1948. >(SET_CUSTOMER, {
  1949. input: {
  1950. emailAddress: customers[0].emailAddress,
  1951. firstName: 'Evil',
  1952. lastName: 'Hacker',
  1953. },
  1954. });
  1955. orderResultGuard.assertErrorResult(setCustomerForOrder);
  1956. expect(setCustomerForOrder.message).toBe('The email address is not available.');
  1957. expect(setCustomerForOrder.errorCode).toBe(ErrorCode.EMAIL_ADDRESS_CONFLICT_ERROR);
  1958. const { customer } = await adminClient.query<
  1959. Codegen.GetCustomerQuery,
  1960. Codegen.GetCustomerQueryVariables
  1961. >(GET_CUSTOMER, {
  1962. id: customers[0].id,
  1963. });
  1964. expect(customer!.firstName).not.toBe('Evil');
  1965. expect(customer!.lastName).not.toBe('Hacker');
  1966. });
  1967. it('guest cannot access Addresses of guest customer', async () => {
  1968. await shopClient.asAnonymousUser();
  1969. const { addItemToOrder } = await shopClient.query<
  1970. CodegenShop.AddItemToOrderMutation,
  1971. CodegenShop.AddItemToOrderMutationVariables
  1972. >(ADD_ITEM_TO_ORDER, {
  1973. productVariantId: 'T_1',
  1974. quantity: 1,
  1975. });
  1976. await shopClient.query<
  1977. CodegenShop.SetCustomerForOrderMutation,
  1978. CodegenShop.SetCustomerForOrderMutationVariables
  1979. >(SET_CUSTOMER, {
  1980. input: {
  1981. emailAddress: 'test@test.com',
  1982. firstName: 'Evil',
  1983. lastName: 'Hacker',
  1984. },
  1985. });
  1986. const { activeOrder } =
  1987. await shopClient.query<CodegenShop.GetCustomerAddressesQuery>(GET_ACTIVE_ORDER_ADDRESSES);
  1988. expect(activeOrder!.customer!.addresses).toEqual([]);
  1989. });
  1990. it('guest cannot access Orders of guest customer', async () => {
  1991. await shopClient.asAnonymousUser();
  1992. const { addItemToOrder } = await shopClient.query<
  1993. CodegenShop.AddItemToOrderMutation,
  1994. CodegenShop.AddItemToOrderMutationVariables
  1995. >(ADD_ITEM_TO_ORDER, {
  1996. productVariantId: 'T_1',
  1997. quantity: 1,
  1998. });
  1999. await shopClient.query<
  2000. CodegenShop.SetCustomerForOrderMutation,
  2001. CodegenShop.SetCustomerForOrderMutationVariables
  2002. >(SET_CUSTOMER, {
  2003. input: {
  2004. emailAddress: 'test@test.com',
  2005. firstName: 'Evil',
  2006. lastName: 'Hacker',
  2007. },
  2008. });
  2009. const { activeOrder } =
  2010. await shopClient.query<CodegenShop.GetCustomerOrdersQuery>(GET_ACTIVE_ORDER_ORDERS);
  2011. expect(activeOrder!.customer!.orders.items).toEqual([]);
  2012. });
  2013. });
  2014. describe('order custom fields', () => {
  2015. it('custom fields added to type', async () => {
  2016. await shopClient.asAnonymousUser();
  2017. await shopClient.query<
  2018. CodegenShop.AddItemToOrderMutation,
  2019. CodegenShop.AddItemToOrderMutationVariables
  2020. >(ADD_ITEM_TO_ORDER, {
  2021. productVariantId: 'T_1',
  2022. quantity: 1,
  2023. });
  2024. const { activeOrder } = await shopClient.query(GET_ORDER_CUSTOM_FIELDS);
  2025. expect(activeOrder?.customFields).toEqual({
  2026. orderImage: null,
  2027. giftWrap: false,
  2028. });
  2029. });
  2030. it('setting order custom fields', async () => {
  2031. const { setOrderCustomFields } = await shopClient.query(SET_ORDER_CUSTOM_FIELDS, {
  2032. input: {
  2033. customFields: { giftWrap: true, orderImageId: 'T_1' },
  2034. },
  2035. });
  2036. expect(setOrderCustomFields?.customFields).toEqual({
  2037. orderImage: { id: 'T_1' },
  2038. giftWrap: true,
  2039. });
  2040. const { activeOrder } = await shopClient.query(GET_ORDER_CUSTOM_FIELDS);
  2041. expect(activeOrder?.customFields).toEqual({
  2042. orderImage: { id: 'T_1' },
  2043. giftWrap: true,
  2044. });
  2045. });
  2046. });
  2047. describe('remove all order lines', () => {
  2048. beforeAll(async () => {
  2049. await shopClient.asAnonymousUser();
  2050. await shopClient.query<
  2051. CodegenShop.AddItemToOrderMutation,
  2052. CodegenShop.AddItemToOrderMutationVariables
  2053. >(ADD_ITEM_TO_ORDER, {
  2054. productVariantId: 'T_1',
  2055. quantity: 1,
  2056. });
  2057. await shopClient.query<
  2058. CodegenShop.AddItemToOrderMutation,
  2059. CodegenShop.AddItemToOrderMutationVariables
  2060. >(ADD_ITEM_TO_ORDER, {
  2061. productVariantId: 'T_2',
  2062. quantity: 3,
  2063. });
  2064. });
  2065. it('should remove all order lines', async () => {
  2066. const { removeAllOrderLines } = await shopClient.query<
  2067. CodegenShop.RemoveAllOrderLinesMutation,
  2068. CodegenShop.RemoveAllOrderLinesMutationVariables
  2069. >(REMOVE_ALL_ORDER_LINES);
  2070. orderResultGuard.assertSuccess(removeAllOrderLines);
  2071. expect(removeAllOrderLines?.total).toBe(0);
  2072. expect(removeAllOrderLines?.lines.length).toBe(0);
  2073. });
  2074. });
  2075. describe('validation of product variant availability', () => {
  2076. const bonsaiProductId = 'T_20';
  2077. const bonsaiVariantId = 'T_34';
  2078. beforeAll(async () => {
  2079. await shopClient.asAnonymousUser();
  2080. });
  2081. it(
  2082. 'addItemToOrder errors when product is disabled',
  2083. assertThrowsWithMessage(async () => {
  2084. await adminClient.query<
  2085. Codegen.UpdateProductMutation,
  2086. Codegen.UpdateProductMutationVariables
  2087. >(UPDATE_PRODUCT, {
  2088. input: {
  2089. id: bonsaiProductId,
  2090. enabled: false,
  2091. },
  2092. });
  2093. await shopClient.query<
  2094. CodegenShop.AddItemToOrderMutation,
  2095. CodegenShop.AddItemToOrderMutationVariables
  2096. >(ADD_ITEM_TO_ORDER, {
  2097. productVariantId: bonsaiVariantId,
  2098. quantity: 1,
  2099. });
  2100. }, 'No ProductVariant with the id "34" could be found'),
  2101. );
  2102. it(
  2103. 'addItemToOrder errors when product variant is disabled',
  2104. assertThrowsWithMessage(async () => {
  2105. await adminClient.query<
  2106. Codegen.UpdateProductMutation,
  2107. Codegen.UpdateProductMutationVariables
  2108. >(UPDATE_PRODUCT, {
  2109. input: {
  2110. id: bonsaiProductId,
  2111. enabled: true,
  2112. },
  2113. });
  2114. await adminClient.query<
  2115. Codegen.UpdateProductVariantsMutation,
  2116. Codegen.UpdateProductVariantsMutationVariables
  2117. >(UPDATE_PRODUCT_VARIANTS, {
  2118. input: [
  2119. {
  2120. id: bonsaiVariantId,
  2121. enabled: false,
  2122. },
  2123. ],
  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. it(
  2135. 'addItemToOrder errors when product is deleted',
  2136. assertThrowsWithMessage(async () => {
  2137. await adminClient.query<
  2138. Codegen.DeleteProductMutation,
  2139. Codegen.DeleteProductMutationVariables
  2140. >(DELETE_PRODUCT, {
  2141. id: bonsaiProductId,
  2142. });
  2143. await shopClient.query<
  2144. CodegenShop.AddItemToOrderMutation,
  2145. CodegenShop.AddItemToOrderMutationVariables
  2146. >(ADD_ITEM_TO_ORDER, {
  2147. productVariantId: bonsaiVariantId,
  2148. quantity: 1,
  2149. });
  2150. }, 'No ProductVariant with the id "34" could be found'),
  2151. );
  2152. it(
  2153. 'addItemToOrder errors when product variant is deleted',
  2154. assertThrowsWithMessage(async () => {
  2155. await adminClient.query<
  2156. Codegen.DeleteProductVariantMutation,
  2157. Codegen.DeleteProductVariantMutationVariables
  2158. >(DELETE_PRODUCT_VARIANT, {
  2159. id: bonsaiVariantId,
  2160. });
  2161. await shopClient.query<
  2162. CodegenShop.AddItemToOrderMutation,
  2163. CodegenShop.AddItemToOrderMutationVariables
  2164. >(ADD_ITEM_TO_ORDER, {
  2165. productVariantId: bonsaiVariantId,
  2166. quantity: 1,
  2167. });
  2168. }, 'No ProductVariant with the id "34" could be found'),
  2169. );
  2170. let orderWithDeletedProductVariantId: string;
  2171. it('errors when transitioning to ArrangingPayment with deleted variant', async () => {
  2172. const orchidProductId = 'T_19';
  2173. const orchidVariantId = 'T_33';
  2174. await shopClient.asUserWithCredentials('marques.sawayn@hotmail.com', 'test');
  2175. const { addItemToOrder } = await shopClient.query<
  2176. CodegenShop.AddItemToOrderMutation,
  2177. CodegenShop.AddItemToOrderMutationVariables
  2178. >(ADD_ITEM_TO_ORDER, {
  2179. productVariantId: orchidVariantId,
  2180. quantity: 1,
  2181. });
  2182. orderResultGuard.assertSuccess(addItemToOrder);
  2183. orderWithDeletedProductVariantId = addItemToOrder.id;
  2184. await adminClient.query<Codegen.DeleteProductMutation, Codegen.DeleteProductMutationVariables>(
  2185. DELETE_PRODUCT,
  2186. {
  2187. id: orchidProductId,
  2188. },
  2189. );
  2190. const { transitionOrderToState } = await shopClient.query<
  2191. CodegenShop.TransitionToStateMutation,
  2192. CodegenShop.TransitionToStateMutationVariables
  2193. >(TRANSITION_TO_STATE, {
  2194. state: 'ArrangingPayment',
  2195. });
  2196. orderResultGuard.assertErrorResult(transitionOrderToState);
  2197. expect(transitionOrderToState!.transitionError).toBe(
  2198. 'Cannot transition to "ArrangingPayment" because the Order contains ProductVariants which are no longer available',
  2199. );
  2200. expect(transitionOrderToState!.errorCode).toBe(ErrorCode.ORDER_STATE_TRANSITION_ERROR);
  2201. });
  2202. // https://github.com/vendurehq/vendure/issues/1567
  2203. it('allows transitioning to Cancelled with deleted variant', async () => {
  2204. const { cancelOrder } = await adminClient.query<
  2205. Codegen.CancelOrderMutation,
  2206. Codegen.CancelOrderMutationVariables
  2207. >(CANCEL_ORDER, {
  2208. input: {
  2209. orderId: orderWithDeletedProductVariantId,
  2210. },
  2211. });
  2212. orderResultGuard.assertSuccess(cancelOrder);
  2213. expect(cancelOrder.state).toBe('Cancelled');
  2214. });
  2215. });
  2216. // https://github.com/vendurehq/vendure/issues/1195
  2217. describe('shipping method invalidation', () => {
  2218. let GBShippingMethodId: string;
  2219. let ATShippingMethodId: string;
  2220. beforeAll(async () => {
  2221. // First we will remove all ShippingMethods and set up 2 specialized ones
  2222. const { shippingMethods } =
  2223. await adminClient.query<Codegen.GetShippingMethodListQuery>(GET_SHIPPING_METHOD_LIST);
  2224. for (const method of shippingMethods.items) {
  2225. await adminClient.query<
  2226. Codegen.DeleteShippingMethodMutation,
  2227. Codegen.DeleteShippingMethodMutationVariables
  2228. >(DELETE_SHIPPING_METHOD, {
  2229. id: method.id,
  2230. });
  2231. }
  2232. function createCountryCodeShippingMethodInput(countryCode: string): CreateShippingMethodInput {
  2233. return {
  2234. code: `${countryCode}-shipping`,
  2235. translations: [
  2236. { languageCode: LanguageCode.en, name: `${countryCode} shipping`, description: '' },
  2237. ],
  2238. fulfillmentHandler: manualFulfillmentHandler.code,
  2239. checker: {
  2240. code: countryCodeShippingEligibilityChecker.code,
  2241. arguments: [{ name: 'countryCode', value: countryCode }],
  2242. },
  2243. calculator: {
  2244. code: defaultShippingCalculator.code,
  2245. arguments: [
  2246. { name: 'rate', value: '1000' },
  2247. { name: 'taxRate', value: '0' },
  2248. { name: 'includesTax', value: 'auto' },
  2249. ],
  2250. },
  2251. };
  2252. }
  2253. // Now create 2 shipping methods, valid only for a single country
  2254. const result1 = await adminClient.query<
  2255. Codegen.CreateShippingMethodMutation,
  2256. Codegen.CreateShippingMethodMutationVariables
  2257. >(CREATE_SHIPPING_METHOD, {
  2258. input: createCountryCodeShippingMethodInput('GB'),
  2259. });
  2260. GBShippingMethodId = result1.createShippingMethod.id;
  2261. const result2 = await adminClient.query<
  2262. Codegen.CreateShippingMethodMutation,
  2263. Codegen.CreateShippingMethodMutationVariables
  2264. >(CREATE_SHIPPING_METHOD, {
  2265. input: createCountryCodeShippingMethodInput('AT'),
  2266. });
  2267. ATShippingMethodId = result2.createShippingMethod.id;
  2268. // Now create an order to GB and set the GB shipping method
  2269. const { addItemToOrder } = await shopClient.query<
  2270. CodegenShop.AddItemToOrderMutation,
  2271. CodegenShop.AddItemToOrderMutationVariables
  2272. >(ADD_ITEM_TO_ORDER, {
  2273. productVariantId: 'T_1',
  2274. quantity: 1,
  2275. });
  2276. await shopClient.query<
  2277. CodegenShop.SetCustomerForOrderMutation,
  2278. CodegenShop.SetCustomerForOrderMutationVariables
  2279. >(SET_CUSTOMER, {
  2280. input: {
  2281. emailAddress: 'test-2@test.com',
  2282. firstName: 'Test',
  2283. lastName: 'Person 2',
  2284. },
  2285. });
  2286. await shopClient.query<
  2287. CodegenShop.SetShippingAddressMutation,
  2288. CodegenShop.SetShippingAddressMutationVariables
  2289. >(SET_SHIPPING_ADDRESS, {
  2290. input: {
  2291. streetLine1: '12 the street',
  2292. countryCode: 'GB',
  2293. },
  2294. });
  2295. await shopClient.query<
  2296. CodegenShop.SetShippingMethodMutation,
  2297. CodegenShop.SetShippingMethodMutationVariables
  2298. >(SET_SHIPPING_METHOD, {
  2299. id: GBShippingMethodId,
  2300. });
  2301. });
  2302. it('if selected method no longer eligible, next best is set automatically', async () => {
  2303. const result1 = await shopClient.query<CodegenShop.GetActiveOrderQuery>(GET_ACTIVE_ORDER);
  2304. expect(result1.activeOrder?.shippingLines[0].shippingMethod.id).toBe(GBShippingMethodId);
  2305. await shopClient.query<
  2306. CodegenShop.SetShippingAddressMutation,
  2307. CodegenShop.SetShippingAddressMutationVariables
  2308. >(SET_SHIPPING_ADDRESS, {
  2309. input: {
  2310. streetLine1: '12 the street',
  2311. countryCode: 'AT',
  2312. },
  2313. });
  2314. const result2 = await shopClient.query<CodegenShop.GetActiveOrderQuery>(GET_ACTIVE_ORDER);
  2315. expect(result2.activeOrder?.shippingLines[0].shippingMethod.id).toBe(ATShippingMethodId);
  2316. });
  2317. it('if no method is eligible, shipping lines are cleared', async () => {
  2318. await shopClient.query<
  2319. CodegenShop.SetShippingAddressMutation,
  2320. CodegenShop.SetShippingAddressMutationVariables
  2321. >(SET_SHIPPING_ADDRESS, {
  2322. input: {
  2323. streetLine1: '12 the street',
  2324. countryCode: 'US',
  2325. },
  2326. });
  2327. const result = await shopClient.query<CodegenShop.GetActiveOrderQuery>(GET_ACTIVE_ORDER);
  2328. expect(result.activeOrder?.shippingLines).toEqual([]);
  2329. });
  2330. // https://github.com/vendurehq/vendure/issues/1441
  2331. it('shipping methods are re-evaluated when all OrderLines are removed', async () => {
  2332. const { createShippingMethod } = await adminClient.query<
  2333. CreateShippingMethod.Mutation,
  2334. CreateShippingMethod.Variables
  2335. >(CREATE_SHIPPING_METHOD, {
  2336. input: {
  2337. code: 'min-price-shipping',
  2338. translations: [
  2339. { languageCode: LanguageCode.en, name: 'min price shipping', description: '' },
  2340. ],
  2341. fulfillmentHandler: manualFulfillmentHandler.code,
  2342. checker: {
  2343. code: defaultShippingEligibilityChecker.code,
  2344. arguments: [{ name: 'orderMinimum', value: '100' }],
  2345. },
  2346. calculator: {
  2347. code: defaultShippingCalculator.code,
  2348. arguments: [
  2349. { name: 'rate', value: '1000' },
  2350. { name: 'taxRate', value: '0' },
  2351. { name: 'includesTax', value: 'auto' },
  2352. ],
  2353. },
  2354. },
  2355. });
  2356. const minPriceShippingMethodId = createShippingMethod.id;
  2357. await shopClient.query<SetShippingMethod.Mutation, SetShippingMethod.Variables>(
  2358. SET_SHIPPING_METHOD,
  2359. {
  2360. id: minPriceShippingMethodId,
  2361. },
  2362. );
  2363. const result1 = await shopClient.query<CodegenShop.GetActiveOrderQuery>(GET_ACTIVE_ORDER);
  2364. expect(result1.activeOrder?.shippingLines[0].shippingMethod.id).toBe(minPriceShippingMethodId);
  2365. const { removeAllOrderLines } = await shopClient.query<
  2366. CodegenShop.RemoveAllOrderLinesMutation,
  2367. CodegenShop.RemoveAllOrderLinesMutationVariables
  2368. >(REMOVE_ALL_ORDER_LINES);
  2369. orderResultGuard.assertSuccess(removeAllOrderLines);
  2370. expect(removeAllOrderLines.shippingLines.length).toBe(0);
  2371. expect(removeAllOrderLines.shippingWithTax).toBe(0);
  2372. });
  2373. });
  2374. describe('edge cases', () => {
  2375. it('calling setShippingMethod and setBillingMethod in parallel does not introduce race condition', async () => {
  2376. const shippingAddress: CreateAddressInput = {
  2377. fullName: 'name',
  2378. company: 'company',
  2379. streetLine1: '12 Shipping Street',
  2380. streetLine2: null,
  2381. city: 'foo',
  2382. province: 'bar',
  2383. postalCode: '123456',
  2384. countryCode: 'US',
  2385. phoneNumber: '4444444',
  2386. };
  2387. const billingAddress: CreateAddressInput = {
  2388. fullName: 'name',
  2389. company: 'company',
  2390. streetLine1: '22 Billing Avenue',
  2391. streetLine2: null,
  2392. city: 'foo',
  2393. province: 'bar',
  2394. postalCode: '123456',
  2395. countryCode: 'US',
  2396. phoneNumber: '4444444',
  2397. };
  2398. await Promise.all([
  2399. shopClient.query<
  2400. CodegenShop.SetBillingAddressMutation,
  2401. CodegenShop.SetBillingAddressMutationVariables
  2402. >(SET_BILLING_ADDRESS, {
  2403. input: billingAddress,
  2404. }),
  2405. shopClient.query<
  2406. CodegenShop.SetShippingAddressMutation,
  2407. CodegenShop.SetShippingAddressMutationVariables
  2408. >(SET_SHIPPING_ADDRESS, {
  2409. input: shippingAddress,
  2410. }),
  2411. ]);
  2412. const { activeOrder } = await shopClient.query(gql`
  2413. query {
  2414. activeOrder {
  2415. shippingAddress {
  2416. ...OrderAddress
  2417. }
  2418. billingAddress {
  2419. ...OrderAddress
  2420. }
  2421. }
  2422. }
  2423. fragment OrderAddress on OrderAddress {
  2424. fullName
  2425. company
  2426. streetLine1
  2427. streetLine2
  2428. city
  2429. province
  2430. postalCode
  2431. countryCode
  2432. phoneNumber
  2433. }
  2434. `);
  2435. expect(activeOrder.shippingAddress).toEqual(shippingAddress);
  2436. expect(activeOrder.billingAddress).toEqual(billingAddress);
  2437. });
  2438. // https://github.com/vendurehq/vendure/issues/2548
  2439. it('hydrating Order in the ShippingEligibilityChecker does not break order modification', async () => {
  2440. // First we'll create a ShippingMethod that uses the hydrating checker
  2441. await adminClient.query(CreateShippingMethodDocument, {
  2442. input: {
  2443. code: 'hydrating-checker',
  2444. translations: [
  2445. { languageCode: LanguageCode.en, name: 'hydrating checker', description: '' },
  2446. ],
  2447. fulfillmentHandler: manualFulfillmentHandler.code,
  2448. checker: {
  2449. code: hydratingShippingEligibilityChecker.code,
  2450. arguments: [],
  2451. },
  2452. calculator: {
  2453. code: defaultShippingCalculator.code,
  2454. arguments: [
  2455. { name: 'rate', value: '1000' },
  2456. { name: 'taxRate', value: '0' },
  2457. { name: 'includesTax', value: 'auto' },
  2458. ],
  2459. },
  2460. },
  2461. });
  2462. await shopClient.asAnonymousUser();
  2463. await shopClient.query<
  2464. CodegenShop.AddItemToOrderMutation,
  2465. CodegenShop.AddItemToOrderMutationVariables
  2466. >(ADD_ITEM_TO_ORDER, {
  2467. productVariantId: 'T_1',
  2468. quantity: 1,
  2469. });
  2470. await shopClient.query<
  2471. CodegenShop.AddItemToOrderMutation,
  2472. CodegenShop.AddItemToOrderMutationVariables
  2473. >(ADD_ITEM_TO_ORDER, {
  2474. productVariantId: 'T_2',
  2475. quantity: 3,
  2476. });
  2477. const result1 = await shopClient.query<CodegenShop.GetActiveOrderQuery>(GET_ACTIVE_ORDER);
  2478. expect(result1.activeOrder?.lines.map(l => l.linePriceWithTax).sort()).toEqual([155880, 503640]);
  2479. expect(result1.activeOrder?.subTotalWithTax).toBe(659520);
  2480. // set the shipping method that uses the hydrating checker
  2481. const { eligibleShippingMethods } = await shopClient.query<CodegenShop.GetShippingMethodsQuery>(
  2482. GET_ELIGIBLE_SHIPPING_METHODS,
  2483. );
  2484. const { setOrderShippingMethod } = await shopClient.query<
  2485. CodegenShop.SetShippingMethodMutation,
  2486. CodegenShop.SetShippingMethodMutationVariables
  2487. >(SET_SHIPPING_METHOD, {
  2488. id: eligibleShippingMethods.find(m => m.code === 'hydrating-checker')!.id,
  2489. });
  2490. orderResultGuard.assertSuccess(setOrderShippingMethod);
  2491. // Remove an item from the order
  2492. const { removeOrderLine } = await shopClient.query(RemoveItemFromOrderDocument, {
  2493. orderLineId: result1.activeOrder!.lines[0].id,
  2494. });
  2495. orderResultGuard.assertSuccess(removeOrderLine);
  2496. expect(removeOrderLine.lines.length).toBe(1);
  2497. const result2 = await shopClient.query<CodegenShop.GetActiveOrderQuery>(GET_ACTIVE_ORDER);
  2498. expect(result2.activeOrder?.lines.map(l => l.linePriceWithTax).sort()).toEqual([503640]);
  2499. expect(result2.activeOrder?.subTotalWithTax).toBe(503640);
  2500. });
  2501. });
  2502. });
  2503. const GET_ORDER_CUSTOM_FIELDS = gql`
  2504. query GetOrderCustomFields {
  2505. activeOrder {
  2506. id
  2507. customFields {
  2508. giftWrap
  2509. orderImage {
  2510. id
  2511. }
  2512. }
  2513. }
  2514. }
  2515. `;
  2516. const SET_ORDER_CUSTOM_FIELDS = gql`
  2517. mutation SetOrderCustomFields($input: UpdateOrderInput!) {
  2518. setOrderCustomFields(input: $input) {
  2519. ... on Order {
  2520. id
  2521. customFields {
  2522. giftWrap
  2523. orderImage {
  2524. id
  2525. }
  2526. }
  2527. }
  2528. ... on ErrorResult {
  2529. errorCode
  2530. message
  2531. }
  2532. }
  2533. }
  2534. `;
  2535. export const LOG_OUT = gql`
  2536. mutation LogOut {
  2537. logout {
  2538. success
  2539. }
  2540. }
  2541. `;
  2542. export const ADD_ITEM_TO_ORDER_WITH_CUSTOM_FIELDS = gql`
  2543. mutation AddItemToOrderWithCustomFields(
  2544. $productVariantId: ID!
  2545. $quantity: Int!
  2546. $customFields: OrderLineCustomFieldsInput
  2547. ) {
  2548. addItemToOrder(
  2549. productVariantId: $productVariantId
  2550. quantity: $quantity
  2551. customFields: $customFields
  2552. ) {
  2553. ...UpdatedOrder
  2554. ... on ErrorResult {
  2555. errorCode
  2556. message
  2557. }
  2558. }
  2559. }
  2560. ${UPDATED_ORDER_FRAGMENT}
  2561. `;
  2562. export const ADD_MULTIPLE_ITEMS_TO_ORDER_WITH_CUSTOM_FIELDS = gql`
  2563. mutation AddMultipleItemsToOrderWithCustomFields($inputs: [AddItemInput!]!) {
  2564. addItemsToOrder(inputs: $inputs) {
  2565. order {
  2566. ...UpdatedOrder
  2567. lines {
  2568. id
  2569. quantity
  2570. productVariant {
  2571. id
  2572. }
  2573. customFields {
  2574. notes
  2575. }
  2576. }
  2577. }
  2578. errorResults {
  2579. ... on ErrorResult {
  2580. errorCode
  2581. message
  2582. }
  2583. }
  2584. }
  2585. }
  2586. ${UPDATED_ORDER_FRAGMENT}
  2587. `;
  2588. const ADJUST_ORDER_LINE_WITH_CUSTOM_FIELDS = gql`
  2589. mutation ($orderLineId: ID!, $quantity: Int!, $customFields: OrderLineCustomFieldsInput) {
  2590. adjustOrderLine(orderLineId: $orderLineId, quantity: $quantity, customFields: $customFields) {
  2591. ... on Order {
  2592. lines {
  2593. id
  2594. customFields {
  2595. notes
  2596. lineImage {
  2597. id
  2598. }
  2599. lineImages {
  2600. id
  2601. }
  2602. }
  2603. }
  2604. }
  2605. }
  2606. }
  2607. `;