Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 45 additions & 5 deletions .github/workflows/build_and_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,30 +84,36 @@ jobs:
image: mariadb:11.5
env:
MARIADB_ROOT_PASSWORD: password
MARIADB_DATABASE: vendure
MARIADB_USER: vendure
MARIADB_PASSWORD: password
ports:
- 3306
options: --health-cmd="mariadb-admin ping -h localhost -u vendure -ppassword" --health-interval=10s --health-timeout=5s --health-retries=3
mysql:
image: vendure/mysql-8-native-auth:latest
env:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: vendure
MYSQL_USER: vendure
MYSQL_PASSWORD: password
ports:
- 3306
options: --health-cmd="mysqladmin ping --silent" --health-interval=10s --health-timeout=20s --health-retries=10
options: --health-cmd="mysqladmin ping -h localhost -u vendure -ppassword" --health-interval=10s --health-timeout=20s --health-retries=10
postgres:
image: postgres:16
env:
POSTGRES_USER: vendure
POSTGRES_PASSWORD: password
ports:
- 5432
options: --health-cmd=pg_isready --health-interval=10s --health-timeout=5s --health-retries=3
options: --health-cmd="pg_isready -U vendure" --health-interval=10s --health-timeout=5s --health-retries=10
elastic:
image: docker.elastic.co/elasticsearch/elasticsearch:7.1.1
env:
discovery.type: single-node
bootstrap.memory_lock: true
ES_JAVA_OPTS: -Xms512m -Xmx512m
bootstrap.memory_lock: "false"
ES_JAVA_OPTS: -Xms256m -Xmx256m
# Elasticsearch will force read-only mode when total available disk space is less than 5%. Since we will
# be running on a shared Azure instance with 84GB SSD, we easily go below 5% available even when there are still
# > 3GB free. So we set this value to an absolute one rather than a percentage to prevent all the Elasticsearch
Expand All @@ -117,7 +123,7 @@ jobs:
cluster.routing.allocation.disk.watermark.flood_stage: 100mb
ports:
- 9200
options: --health-cmd="curl --silent --fail localhost:9200/_cluster/health" --health-interval=10s --health-timeout=5s --health-retries=3
options: --health-cmd="curl --silent --fail localhost:9200/_cluster/health" --health-interval=10s --health-timeout=10s --health-retries=5
redis:
image: redis:7.4.1
ports:
Expand All @@ -139,6 +145,40 @@ jobs:
npm install --os=linux --cpu=x64 sharp
- name: Build
run: npx lerna run ci
- name: Wait for services to be ready
run: |
echo "Waiting for required services..."
# Wait for redis
for i in {1..60}; do
if nc -z localhost $E2E_REDIS_PORT; then break; fi
sleep 1
done

# Wait for DB ports depending on matrix value
if [ "$DB" = "postgres" ]; then
for i in {1..60}; do
if nc -z localhost $E2E_POSTGRES_PORT; then break; fi
sleep 1
done
elif [ "$DB" = "mariadb" ]; then
for i in {1..60}; do
if nc -z localhost $E2E_MARIADB_PORT; then break; fi
sleep 1
done
elif [ "$DB" = "mysql" ]; then
for i in {1..60}; do
if nc -z localhost $E2E_MYSQL_PORT; then break; fi
sleep 1
done
fi

# Wait for Elasticsearch
for i in {1..60}; do
if curl --silent --fail http://localhost:$E2E_ELASTIC_PORT/_cluster/health >/dev/null 2>&1; then break; fi
sleep 1
done

echo "Services appear reachable."
- name: e2e tests
env:
E2E_MYSQL_PORT: ${{ job.services.mysql.ports['3306'] }}
Expand Down
4 changes: 2 additions & 2 deletions e2e-common/test-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ function getDbConfig(): DataSourceOptions {
type: 'mariadb',
host: '127.0.0.1',
port: process.env.CI ? +(process.env.E2E_MARIADB_PORT || 3306) : 3306,
username: 'root',
username: 'vendure',
password: 'password',
};
case 'mysql':
Expand All @@ -113,7 +113,7 @@ function getDbConfig(): DataSourceOptions {
type: 'mysql',
host: '127.0.0.1',
port: process.env.CI ? +(process.env.E2E_MYSQL_PORT || 3306) : 3306,
username: 'root',
username: 'vendure',
password: 'password',
};
case 'sqljs':
Expand Down
65 changes: 65 additions & 0 deletions packages/core/e2e/race-condition.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { createTestEnvironment } from '@vendure/testing';
import * as path from 'path';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';

import { initialData } from '../../e2e-common/e2e-initial-data';
import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../e2e-common/test-config';

describe('Order race conditions', () => {
const { server, adminClient, shopClient } = createTestEnvironment(testConfig);

beforeAll(async () => {
await server.init({
initialData,
productsCsvPath: path.join(__dirname, '../../e2e-common/fixtures/e2e-products-full.csv'),
customerCount: 1,
});
await shopClient.asUserWithCredentials('test@vendure.io', 'test');
}, TEST_SETUP_TIMEOUT_MS);

afterAll(async () => {
await server.destroy();
});

it('handles parallel modifications to the order correctly', async () => {
const ADD_ITEM_TO_ORDER = `
mutation AddItemToOrder($productVariantId: ID!, $quantity: Int!) {
addItemToOrder(productVariantId: $productVariantId, quantity: $quantity) {
... on Order {
id
totalQuantity
total
}
... on ErrorResult {
errorCode
message
}
}
}
`;

const variantId = 'T_1'; // Laptop 13 inch 8GB
const quantityPerRequest = 1;
const concurrency = 5;

// Executing 5 Simulataneous requests to add an item
const promises: Array<Promise<any>> = [];
for (let i = 0; i < concurrency; i++) {
promises.push(
shopClient.query(ADD_ITEM_TO_ORDER, {
productVariantId: variantId,
quantity: quantityPerRequest,
}),
);
}

await Promise.all(promises);

const { activeOrder } = await shopClient.query(`
query GetActiveOrder { activeOrder { totalQuantity } }
`);

// Si hay condición de carrera, el total será menor a 5 (algunas escrituras se sobrescribieron)
expect(activeOrder.totalQuantity).toBe(concurrency * quantityPerRequest);
});
});
Loading
Loading