parallel-transactions.e2e-spec.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. import gql from 'graphql-tag';
  2. import path from 'path';
  3. import { initialData } from '../../../e2e-common/e2e-initial-data';
  4. import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
  5. import { createTestEnvironment } from '../../testing/lib/create-test-environment';
  6. import { SlowMutationPlugin } from './fixtures/test-plugins/slow-mutation-plugin';
  7. import {
  8. AddOptionGroupToProduct,
  9. CreateProduct,
  10. CreateProductOptionGroup,
  11. CreateProductVariants,
  12. LanguageCode,
  13. } from './graphql/generated-e2e-admin-types';
  14. import {
  15. ADD_OPTION_GROUP_TO_PRODUCT,
  16. CREATE_PRODUCT,
  17. CREATE_PRODUCT_OPTION_GROUP,
  18. CREATE_PRODUCT_VARIANTS,
  19. } from './graphql/shared-definitions';
  20. describe('Parallel transactions', () => {
  21. const { server, adminClient, shopClient } = createTestEnvironment({
  22. ...testConfig(),
  23. plugins: [SlowMutationPlugin],
  24. });
  25. beforeAll(async () => {
  26. await server.init({
  27. initialData,
  28. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-minimal.csv'),
  29. customerCount: 2,
  30. });
  31. await adminClient.asSuperAdmin();
  32. }, TEST_SETUP_TIMEOUT_MS);
  33. afterAll(async () => {
  34. await server.destroy();
  35. });
  36. it('does not fail on many concurrent, slow transactions', async () => {
  37. const CONCURRENCY_LIMIT = 20;
  38. const slowMutations = Array.from({ length: CONCURRENCY_LIMIT }).map(i =>
  39. adminClient.query(SLOW_MUTATION, { delay: 50 }),
  40. );
  41. const result = await Promise.all(slowMutations);
  42. expect(result).toEqual(Array.from({ length: CONCURRENCY_LIMIT }).map(() => ({ slowMutation: true })));
  43. }, 100000);
  44. it('does not fail on attempted deadlock', async () => {
  45. const CONCURRENCY_LIMIT = 4;
  46. const slowMutations = Array.from({ length: CONCURRENCY_LIMIT }).map(i =>
  47. adminClient.query(ATTEMPT_DEADLOCK),
  48. );
  49. const result = await Promise.all(slowMutations);
  50. expect(result).toEqual(
  51. Array.from({ length: CONCURRENCY_LIMIT }).map(() => ({ attemptDeadlock: true })),
  52. );
  53. }, 100000);
  54. // A real-world error-case originally reported in https://github.com/vendure-ecommerce/vendure/issues/527
  55. it('does not deadlock on concurrent creating ProductVariants', async () => {
  56. const CONCURRENCY_LIMIT = 4;
  57. const { createProduct } = await adminClient.query<CreateProduct.Mutation, CreateProduct.Variables>(
  58. CREATE_PRODUCT,
  59. {
  60. input: {
  61. translations: [
  62. { languageCode: LanguageCode.en, name: 'Test', slug: 'test', description: 'test' },
  63. ],
  64. },
  65. },
  66. );
  67. const sizes = Array.from({ length: CONCURRENCY_LIMIT }).map(i => `size-${i}`);
  68. const { createProductOptionGroup } = await adminClient.query<
  69. CreateProductOptionGroup.Mutation,
  70. CreateProductOptionGroup.Variables
  71. >(CREATE_PRODUCT_OPTION_GROUP, {
  72. input: {
  73. code: 'size',
  74. options: sizes.map(size => ({
  75. code: size,
  76. translations: [{ languageCode: LanguageCode.en, name: size }],
  77. })),
  78. translations: [{ languageCode: LanguageCode.en, name: 'size' }],
  79. },
  80. });
  81. await adminClient.query<AddOptionGroupToProduct.Mutation, AddOptionGroupToProduct.Variables>(
  82. ADD_OPTION_GROUP_TO_PRODUCT,
  83. {
  84. productId: createProduct.id,
  85. optionGroupId: createProductOptionGroup.id,
  86. },
  87. );
  88. const createVariantMutations = createProductOptionGroup.options
  89. .filter((_, index) => index < CONCURRENCY_LIMIT)
  90. .map((option, i) => {
  91. return adminClient.query<CreateProductVariants.Mutation, CreateProductVariants.Variables>(
  92. CREATE_PRODUCT_VARIANTS,
  93. {
  94. input: [
  95. {
  96. sku: `VARIANT-${i}`,
  97. productId: createProduct.id,
  98. optionIds: [option.id],
  99. translations: [{ languageCode: LanguageCode.en, name: `Variant ${i}` }],
  100. price: 1000,
  101. taxCategoryId: 'T_1',
  102. facetValueIds: ['T_1', 'T_2'],
  103. featuredAssetId: 'T_1',
  104. assetIds: ['T_1'],
  105. },
  106. ],
  107. },
  108. );
  109. });
  110. const results = await Promise.all(createVariantMutations);
  111. expect(results.length).toBe(CONCURRENCY_LIMIT);
  112. }, 100000);
  113. });
  114. const SLOW_MUTATION = gql`
  115. mutation SlowMutation($delay: Int!) {
  116. slowMutation(delay: $delay)
  117. }
  118. `;
  119. const ATTEMPT_DEADLOCK = gql`
  120. mutation AttemptDeadlock {
  121. attemptDeadlock
  122. }
  123. `;