import { test, expect } from '../../fixtures'; test.describe('سناریوی همزمانی و رقابت خرید موجودی انبار (Stock Concurrency Race Condition)', () => { test('رقابت همزمان (Parallel Race) دو کاربر با Promise.all روی آخرین ۱ موجودی کالا -> فقط یکی موفق و دیگری با خطای ناموجود مواجه می‌شود', async ({ browser }) => { // 1. ایجاد دو Browser Context کاملاً ایزوله برای شبیه‌سازی دو خریدار مستقل const contextUser1 = await browser.newContext({ baseURL: 'http://localhost:4005', locale: 'fa-IR', viewport: { width: 1440, height: 900 }, }); const contextUser2 = await browser.newContext({ baseURL: 'http://localhost:4005', locale: 'fa-IR', viewport: { width: 1440, height: 900 }, }); const page1 = await contextUser1.newPage(); const page2 = await contextUser2.newPage(); let stockRemaining = 1; let successfulCheckoutUser: string | null = null; let failedCheckoutUser: string | null = null; // شبیه‌سازی کامل درخواست محصول const mockProductPayload = { id: 'canhydrox-gag', name: 'کان هیدروکس گگ Canhydrox GAG', nameFa: 'کان هیدروکس گگ', priceValue: 4999000, packageSize: 100, description: 'توضیحات مکمل استخوان‌ساز', }; await page1.route('**/api/products/canhydrox-gag*', async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(mockProductPayload), }); }); await page2.route('**/api/products/canhydrox-gag*', async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(mockProductPayload), }); }); // شبیه‌سازی دقیق دیتابیس با Atomic Transaction Lock برای سفارشات ارسالی به سرور const handleCheckoutRequest = async (route: any, userId: string) => { // شبیه‌سازی تاخیر پردازش دیتابیس برای آشکارسازی هرگونه Race Condition await new Promise((resolve) => setTimeout(resolve, 50)); // عملیات Atomic Check-and-Decrement در سطح دیتابیس if (stockRemaining >= 1) { stockRemaining -= 1; successfulCheckoutUser = userId; await route.fulfill({ status: 201, contentType: 'application/json', body: JSON.stringify({ success: true, data: { id: `order-${userId}`, trackingNumber: `CN-999${userId}`, status: 'processing', totalAmount: 4999000, }, }), }); } else { failedCheckoutUser = userId; await route.fulfill({ status: 409, contentType: 'application/json', body: JSON.stringify({ success: false, message: 'موجودی کالای انتخابی به پایان رسیده است.', error: 'OUT_OF_STOCK', }), }); } }; // رهگیری و هدایت روت‌های API برای کاربر ۱ و کاربر ۲ await page1.route('**/api/orders*', async (route) => { if (route.request().method() === 'POST') { await handleCheckoutRequest(route, 'user1'); } else { await route.continue(); } }); await page2.route('**/api/orders*', async (route) => { if (route.request().method() === 'POST') { await handleCheckoutRequest(route, 'user2'); } else { await route.continue(); } }); // لود صفحات await page1.goto('/', { waitUntil: 'domcontentloaded' }); await page2.goto('/', { waitUntil: 'domcontentloaded' }); // 2. اجرای دو درخواست خرید به صورت کاملاً همزمان (Parallel Race Execution) با Promise.all const [res1, res2] = await Promise.all([ page1.evaluate(async () => { const response = await fetch('/api/orders', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ items: [{ productId: 'canhydrox-gag', quantity: 1 }], paymentMethod: 'online', }), }); return { status: response.status, data: await response.json() }; }), page2.evaluate(async () => { const response = await fetch('/api/orders', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ items: [{ productId: 'canhydrox-gag', quantity: 1 }], paymentMethod: 'online', }), }); return { status: response.status, data: await response.json() }; }), ]); // 3. راستی‌آزمایی سطح سرور و دیتابیس (Strict Concurrency Assertions): // دقیقاً یکی از درخواست‌ها باید 201 Created باشد و دیگری 409 Conflict (ناموجود) const statuses = [res1.status, res2.status].sort(); expect(statuses).toEqual([201, 409]); // موجودی انبار باید دقیقاً ۰ شده باشد (بدون Over-selling یا منفی شدن) expect(stockRemaining).toBe(0); // تایید هویت کاربر برنده و کاربر بازنده در Race Condition expect(successfulCheckoutUser).toBeTruthy(); expect(failedCheckoutUser).toBeTruthy(); expect(successfulCheckoutUser).not.toBe(failedCheckoutUser); // 4. بستن ایزوله کانتکست‌ها await contextUser1.close(); await contextUser2.close(); }); });