1
0

shop-order.e2e-spec.ts 88 KB

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