test(e2e): complete cross-platform coverage and hardening for security, admin, and cart suites

This commit is contained in:
parsa aghaei 2026-08-25 17:48:33 +03:30
parent e6018f3a37
commit 770665cfc0
9 changed files with 459 additions and 22 deletions

View File

@ -1,3 +1,4 @@
import process from 'node:process';
import { NestFactory } from '@nestjs/core'; import { NestFactory } from '@nestjs/core';
import { AppModule } from '../src/app.module'; import { AppModule } from '../src/app.module';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';

View File

@ -1,3 +1,4 @@
import process from 'node:process';
import { PrismaClient } from '@prisma/client'; import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient(); const prisma = new PrismaClient();

View File

@ -243,6 +243,7 @@ export default function ContactFormClient() {
</div> </div>
<button <button
data-testid="contact-form-submit-btn"
type="submit" type="submit"
disabled={loading} disabled={loading}
className="w-full bg-canina-blue text-white py-4 rounded-2xl font-black flex items-center justify-center gap-2 hover:shadow-xl hover:shadow-canina-blue/20 transition-all disabled:opacity-50" className="w-full bg-canina-blue text-white py-4 rounded-2xl font-black flex items-center justify-center gap-2 hover:shadow-xl hover:shadow-canina-blue/20 transition-all disabled:opacity-50"

396
package-lock.json generated
View File

@ -9,8 +9,10 @@
"version": "1.0.0", "version": "1.0.0",
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.62.1", "@playwright/test": "^1.62.1",
"@types/node": "^26.3.0",
"concurrently": "^8.2.2", "concurrently": "^8.2.2",
"dotenv": "^17.4.2" "dotenv": "^17.4.2",
"typescript": "^7.0.2"
} }
}, },
"node_modules/@babel/runtime": { "node_modules/@babel/runtime": {
@ -39,6 +41,356 @@
"node": ">=20" "node": ">=20"
} }
}, },
"node_modules/@types/node": {
"version": "26.3.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.3.0.tgz",
"integrity": "sha512-L3fgrnchriRC2ExBflb8j4uZZURHZfQsmQeyVzhjcHW4kkwVyo8/0h1B2MVzMTrYUJYu6G7EWs14hW/L9putqw==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~8.3.0"
}
},
"node_modules/@typescript/typescript-aix-ppc64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz",
"integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-darwin-arm64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz",
"integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-darwin-x64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz",
"integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==",
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-freebsd-arm64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz",
"integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-freebsd-x64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz",
"integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==",
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-linux-arm": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz",
"integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==",
"cpu": [
"arm"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-linux-arm64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz",
"integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-linux-loong64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz",
"integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==",
"cpu": [
"loong64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-linux-mips64el": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz",
"integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-linux-ppc64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz",
"integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-linux-riscv64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz",
"integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-linux-s390x": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz",
"integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==",
"cpu": [
"s390x"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-linux-x64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz",
"integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==",
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-netbsd-arm64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz",
"integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-netbsd-x64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz",
"integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==",
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-openbsd-arm64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz",
"integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-openbsd-x64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz",
"integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==",
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-sunos-x64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz",
"integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==",
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-win32-arm64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz",
"integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/@typescript/typescript-win32-x64": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz",
"integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==",
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=16.20.0"
}
},
"node_modules/ansi-regex": { "node_modules/ansi-regex": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
@ -389,6 +741,48 @@
"dev": true, "dev": true,
"license": "0BSD" "license": "0BSD"
}, },
"node_modules/typescript": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz",
"integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc"
},
"engines": {
"node": ">=16.20.0"
},
"optionalDependencies": {
"@typescript/typescript-aix-ppc64": "7.0.2",
"@typescript/typescript-darwin-arm64": "7.0.2",
"@typescript/typescript-darwin-x64": "7.0.2",
"@typescript/typescript-freebsd-arm64": "7.0.2",
"@typescript/typescript-freebsd-x64": "7.0.2",
"@typescript/typescript-linux-arm": "7.0.2",
"@typescript/typescript-linux-arm64": "7.0.2",
"@typescript/typescript-linux-loong64": "7.0.2",
"@typescript/typescript-linux-mips64el": "7.0.2",
"@typescript/typescript-linux-ppc64": "7.0.2",
"@typescript/typescript-linux-riscv64": "7.0.2",
"@typescript/typescript-linux-s390x": "7.0.2",
"@typescript/typescript-linux-x64": "7.0.2",
"@typescript/typescript-netbsd-arm64": "7.0.2",
"@typescript/typescript-netbsd-x64": "7.0.2",
"@typescript/typescript-openbsd-arm64": "7.0.2",
"@typescript/typescript-openbsd-x64": "7.0.2",
"@typescript/typescript-sunos-x64": "7.0.2",
"@typescript/typescript-win32-arm64": "7.0.2",
"@typescript/typescript-win32-x64": "7.0.2"
}
},
"node_modules/undici-types": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz",
"integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==",
"dev": true,
"license": "MIT"
},
"node_modules/wrap-ansi": { "node_modules/wrap-ansi": {
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",

View File

@ -19,7 +19,9 @@
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.62.1", "@playwright/test": "^1.62.1",
"@types/node": "^26.3.0",
"concurrently": "^8.2.2", "concurrently": "^8.2.2",
"dotenv": "^17.4.2" "dotenv": "^17.4.2",
"typescript": "^7.0.2"
} }
} }

View File

@ -81,6 +81,10 @@
10. `tests/e2e/security/xss-ratelimit.spec.ts`: تست مقاومت امنیتی در برابر حملات XSS و محدودیت نرخ ارسال پیامک (Cooldown Timer). 10. `tests/e2e/security/xss-ratelimit.spec.ts`: تست مقاومت امنیتی در برابر حملات XSS و محدودیت نرخ ارسال پیامک (Cooldown Timer).
11. `tests/e2e/storefront/concurrency-stock.spec.ts`: رقابت موازی همزمان (Parallel Race با `Promise.all`) روی آخرین موجودی کالا در دو کانتکست مجزا. 11. `tests/e2e/storefront/concurrency-stock.spec.ts`: رقابت موازی همزمان (Parallel Race با `Promise.all`) روی آخرین موجودی کالا در دو کانتکست مجزا.
> [!WARNING]
> **محدودیت شناخته‌شده در تست همزمانی سطح E2E (Stock Concurrency & Race Conditions):**
> تست `concurrency-stock.spec.ts` رفتار لایه فرانت‌اند و کلاینت در برابر پاسخ‌های همزمان را پوشش می‌دهد. از آنجا که اجرای تست‌های مرورگری E2E به صورت ایزوله انجام شده و دیتابیس مشترک تست را مسدود نمی‌کند، برای راستی‌آزمایی دقیق تراکنش‌های ACID دیتابیس (مانند `SELECT FOR UPDATE`، جلوگیری از Overselling و قفل‌های ردیفی در Postgres)، قویاً توصیه می‌شود یک **Integration Test اختصاصی** مستقیماً روی لایه سرویس بک‌اند (`orders.service.ts` با یک نمونه تست واقعی PostgreSQL) اجرا گردد.
--- ---
## ۴. راهنمای نگهداری و افزودن فیچرهای جدید بدون شکستن تست‌ها (Developer Maintenance Guide) ## ۴. راهنمای نگهداری و افزودن فیچرهای جدید بدون شکستن تست‌ها (Developer Maintenance Guide)

View File

@ -60,6 +60,25 @@ test.describe('سناریوی مدیریت مقالات و وبلاگ در پن
}); });
}); });
await page.route('**/api/admin/blogs/authors*', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
success: true,
data: [{ id: 'admin-1', firstName: 'مدیر', lastName: 'کل', role: 'SUPER_ADMIN' }],
}),
});
});
await page.route('**/api/admin/products*', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ success: true, data: [] }),
});
});
await page.route('**/api/admin/blogs*', async (route) => { await page.route('**/api/admin/blogs*', async (route) => {
if (route.request().method() === 'GET') { if (route.request().method() === 'GET') {
await route.fulfill({ await route.fulfill({

View File

@ -1,10 +1,10 @@
import { test, expect } from '../../fixtures'; import { test, expect } from '../../fixtures';
test.describe('سناریوی اعتبارسنجی امنیتی XSS و محدودیت نرخ پیامک (Security XSS & Rate-Limiting)', () => { test.describe('سناریوی اعتبارسنجی امنیتی XSS و محدودیت نرخ پیامک (Security XSS & Rate-Limiting)', () => {
test('تزریق اسکریپت مخرب در فیلدهای فرم تماس و راستی‌آزمایی عدم اجرای XSS و پاکسازی داده‌ها', async ({ page }) => { test('تزریق اسکریپت مخرب در فیلدهای فرم تماس و راستی‌آزمایی عدم اجرای XSS در فرانت و پنل ادمین', async ({ page }) => {
let capturedPayload: Record<string, string> = {}; let capturedPayload: Record<string, string> = {};
await page.route('**/api/contact*', async (route) => { await page.route(/\/api\/contact|\/contact/, async (route) => {
if (route.request().method() === 'POST') { if (route.request().method() === 'POST') {
capturedPayload = route.request().postDataJSON?.() || {}; capturedPayload = route.request().postDataJSON?.() || {};
await route.fulfill({ await route.fulfill({
@ -38,30 +38,35 @@ test.describe('سناریوی اعتبارسنجی امنیتی XSS و محدو
const messageInput = page.locator('#contact-message'); const messageInput = page.locator('#contact-message');
await expect(nameInput).toBeVisible({ timeout: 10000 }); await expect(nameInput).toBeVisible({ timeout: 10000 });
await nameInput.click();
await nameInput.fill(xssPayload); await nameInput.fill(xssPayload);
await phoneInput.click();
await phoneInput.fill('09121112233'); await phoneInput.fill('09121112233');
await subjectInput.click();
await subjectInput.fill('XSS Test Subject'); await subjectInput.fill('XSS Test Subject');
await messageInput.click();
await messageInput.fill('<script>alert("XSS")</script> متن درخواست تستی'); await messageInput.fill('<script>alert("XSS")</script> متن درخواست تستی');
// 3. ارسال فرم تماس // 3. ارسال فرم تماس
const submitBtn = page.locator('button[type="submit"]').filter({ hasText: /ارسال پیام/i }).first(); const submitBtn = page.locator('[data-testid="contact-form-submit-btn"]').first();
await expect(submitBtn).toBeVisible(); await expect(submitBtn).toBeVisible({ timeout: 10000 });
await submitBtn.click(); await submitBtn.click();
// 4. راستی‌آزمایی نمایش پیام موفقیت بدون فعال شدن هیچ alert/dialog مخرب // 4. راستی‌آزمایی نمایش پیام موفقیت بدون فعال شدن هیچ alert/dialog مخرب
const successBox = page.locator('h4').filter({ hasText: 'پیام شما با موفقیت ثبت شد' }); const successBox = page.locator('h4').filter({ hasText: 'پیام شما با موفقیت ثبت شد' });
await expect(successBox).toBeVisible({ timeout: 10000 }); await expect(successBox).toBeVisible({ timeout: 15000 });
// راستی‌آزمایی قطعی عدم اجرای alert جاوااسکریپت در مرورگر // راستی‌آزمایی قطعی عدم اجرای alert جاوااسکریپت در مرورگر کلاینت
expect(dialogFired).toBe(false); expect(dialogFired).toBe(false);
expect(capturedPayload.phone).toBe('09121112233');
// راستی‌آزمایی ارسال امن داده‌ها
expect(capturedPayload.name).toBe(xssPayload); expect(capturedPayload.name).toBe(xssPayload);
expect(capturedPayload.phone).toBe('09121112233'); expect(capturedPayload.phone).toBe('09121112233');
}); });
test('شبیه‌سازی محدودیت نرخ و تایمر معکوس OTP (Rate-Limiting & Cooldown Timer)', async ({ page }) => { test('شبیه‌سازی محدودیت نرخ و تلاش مجدد با خطای ۴۲۹ (Rate-Limiting & 429 Cooldown Enforcement)', async ({ page }) => {
// Intercept send-otp to return 429 when rate limited
let sendOtpAttempts = 0; let sendOtpAttempts = 0;
await page.route('**/api/auth/send-otp*', async (route) => { await page.route('**/api/auth/send-otp*', async (route) => {
sendOtpAttempts++; sendOtpAttempts++;
@ -103,8 +108,22 @@ test.describe('سناریوی اعتبارسنجی امنیتی XSS و محدو
await expect(sendOtpBtn).toBeVisible(); await expect(sendOtpBtn).toBeVisible();
await sendOtpBtn.click(); await sendOtpBtn.click();
// 4. راستی‌آزمایی فعال بودن متن تایمر معکوس (Cooldown Timer) و عدم امکان کلیک زودهنگام // 4. راستی‌آزمایی فعال بودن متن تایمر معکوس (Cooldown Timer)
const timerText = page.locator('span').filter({ hasText: /ارسال مجدد کد تا/i }).first(); const timerText = page.locator('span').filter({ hasText: /ارسال مجدد کد تا/i }).first();
await expect(timerText).toBeVisible({ timeout: 10000 }); await expect(timerText).toBeVisible({ timeout: 10000 });
// 5. ارسال درخواست مستقیم دوم در بازه Cooldown و راستی‌آزمایی قطعی پاسخ 429 سرور
const rateLimitResponse = await page.evaluate(async () => {
const res = await fetch('/api/auth/send-otp', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ mobile: '09129998877' }),
});
return { status: res.status, data: await res.json() };
});
expect(rateLimitResponse.status).toBe(429);
expect(rateLimitResponse.data.message).toContain('بیش از حد مجاز');
expect(sendOtpAttempts).toBe(2);
}); });
}); });

View File

@ -21,15 +21,11 @@ test.describe('سناریوی عملیات پیشرفته سبد خرید (Cart
await page.goto(productHref!, { waitUntil: 'domcontentloaded' }); await page.goto(productHref!, { waitUntil: 'domcontentloaded' });
// 2. افزودن محصول به سبد خرید // 2. افزودن محصول به سبد خرید
if (isMobile) { const addBtn = isMobile
const mobileAddBtn = page.locator('div.fixed.bottom-0 button').filter({ hasText: /افزودن به سبد/i }).first(); ? page.locator('[data-testid="product-add-to-cart-btn"]').first()
await expect(mobileAddBtn).toBeVisible({ timeout: 15000 }); : page.locator('[data-testid="product-desktop-add-to-cart-btn"]').first();
await mobileAddBtn.click(); await expect(addBtn).toBeVisible({ timeout: 20000 });
} else { await addBtn.click();
const desktopAddBtn = page.locator('button').filter({ hasText: /افزودن به سبد/i }).first();
await expect(desktopAddBtn).toBeVisible({ timeout: 15000 });
await desktopAddBtn.click();
}
// 3. باز کردن دراور سبد خرید از هدر // 3. باز کردن دراور سبد خرید از هدر
const cartHeaderBtn = page.locator('[data-testid="cart-header-btn"]').first(); const cartHeaderBtn = page.locator('[data-testid="cart-header-btn"]').first();