parallel-transactions.e2e-spec.ts 5.0 KB

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