shipping-method.e2e-spec.ts 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  1. /* eslint-disable @typescript-eslint/no-non-null-assertion */
  2. import {
  3. defaultShippingCalculator,
  4. defaultShippingEligibilityChecker,
  5. ShippingCalculator,
  6. } from '@vendure/core';
  7. import { createTestEnvironment } from '@vendure/testing';
  8. import gql from 'graphql-tag';
  9. import path from 'path';
  10. import { afterAll, beforeAll, describe, expect, it } from 'vitest';
  11. import { initialData } from '../../../e2e-common/e2e-initial-data';
  12. import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
  13. import { manualFulfillmentHandler } from '../src/config/fulfillment/manual-fulfillment-handler';
  14. import { SHIPPING_METHOD_FRAGMENT } from './graphql/fragments';
  15. import * as Codegen from './graphql/generated-e2e-admin-types';
  16. import { DeletionResult, LanguageCode } from './graphql/generated-e2e-admin-types';
  17. import {
  18. CREATE_SHIPPING_METHOD,
  19. DELETE_SHIPPING_METHOD,
  20. GET_SHIPPING_METHOD_LIST,
  21. UPDATE_SHIPPING_METHOD,
  22. } from './graphql/shared-definitions';
  23. const TEST_METADATA = {
  24. foo: 'bar',
  25. baz: [1, 2, 3],
  26. };
  27. const calculatorWithMetadata = new ShippingCalculator({
  28. code: 'calculator-with-metadata',
  29. description: [{ languageCode: LanguageCode.en, value: 'Has metadata' }],
  30. args: {},
  31. calculate: () => {
  32. return {
  33. price: 100,
  34. priceIncludesTax: true,
  35. taxRate: 0,
  36. metadata: TEST_METADATA,
  37. };
  38. },
  39. });
  40. describe('ShippingMethod resolver', () => {
  41. const { server, adminClient, shopClient } = createTestEnvironment({
  42. ...testConfig(),
  43. shippingOptions: {
  44. shippingEligibilityCheckers: [defaultShippingEligibilityChecker],
  45. shippingCalculators: [defaultShippingCalculator, calculatorWithMetadata],
  46. },
  47. });
  48. beforeAll(async () => {
  49. await server.init({
  50. initialData,
  51. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-full.csv'),
  52. customerCount: 1,
  53. });
  54. await adminClient.asSuperAdmin();
  55. }, TEST_SETUP_TIMEOUT_MS);
  56. afterAll(async () => {
  57. await server.destroy();
  58. });
  59. it('shippingEligibilityCheckers', async () => {
  60. const { shippingEligibilityCheckers } = await adminClient.query<Codegen.GetEligibilityCheckersQuery>(
  61. GET_ELIGIBILITY_CHECKERS,
  62. );
  63. expect(shippingEligibilityCheckers).toEqual([
  64. {
  65. args: [
  66. {
  67. description: 'Order is eligible only if its total is greater or equal to this value',
  68. label: 'Minimum order value',
  69. name: 'orderMinimum',
  70. type: 'int',
  71. ui: {
  72. component: 'currency-form-input',
  73. },
  74. },
  75. ],
  76. code: 'default-shipping-eligibility-checker',
  77. description: 'Default Shipping Eligibility Checker',
  78. },
  79. ]);
  80. });
  81. it('shippingCalculators', async () => {
  82. const { shippingCalculators } = await adminClient.query<Codegen.GetCalculatorsQuery>(GET_CALCULATORS);
  83. expect(shippingCalculators).toEqual([
  84. {
  85. args: [
  86. {
  87. ui: {
  88. component: 'currency-form-input',
  89. },
  90. description: null,
  91. label: 'Shipping price',
  92. name: 'rate',
  93. type: 'int',
  94. },
  95. {
  96. label: 'Price includes tax',
  97. name: 'includesTax',
  98. type: 'string',
  99. description: null,
  100. ui: {
  101. component: 'select-form-input',
  102. options: [
  103. {
  104. label: [{ languageCode: LanguageCode.en, value: 'Includes tax' }],
  105. value: 'include',
  106. },
  107. {
  108. label: [{ languageCode: LanguageCode.en, value: 'Excludes tax' }],
  109. value: 'exclude',
  110. },
  111. {
  112. label: [
  113. { languageCode: LanguageCode.en, value: 'Auto (based on Channel)' },
  114. ],
  115. value: 'auto',
  116. },
  117. ],
  118. },
  119. },
  120. {
  121. ui: {
  122. component: 'number-form-input',
  123. suffix: '%',
  124. },
  125. description: null,
  126. label: 'Tax rate',
  127. name: 'taxRate',
  128. type: 'int',
  129. },
  130. ],
  131. code: 'default-shipping-calculator',
  132. description: 'Default Flat-Rate Shipping Calculator',
  133. },
  134. {
  135. args: [],
  136. code: 'calculator-with-metadata',
  137. description: 'Has metadata',
  138. },
  139. ]);
  140. });
  141. it('shippingMethods', async () => {
  142. const { shippingMethods } = await adminClient.query<Codegen.GetShippingMethodListQuery>(
  143. GET_SHIPPING_METHOD_LIST,
  144. );
  145. expect(shippingMethods.totalItems).toEqual(2);
  146. expect(shippingMethods.items[0].code).toBe('standard-shipping');
  147. expect(shippingMethods.items[1].code).toBe('express-shipping');
  148. });
  149. it('shippingMethod', async () => {
  150. const { shippingMethod } = await adminClient.query<
  151. Codegen.GetShippingMethodQuery,
  152. Codegen.GetShippingMethodQueryVariables
  153. >(GET_SHIPPING_METHOD, {
  154. id: 'T_1',
  155. });
  156. expect(shippingMethod!.code).toBe('standard-shipping');
  157. });
  158. it('createShippingMethod', async () => {
  159. const { createShippingMethod } = await adminClient.query<
  160. Codegen.CreateShippingMethodMutation,
  161. Codegen.CreateShippingMethodMutationVariables
  162. >(CREATE_SHIPPING_METHOD, {
  163. input: {
  164. code: 'new-method',
  165. fulfillmentHandler: manualFulfillmentHandler.code,
  166. checker: {
  167. code: defaultShippingEligibilityChecker.code,
  168. arguments: [
  169. {
  170. name: 'orderMinimum',
  171. value: '0',
  172. },
  173. ],
  174. },
  175. calculator: {
  176. code: calculatorWithMetadata.code,
  177. arguments: [],
  178. },
  179. translations: [{ languageCode: LanguageCode.en, name: 'new method', description: '' }],
  180. },
  181. });
  182. expect(createShippingMethod).toEqual({
  183. id: 'T_3',
  184. code: 'new-method',
  185. name: 'new method',
  186. description: '',
  187. calculator: {
  188. code: 'calculator-with-metadata',
  189. args: [],
  190. },
  191. checker: {
  192. code: 'default-shipping-eligibility-checker',
  193. args: [
  194. {
  195. name: 'orderMinimum',
  196. value: '0',
  197. },
  198. ],
  199. },
  200. });
  201. });
  202. it('testShippingMethod', async () => {
  203. const { testShippingMethod } = await adminClient.query<
  204. Codegen.TestShippingMethodQuery,
  205. Codegen.TestShippingMethodQueryVariables
  206. >(TEST_SHIPPING_METHOD, {
  207. input: {
  208. calculator: {
  209. code: calculatorWithMetadata.code,
  210. arguments: [],
  211. },
  212. checker: {
  213. code: defaultShippingEligibilityChecker.code,
  214. arguments: [
  215. {
  216. name: 'orderMinimum',
  217. value: '0',
  218. },
  219. ],
  220. },
  221. lines: [{ productVariantId: 'T_1', quantity: 1 }],
  222. shippingAddress: {
  223. streetLine1: '',
  224. countryCode: 'GB',
  225. },
  226. },
  227. });
  228. expect(testShippingMethod).toEqual({
  229. eligible: true,
  230. quote: {
  231. price: 100,
  232. priceWithTax: 100,
  233. metadata: TEST_METADATA,
  234. },
  235. });
  236. });
  237. it('testEligibleShippingMethods', async () => {
  238. const { testEligibleShippingMethods } = await adminClient.query<
  239. Codegen.TestEligibleMethodsQuery,
  240. Codegen.TestEligibleMethodsQueryVariables
  241. >(TEST_ELIGIBLE_SHIPPING_METHODS, {
  242. input: {
  243. lines: [{ productVariantId: 'T_1', quantity: 1 }],
  244. shippingAddress: {
  245. streetLine1: '',
  246. countryCode: 'GB',
  247. },
  248. },
  249. });
  250. expect(testEligibleShippingMethods).toEqual([
  251. {
  252. id: 'T_3',
  253. name: 'new method',
  254. description: '',
  255. price: 100,
  256. priceWithTax: 100,
  257. metadata: TEST_METADATA,
  258. },
  259. {
  260. id: 'T_1',
  261. name: 'Standard Shipping',
  262. description: '',
  263. price: 500,
  264. priceWithTax: 500,
  265. metadata: null,
  266. },
  267. {
  268. id: 'T_2',
  269. name: 'Express Shipping',
  270. description: '',
  271. price: 1000,
  272. priceWithTax: 1000,
  273. metadata: null,
  274. },
  275. ]);
  276. });
  277. it('updateShippingMethod', async () => {
  278. const { updateShippingMethod } = await adminClient.query<
  279. Codegen.UpdateShippingMethodMutation,
  280. Codegen.UpdateShippingMethodMutationVariables
  281. >(UPDATE_SHIPPING_METHOD, {
  282. input: {
  283. id: 'T_3',
  284. translations: [{ languageCode: LanguageCode.en, name: 'changed method', description: '' }],
  285. },
  286. });
  287. expect(updateShippingMethod.name).toBe('changed method');
  288. });
  289. it('deleteShippingMethod', async () => {
  290. const listResult1 = await adminClient.query<Codegen.GetShippingMethodListQuery>(
  291. GET_SHIPPING_METHOD_LIST,
  292. );
  293. expect(listResult1.shippingMethods.items.map(i => i.id)).toEqual(['T_1', 'T_2', 'T_3']);
  294. const { deleteShippingMethod } = await adminClient.query<
  295. Codegen.DeleteShippingMethodMutation,
  296. Codegen.DeleteShippingMethodMutationVariables
  297. >(DELETE_SHIPPING_METHOD, {
  298. id: 'T_3',
  299. });
  300. expect(deleteShippingMethod).toEqual({
  301. result: DeletionResult.DELETED,
  302. message: null,
  303. });
  304. const listResult2 = await adminClient.query<Codegen.GetShippingMethodListQuery>(
  305. GET_SHIPPING_METHOD_LIST,
  306. );
  307. expect(listResult2.shippingMethods.items.map(i => i.id)).toEqual(['T_1', 'T_2']);
  308. });
  309. describe('argument ordering', () => {
  310. it('createShippingMethod corrects order of arguments', async () => {
  311. const { createShippingMethod } = await adminClient.query<
  312. Codegen.CreateShippingMethodMutation,
  313. Codegen.CreateShippingMethodMutationVariables
  314. >(CREATE_SHIPPING_METHOD, {
  315. input: {
  316. code: 'new-method',
  317. fulfillmentHandler: manualFulfillmentHandler.code,
  318. checker: {
  319. code: defaultShippingEligibilityChecker.code,
  320. arguments: [
  321. {
  322. name: 'orderMinimum',
  323. value: '0',
  324. },
  325. ],
  326. },
  327. calculator: {
  328. code: defaultShippingCalculator.code,
  329. arguments: [
  330. { name: 'rate', value: '500' },
  331. { name: 'taxRate', value: '20' },
  332. { name: 'includesTax', value: 'include' },
  333. ],
  334. },
  335. translations: [{ languageCode: LanguageCode.en, name: 'new method', description: '' }],
  336. },
  337. });
  338. expect(createShippingMethod.calculator).toEqual({
  339. code: defaultShippingCalculator.code,
  340. args: [
  341. { name: 'rate', value: '500' },
  342. { name: 'includesTax', value: 'include' },
  343. { name: 'taxRate', value: '20' },
  344. ],
  345. });
  346. });
  347. it('updateShippingMethod corrects order of arguments', async () => {
  348. const { updateShippingMethod } = await adminClient.query<
  349. Codegen.UpdateShippingMethodMutation,
  350. Codegen.UpdateShippingMethodMutationVariables
  351. >(UPDATE_SHIPPING_METHOD, {
  352. input: {
  353. id: 'T_4',
  354. translations: [],
  355. calculator: {
  356. code: defaultShippingCalculator.code,
  357. arguments: [
  358. { name: 'rate', value: '500' },
  359. { name: 'taxRate', value: '20' },
  360. { name: 'includesTax', value: 'include' },
  361. ],
  362. },
  363. },
  364. });
  365. expect(updateShippingMethod.calculator).toEqual({
  366. code: defaultShippingCalculator.code,
  367. args: [
  368. { name: 'rate', value: '500' },
  369. { name: 'includesTax', value: 'include' },
  370. { name: 'taxRate', value: '20' },
  371. ],
  372. });
  373. });
  374. it('get shippingMethod preserves correct ordering', async () => {
  375. const { shippingMethod } = await adminClient.query<
  376. Codegen.GetShippingMethodQuery,
  377. Codegen.GetShippingMethodQueryVariables
  378. >(GET_SHIPPING_METHOD, {
  379. id: 'T_4',
  380. });
  381. expect(shippingMethod?.calculator.args).toEqual([
  382. { name: 'rate', value: '500' },
  383. { name: 'includesTax', value: 'include' },
  384. { name: 'taxRate', value: '20' },
  385. ]);
  386. });
  387. it('testShippingMethod corrects order of arguments', async () => {
  388. const { testShippingMethod } = await adminClient.query<
  389. Codegen.TestShippingMethodQuery,
  390. Codegen.TestShippingMethodQueryVariables
  391. >(TEST_SHIPPING_METHOD, {
  392. input: {
  393. calculator: {
  394. code: defaultShippingCalculator.code,
  395. arguments: [
  396. { name: 'rate', value: '500' },
  397. { name: 'taxRate', value: '0' },
  398. { name: 'includesTax', value: 'include' },
  399. ],
  400. },
  401. checker: {
  402. code: defaultShippingEligibilityChecker.code,
  403. arguments: [
  404. {
  405. name: 'orderMinimum',
  406. value: '0',
  407. },
  408. ],
  409. },
  410. lines: [{ productVariantId: 'T_1', quantity: 1 }],
  411. shippingAddress: {
  412. streetLine1: '',
  413. countryCode: 'GB',
  414. },
  415. },
  416. });
  417. expect(testShippingMethod).toEqual({
  418. eligible: true,
  419. quote: {
  420. metadata: null,
  421. price: 500,
  422. priceWithTax: 500,
  423. },
  424. });
  425. });
  426. });
  427. });
  428. const GET_SHIPPING_METHOD = gql`
  429. query GetShippingMethod($id: ID!) {
  430. shippingMethod(id: $id) {
  431. ...ShippingMethod
  432. }
  433. }
  434. ${SHIPPING_METHOD_FRAGMENT}
  435. `;
  436. const GET_ELIGIBILITY_CHECKERS = gql`
  437. query GetEligibilityCheckers {
  438. shippingEligibilityCheckers {
  439. code
  440. description
  441. args {
  442. name
  443. type
  444. description
  445. label
  446. ui
  447. }
  448. }
  449. }
  450. `;
  451. const GET_CALCULATORS = gql`
  452. query GetCalculators {
  453. shippingCalculators {
  454. code
  455. description
  456. args {
  457. name
  458. type
  459. description
  460. label
  461. ui
  462. }
  463. }
  464. }
  465. `;
  466. const TEST_SHIPPING_METHOD = gql`
  467. query TestShippingMethod($input: TestShippingMethodInput!) {
  468. testShippingMethod(input: $input) {
  469. eligible
  470. quote {
  471. price
  472. priceWithTax
  473. metadata
  474. }
  475. }
  476. }
  477. `;
  478. export const TEST_ELIGIBLE_SHIPPING_METHODS = gql`
  479. query TestEligibleMethods($input: TestEligibleShippingMethodsInput!) {
  480. testEligibleShippingMethods(input: $input) {
  481. id
  482. name
  483. description
  484. price
  485. priceWithTax
  486. metadata
  487. }
  488. }
  489. `;