money-strategy.e2e-spec.ts 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. import { DefaultMoneyStrategy, Logger, mergeConfig, MoneyStrategy, VendurePlugin } from '@vendure/core';
  2. import { createErrorResultGuard, createTestEnvironment, ErrorResultGuard } from '@vendure/testing';
  3. import path from 'path';
  4. import { ColumnOptions } from 'typeorm';
  5. import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
  6. import { initialData } from '../../../e2e-common/e2e-initial-data';
  7. import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
  8. import * as Codegen from './graphql/generated-e2e-admin-types';
  9. import { SortOrder } from './graphql/generated-e2e-admin-types';
  10. import * as CodegenShop from './graphql/generated-e2e-shop-types';
  11. import { AddItemToOrderMutation, AddItemToOrderMutationVariables } from './graphql/generated-e2e-shop-types';
  12. import { GET_PRODUCT_VARIANT_LIST } from './graphql/shared-definitions';
  13. import { ADD_ITEM_TO_ORDER } from './graphql/shop-definitions';
  14. /* eslint-disable @typescript-eslint/no-non-null-assertion */
  15. const orderGuard: ErrorResultGuard<CodegenShop.UpdatedOrderFragment> = createErrorResultGuard(
  16. input => !!input.total,
  17. );
  18. class CustomMoneyStrategy implements MoneyStrategy {
  19. static transformerFromSpy = vi.fn();
  20. readonly moneyColumnOptions: ColumnOptions = {
  21. type: 'bigint',
  22. transformer: {
  23. to: (entityValue: number) => {
  24. return entityValue;
  25. },
  26. from: (databaseValue: string): number => {
  27. CustomMoneyStrategy.transformerFromSpy(databaseValue);
  28. if (databaseValue == null) {
  29. return databaseValue;
  30. }
  31. const intVal = Number.parseInt(databaseValue, 10);
  32. if (!Number.isSafeInteger(intVal)) {
  33. Logger.warn(`Monetary value ${databaseValue} is not a safe integer!`);
  34. }
  35. if (Number.isNaN(intVal)) {
  36. Logger.warn(`Monetary value ${databaseValue} is not a number!`);
  37. }
  38. return intVal;
  39. },
  40. },
  41. };
  42. round(value: number, quantity = 1): number {
  43. return Math.round(value * quantity);
  44. }
  45. }
  46. @VendurePlugin({
  47. configuration: config => {
  48. config.entityOptions.moneyStrategy = new CustomMoneyStrategy();
  49. return config;
  50. },
  51. })
  52. class MyPlugin {}
  53. describe('Custom MoneyStrategy', () => {
  54. const { server, adminClient, shopClient } = createTestEnvironment(
  55. mergeConfig(testConfig(), {
  56. plugins: [MyPlugin],
  57. }),
  58. );
  59. let cheapVariantId: string;
  60. let expensiveVariantId: string;
  61. beforeAll(async () => {
  62. await server.init({
  63. initialData,
  64. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-money-handling.csv'),
  65. customerCount: 1,
  66. });
  67. await adminClient.asSuperAdmin();
  68. }, TEST_SETUP_TIMEOUT_MS);
  69. afterAll(async () => {
  70. await server.destroy();
  71. });
  72. it('check initial prices', async () => {
  73. expect(CustomMoneyStrategy.transformerFromSpy).toHaveBeenCalledTimes(0);
  74. const { productVariants } = await adminClient.query<
  75. Codegen.GetProductVariantListQuery,
  76. Codegen.GetProductVariantListQueryVariables
  77. >(GET_PRODUCT_VARIANT_LIST, {
  78. options: {
  79. sort: {
  80. price: SortOrder.ASC,
  81. },
  82. },
  83. });
  84. expect(productVariants.items[0].price).toBe(31);
  85. expect(productVariants.items[0].priceWithTax).toBe(37);
  86. expect(productVariants.items[1].price).toBe(9_999_999_00);
  87. expect(productVariants.items[1].priceWithTax).toBe(11_999_998_80);
  88. cheapVariantId = productVariants.items[0].id;
  89. expensiveVariantId = productVariants.items[1].id;
  90. expect(CustomMoneyStrategy.transformerFromSpy).toHaveBeenCalledTimes(6);
  91. });
  92. // https://github.com/vendure-ecommerce/vendure/issues/838
  93. it('can handle totals over 21 million', async () => {
  94. await shopClient.asAnonymousUser();
  95. const { addItemToOrder } = await shopClient.query<
  96. AddItemToOrderMutation,
  97. AddItemToOrderMutationVariables
  98. >(ADD_ITEM_TO_ORDER, {
  99. productVariantId: expensiveVariantId,
  100. quantity: 2,
  101. });
  102. orderGuard.assertSuccess(addItemToOrder);
  103. expect(addItemToOrder.lines[0].linePrice).toBe(1_999_999_800);
  104. expect(addItemToOrder.lines[0].linePriceWithTax).toBe(2_399_999_760);
  105. });
  106. // https://github.com/vendure-ecommerce/vendure/issues/1835
  107. // 31 * 1.2 = 37.2
  108. // Math.round(37.2 * 10) =372
  109. it('tax calculation rounds at the unit level', async () => {
  110. await shopClient.asAnonymousUser();
  111. const { addItemToOrder } = await shopClient.query<
  112. AddItemToOrderMutation,
  113. AddItemToOrderMutationVariables
  114. >(ADD_ITEM_TO_ORDER, {
  115. productVariantId: cheapVariantId,
  116. quantity: 10,
  117. });
  118. orderGuard.assertSuccess(addItemToOrder);
  119. expect(addItemToOrder.lines[0].linePrice).toBe(310);
  120. expect(addItemToOrder.lines[0].linePriceWithTax).toBe(372);
  121. });
  122. });