Browse Source

fix(core): Fix error on concurrent calls to new channel

Fixes #834
Michael Bromley 4 years ago
parent
commit
fad900636d

+ 35 - 1
packages/core/e2e/customer-channel.e2e-spec.ts

@@ -3,7 +3,7 @@ import { createTestEnvironment, E2E_DEFAULT_CHANNEL_TOKEN } from '@vendure/testi
 import path from 'path';
 
 import { initialData } from '../../../e2e-common/e2e-initial-data';
-import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-config';
+import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
 
 import {
     AddCustomersToGroup,
@@ -286,6 +286,40 @@ describe('ChannelAware Customers', () => {
             expect(customers.totalItems).toBe(4);
             expect(customers.items.map(customer => customer.emailAddress)).toContain('john.doe.2@test.com');
         });
+
+        // https://github.com/vendure-ecommerce/vendure/issues/834
+        it('handles concurrent assignments to a new channel', async () => {
+            const THIRD_CHANNEL_TOKEN = 'third_channel_token';
+            await adminClient.query<CreateChannel.Mutation, CreateChannel.Variables>(CREATE_CHANNEL, {
+                input: {
+                    code: 'third-channel',
+                    token: THIRD_CHANNEL_TOKEN,
+                    defaultLanguageCode: LanguageCode.en,
+                    currencyCode: CurrencyCode.GBP,
+                    pricesIncludeTax: true,
+                    defaultShippingZoneId: 'T_1',
+                    defaultTaxZoneId: 'T_1',
+                },
+            });
+
+            await shopClient.asUserWithCredentials(secondCustomer.emailAddress, 'test');
+            shopClient.setChannelToken(THIRD_CHANNEL_TOKEN);
+
+            try {
+                await Promise.all([shopClient.query<Me.Query>(ME), shopClient.query<Me.Query>(ME)]);
+            } catch (e) {
+                fail('Threw: ' + e.message);
+            }
+
+            adminClient.setChannelToken(THIRD_CHANNEL_TOKEN);
+            const { customers } = await adminClient.query<GetCustomerList.Query, GetCustomerList.Variables>(
+                GET_CUSTOMER_LIST,
+            );
+            expect(customers.totalItems).toBe(1);
+            expect(customers.items.map(customer => customer.emailAddress)).toContain(
+                secondCustomer.emailAddress,
+            );
+        });
     });
 
     describe('Customergroup manipulation', () => {

+ 17 - 3
packages/core/src/api/middleware/auth-guard.ts

@@ -105,9 +105,23 @@ export class AuthGuard implements CanActivate {
                 // To avoid assigning the customer to the active channel on every request,
                 // it is only done on the first request and whenever the channel changes
                 if (customer) {
-                    await this.channelService.assignToChannels(requestContext, Customer, customer.id, [
-                        requestContext.channelId,
-                    ]);
+                    try {
+                        await this.channelService.assignToChannels(requestContext, Customer, customer.id, [
+                            requestContext.channelId,
+                        ]);
+                    } catch (e) {
+                        const isDuplicateError =
+                            e.code === 'ER_DUP_ENTRY' /* mySQL/MariaDB */ ||
+                            e.code === '23505'; /* postgres */
+                        if (isDuplicateError) {
+                            // For a duplicate error, this means that concurrent requests have resulted in attempting to
+                            // assign the Customer to the channel more than once. In this case we can safely ignore the
+                            // error as the Customer was successfully assigned in the earlier call.
+                            // See https://github.com/vendure-ecommerce/vendure/issues/834
+                        } else {
+                            throw e;
+                        }
+                    }
                 }
             }
             return true;