shop-order.e2e-spec.ts 103 KB

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