parallel-transactions.e2e-spec.ts 5.0 KB

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