openmoneta-dev-kit 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +103 -0
  2. package/agents/qa-autonomous.md +131 -0
  3. package/agents/requirement-analyst.md +98 -0
  4. package/agents/security-auditor.md +120 -0
  5. package/agents/ui-tester.md +186 -0
  6. package/bin/openmoneta.js +11 -0
  7. package/hooks/check-plan-exists.sh +154 -0
  8. package/hooks/enforce-docs-first.sh +169 -0
  9. package/hooks/inject-process-context.sh +117 -0
  10. package/hooks/track-changes.sh +46 -0
  11. package/hooks/verify-completion.sh +165 -0
  12. package/hooks.json +30 -0
  13. package/opencode/AGENTS.md.tpl +38 -0
  14. package/opencode/agents/qa-autonomous.md +42 -0
  15. package/opencode/agents/requirement-analyst.md +51 -0
  16. package/opencode/agents/security-auditor.md +46 -0
  17. package/opencode/agents/ui-tester.md +43 -0
  18. package/opencode/plugins/openmoneta-guard.ts +389 -0
  19. package/package.json +41 -0
  20. package/scripts/debug-hooks.sh +54 -0
  21. package/scripts/init-project.sh +438 -0
  22. package/scripts/list-affected-modules.sh +74 -0
  23. package/skills/auth-bypass-testing/SKILL.md +236 -0
  24. package/skills/automated-testing/SKILL.md +162 -0
  25. package/skills/automated-testing/scripts/install-playwright.sh +134 -0
  26. package/skills/module-architect/SKILL.md +256 -0
  27. package/skills/plan-writer/SKILL.md +229 -0
  28. package/skills/requirement-analysis/SKILL.md +163 -0
  29. package/skills/safe-push/SKILL.md +182 -0
  30. package/skills/security-checklist/SKILL.md +116 -0
  31. package/skills/test-strategy/SKILL.md +135 -0
  32. package/skills/ui-test-loop/SKILL.md +161 -0
  33. package/src/cli.js +63 -0
  34. package/src/commands/check.js +30 -0
  35. package/src/commands/init.js +43 -0
  36. package/src/commands/install.js +50 -0
  37. package/src/commands/uninstall.js +74 -0
  38. package/src/commands/update.js +81 -0
  39. package/src/lib/paths.js +46 -0
  40. package/src/lib/version.js +45 -0
  41. package/templates/AGENTS.md.tpl +106 -0
  42. package/templates/docs-INDEX.md.tpl +62 -0
  43. package/templates/env.test.tpl +16 -0
  44. package/templates/karpathy-reference.md +49 -0
  45. package/templates/plans-INDEX.md.tpl +38 -0
  46. package/templates/playwright.config.ts.tpl +44 -0
@@ -0,0 +1,236 @@
1
+ ---
2
+ name: auth-bypass-testing
3
+ description: "ON-DEMAND ONLY: chỉ kích hoạt khi user explicitly yêu cầu bypass auth cho test (vd 'thêm test bypass cho Playwright login', 'setup test user pattern'). KHÔNG tự trigger trong quy trình bình thường. Triển khai Test Bypass Flag an toàn để Playwright test có thể bypass OAuth/login mà không phá bảo mật production. 6 layer defense (env guard, IP whitelist 127.0.0.1, token rotation, double header, audit log, CI/CD guard)."
4
+ ---
5
+
6
+ # Auth Bypass Testing — An toàn cho production
7
+
8
+ Pattern để Playwright test **bypass OAuth/login** (Google, GitHub, ...) khi test mà KHÔNG mở lỗ hổng cho production.
9
+
10
+ ## Nguyên tắc 6 Layer Defense
11
+
12
+ Tất cả layer phải pass cùng lúc thì bypass mới hoạt động:
13
+
14
+ | Layer | Cơ chế | Vi phạm = chặn |
15
+ |---|---|---|
16
+ | 1. Env guard | `NODE_ENV !== 'production'` AND `ENABLE_TEST_BYPASS === 'true'` | Tree-shake trong build prod |
17
+ | 2. IP whitelist | Chỉ accept `127.0.0.1`, `::1`, `localhost` | Reject mọi IP khác |
18
+ | 3. Token rotation | Token random 64-char, sinh mỗi bootstrap, lưu `.env.test` (gitignored) | Token sai = 401 |
19
+ | 4. Double header | Phải có `X-Test-Bypass-Token` AND `X-Test-User-Id` | Thiếu = 401 |
20
+ | 5. Audit log | Log mọi request bypass vào `logs/test-bypass.log` | Để forensic |
21
+ | 6. CI/CD guard | Pre-deploy script grep `ENABLE_TEST_BYPASS=true` trong prod config | Fail build |
22
+
23
+ ## Triển khai
24
+
25
+ ### Bước 1 — Tạo middleware bypass (theo stack)
26
+
27
+ #### Express / Node.js
28
+
29
+ ```ts
30
+ // src/middleware/test-bypass.ts
31
+ import type { Request, Response, NextFunction } from 'express';
32
+ import * as fs from 'fs';
33
+ import * as path from 'path';
34
+
35
+ const ALLOWED_IPS = (process.env.TEST_BYPASS_ALLOWED_IPS || '127.0.0.1,::1,localhost')
36
+ .split(',').map(s => s.trim());
37
+
38
+ const LOG_PATH = path.resolve(process.cwd(), 'logs/test-bypass.log');
39
+
40
+ export function testBypassMiddleware(req: Request, res: Response, next: NextFunction) {
41
+ // Layer 1: Env guard - sẽ bị tree-shake trong production build
42
+ if (process.env.NODE_ENV === 'production' || process.env.ENABLE_TEST_BYPASS !== 'true') {
43
+ return next();
44
+ }
45
+
46
+ const token = req.header('X-Test-Bypass-Token');
47
+ const userId = req.header('X-Test-User-Id');
48
+ if (!token || !userId) return next();
49
+
50
+ // Layer 2: IP whitelist
51
+ const ip = (req.ip || req.socket.remoteAddress || '').replace(/^::ffff:/, '');
52
+ if (!ALLOWED_IPS.includes(ip)) {
53
+ return res.status(403).json({ error: 'Test bypass: IP not allowed' });
54
+ }
55
+
56
+ // Layer 3 + 4: Token check
57
+ const expectedToken = process.env.TEST_BYPASS_TOKEN;
58
+ if (!expectedToken || token !== expectedToken) {
59
+ return res.status(401).json({ error: 'Test bypass: invalid token' });
60
+ }
61
+
62
+ // Validate userId thuộc fixture
63
+ const fixturePath = path.resolve(process.cwd(), 'tests/e2e/fixtures/test-users.json');
64
+ const fixtures = JSON.parse(fs.readFileSync(fixturePath, 'utf8'));
65
+ const user = Object.values(fixtures).find((u: any) => u.id === userId);
66
+ if (!user) {
67
+ return res.status(401).json({ error: 'Test bypass: invalid user id' });
68
+ }
69
+
70
+ // Layer 5: Audit log
71
+ fs.mkdirSync(path.dirname(LOG_PATH), { recursive: true });
72
+ fs.appendFileSync(LOG_PATH, JSON.stringify({
73
+ ts: new Date().toISOString(),
74
+ ip, userId, path: req.path, method: req.method,
75
+ ua: req.header('user-agent'),
76
+ }) + '\n');
77
+
78
+ // Inject user vào request
79
+ (req as any).user = user;
80
+ next();
81
+ }
82
+ ```
83
+
84
+ Mount **trước** middleware auth thật:
85
+
86
+ ```ts
87
+ app.use(testBypassMiddleware);
88
+ app.use(realAuthMiddleware);
89
+ ```
90
+
91
+ #### Next.js (App Router) — middleware.ts
92
+
93
+ ```ts
94
+ // middleware.ts ở root
95
+ import { NextRequest, NextResponse } from 'next/server';
96
+ import * as fs from 'fs';
97
+ import * as path from 'path';
98
+
99
+ const ALLOWED_IPS = ['127.0.0.1', '::1', 'localhost'];
100
+
101
+ export function middleware(req: NextRequest) {
102
+ if (process.env.NODE_ENV === 'production' || process.env.ENABLE_TEST_BYPASS !== 'true') {
103
+ return NextResponse.next();
104
+ }
105
+
106
+ const token = req.headers.get('x-test-bypass-token');
107
+ const userId = req.headers.get('x-test-user-id');
108
+ if (!token || !userId) return NextResponse.next();
109
+
110
+ const ip = req.ip || req.headers.get('x-forwarded-for') || '';
111
+ if (!ALLOWED_IPS.some(a => ip.includes(a))) {
112
+ return new NextResponse('IP not allowed', { status: 403 });
113
+ }
114
+
115
+ if (token !== process.env.TEST_BYPASS_TOKEN) {
116
+ return new NextResponse('Invalid token', { status: 401 });
117
+ }
118
+
119
+ // Set cookie/header để route handler đọc user
120
+ const res = NextResponse.next();
121
+ res.headers.set('x-bypass-user-id', userId);
122
+ return res;
123
+ }
124
+ ```
125
+
126
+ #### FastAPI (Python)
127
+
128
+ ```python
129
+ # app/middleware/test_bypass.py
130
+ from fastapi import Request, HTTPException
131
+ from starlette.middleware.base import BaseHTTPMiddleware
132
+ import os, json, datetime
133
+ from pathlib import Path
134
+
135
+ ALLOWED_IPS = os.getenv('TEST_BYPASS_ALLOWED_IPS', '127.0.0.1,::1,localhost').split(',')
136
+ LOG_PATH = Path('logs/test-bypass.log')
137
+
138
+ class TestBypassMiddleware(BaseHTTPMiddleware):
139
+ async def dispatch(self, request: Request, call_next):
140
+ if os.getenv('ENV') == 'production' or os.getenv('ENABLE_TEST_BYPASS') != 'true':
141
+ return await call_next(request)
142
+
143
+ token = request.headers.get('x-test-bypass-token')
144
+ user_id = request.headers.get('x-test-user-id')
145
+ if not token or not user_id:
146
+ return await call_next(request)
147
+
148
+ ip = (request.client.host if request.client else '').replace('::ffff:', '')
149
+ if ip not in ALLOWED_IPS:
150
+ raise HTTPException(403, 'Test bypass: IP not allowed')
151
+
152
+ if token != os.getenv('TEST_BYPASS_TOKEN'):
153
+ raise HTTPException(401, 'Test bypass: invalid token')
154
+
155
+ # Audit log
156
+ LOG_PATH.parent.mkdir(parents=True, exist_ok=True)
157
+ with LOG_PATH.open('a') as f:
158
+ f.write(json.dumps({
159
+ 'ts': datetime.datetime.utcnow().isoformat(),
160
+ 'ip': ip, 'user_id': user_id,
161
+ 'path': request.url.path, 'method': request.method,
162
+ }) + '\n')
163
+
164
+ request.state.bypass_user_id = user_id
165
+ return await call_next(request)
166
+ ```
167
+
168
+ ### Bước 2 — Test bypass với Playwright
169
+
170
+ Header đã được inject tự động trong `playwright.config.ts` (xem `automated-testing`). Test có thể assume user đã login.
171
+
172
+ ### Bước 3 — Pre-deploy CI guard
173
+
174
+ Tạo `scripts/check-no-bypass.sh`:
175
+
176
+ ```bash
177
+ #!/usr/bin/env bash
178
+ set -euo pipefail
179
+
180
+ # Fail build nếu phát hiện ENABLE_TEST_BYPASS=true trong production config
181
+ PROD_FILES=(
182
+ ".env.production"
183
+ ".env.prod"
184
+ "ecosystem.config.js"
185
+ "Dockerfile"
186
+ "docker-compose.prod.yml"
187
+ "kubernetes/*.yaml"
188
+ )
189
+
190
+ FOUND=0
191
+ for pattern in "${PROD_FILES[@]}"; do
192
+ for f in $pattern; do
193
+ [[ -f "$f" ]] || continue
194
+ if grep -E "ENABLE_TEST_BYPASS\s*[:=]\s*['\"]?true" "$f"; then
195
+ echo "[!] DANGER: ENABLE_TEST_BYPASS=true trong $f"
196
+ FOUND=1
197
+ fi
198
+ done
199
+ done
200
+
201
+ [[ $FOUND -eq 0 ]] && echo "✅ Pre-deploy check OK" || exit 1
202
+ ```
203
+
204
+ Thêm vào CI pipeline (GitHub Actions):
205
+
206
+ ```yaml
207
+ - name: Pre-deploy security check
208
+ run: bash scripts/check-no-bypass.sh
209
+ ```
210
+
211
+ ### Bước 4 — Verify trong Bước 5 (Security Check)
212
+
213
+ Khi audit:
214
+ - [ ] `ENABLE_TEST_BYPASS` chỉ enable trong `.env.test` (gitignored)
215
+ - [ ] `.env.test` có trong `.gitignore`
216
+ - [ ] Middleware có `process.env.NODE_ENV !== 'production'` guard
217
+ - [ ] CI có `check-no-bypass.sh`
218
+ - [ ] `logs/test-bypass.log` không chứa request từ IP lạ
219
+
220
+ ## Fallback: Storage State
221
+
222
+ Khi cần test flow **không thể bypass** (vd: OAuth callback từ Google, đăng ký user mới):
223
+
224
+ 1. Login thủ công 1 lần với tài khoản test riêng (KHÔNG dùng tài khoản cá nhân).
225
+ 2. Playwright lưu cookies + localStorage vào `tests/.auth/user.json` (gitignored).
226
+ 3. Test sau dùng `storageState` để skip login.
227
+
228
+ Xem skill `automated-testing` mục "Storage State".
229
+
230
+ ## Anti-patterns
231
+
232
+ ❌ Hardcode token trong code (phải qua env)
233
+ ❌ Bỏ IP check vì "test thôi mà"
234
+ ❌ Quên rotate token sau leak nghi ngờ
235
+ ❌ Log secret vào log file
236
+ ❌ Cho deploy với `ENABLE_TEST_BYPASS=true`
@@ -0,0 +1,162 @@
1
+ ---
2
+ name: automated-testing
3
+ description: "ON-DEMAND ONLY: chỉ kích hoạt khi user explicitly yêu cầu E2E/UI test với Playwright (vd 'viết Playwright test cho checkout flow', 'chạy E2E mobile'). KHÔNG tự trigger trong quy trình bình thường (v1.5.0 đã bỏ Bước 6 Test khỏi core process). Cài và dùng Playwright multi-viewport (mobile/tablet/desktop), screenshot, fixture cho auth state. Có script install-playwright.sh để AI tự cài."
4
+ ---
5
+
6
+ # Automated Testing với Playwright
7
+
8
+ Bộ công cụ test tự động cho UI/UX. Dùng kết hợp với skill `ui-test-loop` (workflow loop fix) và `auth-bypass-testing` (qua auth).
9
+
10
+ ## Cài Playwright
11
+
12
+ Chạy script tự động (idempotent):
13
+
14
+ ```bash
15
+ bash ~/.cursor/skills/automated-testing/scripts/install-playwright.sh
16
+ ```
17
+
18
+ Script sẽ:
19
+ 1. Detect package manager (`npm`/`pnpm`/`yarn`/`bun`).
20
+ 2. Cài `@playwright/test` + `dotenv`.
21
+ 3. Cài browser `chromium` (mặc định, tiết kiệm dung lượng).
22
+ 4. Tạo `playwright.config.ts` từ template nếu chưa có.
23
+ 5. Tạo `tests/e2e/auth.setup.ts` mẫu.
24
+
25
+ ## Cấu trúc tests/
26
+
27
+ ```
28
+ tests/
29
+ ├── unit/ # Vitest/Jest unit tests
30
+ ├── integration/ # Integration tests
31
+ ├── e2e/
32
+ │ ├── auth.setup.ts # Setup login state + test bypass header
33
+ │ ├── *.spec.ts # E2E specs
34
+ │ └── fixtures/
35
+ │ └── test-users.json
36
+ ├── screenshots/
37
+ │ └── <plan-slug>/iter-<n>/ # Output ui-test-loop
38
+ └── .auth/ # GITIGNORED — Playwright storageState (nếu dùng)
39
+ ```
40
+
41
+ ## Pattern cơ bản
42
+
43
+ ### 1. Test multi-viewport
44
+
45
+ `playwright.config.ts` đã setup 3 projects: mobile (375px), tablet (768px), desktop (1440px). Mặc định mọi spec chạy cả 3.
46
+
47
+ ```bash
48
+ # Chạy tất cả 3 viewport
49
+ npx playwright test
50
+
51
+ # Chạy 1 viewport cụ thể
52
+ npx playwright test --project=mobile
53
+
54
+ # Chạy 1 spec
55
+ npx playwright test login.spec.ts
56
+ ```
57
+
58
+ ### 2. Test với Test Bypass Auth
59
+
60
+ Header `X-Test-Bypass-Token` đã được inject tự động vào mọi request qua `extraHTTPHeaders` trong config (xem template `playwright.config.ts.tpl`). Test có thể assume user đã login.
61
+
62
+ ```ts
63
+ import { test, expect } from '@playwright/test';
64
+
65
+ test('user vào dashboard với test bypass', async ({ page }) => {
66
+ await page.goto('/dashboard');
67
+ await expect(page.getByRole('heading', { name: /dashboard/i })).toBeVisible();
68
+ });
69
+ ```
70
+
71
+ ### 3. Screenshot có cấu trúc
72
+
73
+ ```ts
74
+ import { test } from '@playwright/test';
75
+
76
+ test('Login button responsive', async ({ page }, testInfo) => {
77
+ await page.goto('/login');
78
+
79
+ const planSlug = process.env.PLAN_SLUG || 'unknown';
80
+ const iter = process.env.UI_ITER || '0';
81
+ const dir = `tests/screenshots/${planSlug}/iter-${iter}`;
82
+
83
+ await page.screenshot({
84
+ path: `${dir}/${testInfo.project.name}-login.png`,
85
+ fullPage: true,
86
+ });
87
+ });
88
+ ```
89
+
90
+ ### 4. Visual regression (snapshot)
91
+
92
+ ```ts
93
+ test('Login matches design', async ({ page }) => {
94
+ await page.goto('/login');
95
+ await expect(page).toHaveScreenshot('login.png', {
96
+ maxDiffPixels: 100, // Cho phép sai tối đa 100 pixel
97
+ });
98
+ });
99
+ ```
100
+
101
+ Lần đầu chạy → tạo baseline. Lần sau → so sánh.
102
+
103
+ ### 5. Storage State (cho flow OAuth thật)
104
+
105
+ Khi không thể bypass (vd: phải test OAuth callback từ Google thật), login 1 lần thủ công và lưu state:
106
+
107
+ ```ts
108
+ // tests/e2e/auth.setup.ts
109
+ import { test as setup } from '@playwright/test';
110
+
111
+ setup('login Google', async ({ page }) => {
112
+ await page.goto('/login');
113
+ await page.click('button:has-text("Đăng nhập Google")');
114
+ // ... user thao tác manual lần đầu
115
+ await page.context().storageState({ path: 'tests/.auth/user.json' });
116
+ });
117
+ ```
118
+
119
+ ```ts
120
+ // playwright.config.ts — thêm
121
+ export default defineConfig({
122
+ use: {
123
+ storageState: 'tests/.auth/user.json',
124
+ },
125
+ });
126
+ ```
127
+
128
+ ## Chạy test trong CI
129
+
130
+ ```bash
131
+ CI=1 npx playwright test --reporter=html
132
+ ```
133
+
134
+ ## Debug
135
+
136
+ ```bash
137
+ # UI mode — xem step-by-step
138
+ npx playwright test --ui
139
+
140
+ # Headed — xem browser thật
141
+ npx playwright test --headed
142
+
143
+ # Trace viewer — xem trace của test fail
144
+ npx playwright show-trace trace.zip
145
+ ```
146
+
147
+ ## Khi test fail
148
+
149
+ 1. Xem `tests/playwright-report/` (HTML report).
150
+ 2. Trace tự động lưu cho test fail (`retain-on-failure`).
151
+ 3. Apply skill `ui-test-loop` để loop fix.
152
+
153
+ ## Output bắt buộc cho hook `verify-completion`
154
+
155
+ Sau khi chạy test, lưu kết quả:
156
+
157
+ ```bash
158
+ # Ví dụ wrapper script
159
+ RESULT=$(npx playwright test --reporter=json 2>&1)
160
+ STATUS=$([[ $? == 0 ]] && echo "pass" || echo "fail")
161
+ echo "{\"status\": \"$STATUS\", \"timestamp\": \"$(date -Iseconds)\", \"tool\": \"playwright\"}" > .cursor/.last-test-result
162
+ ```
@@ -0,0 +1,134 @@
1
+ #!/usr/bin/env bash
2
+ # Cài Playwright cho project hiện tại. Idempotent - chạy lại không sao.
3
+ # Usage: bash ~/.cursor/skills/automated-testing/scripts/install-playwright.sh [project-path]
4
+
5
+ set -euo pipefail
6
+
7
+ PROJECT_DIR="${1:-$(pwd)}"
8
+ cd "$PROJECT_DIR"
9
+
10
+ echo "==> Cài Playwright cho: $PROJECT_DIR"
11
+
12
+ # Detect package manager
13
+ if [[ -f bun.lockb || -f bun.lock ]]; then
14
+ PM=bun
15
+ ADD="bun add -d"
16
+ elif [[ -f pnpm-lock.yaml ]]; then
17
+ PM=pnpm
18
+ ADD="pnpm add -D"
19
+ elif [[ -f yarn.lock ]]; then
20
+ PM=yarn
21
+ ADD="yarn add -D"
22
+ elif [[ -f package.json ]]; then
23
+ PM=npm
24
+ ADD="npm install -D"
25
+ else
26
+ echo "[!] Không phát hiện package.json. Tạo dự án Node trước (npm init -y) rồi chạy lại."
27
+ exit 1
28
+ fi
29
+
30
+ echo "==> Package manager: $PM"
31
+
32
+ # Check đã cài chưa
33
+ if [[ -f node_modules/@playwright/test/package.json ]]; then
34
+ echo "==> @playwright/test đã cài, skip."
35
+ else
36
+ echo "==> Cài @playwright/test và dotenv..."
37
+ $ADD @playwright/test dotenv
38
+ fi
39
+
40
+ # Cài browser chromium (mặc định, nhẹ nhất)
41
+ echo "==> Cài Chromium browser cho Playwright..."
42
+ npx playwright install chromium --with-deps 2>/dev/null || npx playwright install chromium
43
+
44
+ # Copy playwright.config.ts từ template nếu chưa có
45
+ TEMPLATE="$HOME/.cursor/templates/playwright.config.ts.tpl"
46
+ TARGET="playwright.config.ts"
47
+ if [[ -f "$TARGET" ]]; then
48
+ echo "==> $TARGET đã tồn tại, skip."
49
+ elif [[ -f "$TEMPLATE" ]]; then
50
+ cp "$TEMPLATE" "$TARGET"
51
+ echo "==> Đã tạo $TARGET từ template."
52
+ fi
53
+
54
+ # Tạo tests/e2e/auth.setup.ts mẫu
55
+ mkdir -p tests/e2e/fixtures
56
+ AUTH_SETUP="tests/e2e/auth.setup.ts"
57
+ if [[ ! -f "$AUTH_SETUP" ]]; then
58
+ cat > "$AUTH_SETUP" <<'EOF'
59
+ import { test as setup, expect } from '@playwright/test';
60
+ import * as fs from 'fs';
61
+ import * as path from 'path';
62
+
63
+ const AUTH_FILE = path.resolve(__dirname, '../.auth/user.json');
64
+
65
+ /**
66
+ * Setup auth state. 2 cách:
67
+ * 1. Test Bypass Flag (recommended): header X-Test-Bypass-Token đã set
68
+ * trong playwright.config.ts. Setup này chỉ sanity-check.
69
+ * 2. Storage State: login thật, lưu cookies/localStorage vào AUTH_FILE.
70
+ */
71
+ setup('verify auth bypass works', async ({ request }) => {
72
+ const baseURL = process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:3000';
73
+ const token = process.env.TEST_BYPASS_TOKEN;
74
+
75
+ if (!token) {
76
+ console.warn('[auth.setup] TEST_BYPASS_TOKEN không set. Bỏ qua bypass check.');
77
+ return;
78
+ }
79
+
80
+ // Sanity: gọi endpoint health/me với header bypass
81
+ const res = await request.get(`${baseURL}/api/me`, {
82
+ headers: {
83
+ 'X-Test-Bypass-Token': token,
84
+ 'X-Test-User-Id': process.env.TEST_USER_ID_DEFAULT || '',
85
+ },
86
+ failOnStatusCode: false,
87
+ });
88
+
89
+ if (res.status() === 404) {
90
+ console.warn('[auth.setup] Endpoint /api/me chưa có, skip sanity check.');
91
+ return;
92
+ }
93
+
94
+ expect(res.status()).toBeLessThan(500);
95
+ console.log('[auth.setup] Bypass auth OK.');
96
+
97
+ // Đảm bảo thư mục .auth tồn tại (không lưu state khi dùng bypass)
98
+ const dir = path.dirname(AUTH_FILE);
99
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
100
+ });
101
+ EOF
102
+ echo "==> Đã tạo $AUTH_SETUP mẫu."
103
+ fi
104
+
105
+ # Tạo tests/fixtures/test-users.json mẫu
106
+ USERS_JSON="tests/e2e/fixtures/test-users.json"
107
+ if [[ ! -f "$USERS_JSON" ]]; then
108
+ cat > "$USERS_JSON" <<'EOF'
109
+ {
110
+ "default": {
111
+ "id": "00000000-0000-0000-0000-000000000001",
112
+ "email": "test-default@example.com",
113
+ "name": "Test User",
114
+ "role": "user"
115
+ },
116
+ "admin": {
117
+ "id": "00000000-0000-0000-0000-000000000002",
118
+ "email": "test-admin@example.com",
119
+ "name": "Test Admin",
120
+ "role": "admin"
121
+ }
122
+ }
123
+ EOF
124
+ echo "==> Đã tạo $USERS_JSON mẫu."
125
+ fi
126
+
127
+ echo ""
128
+ echo "✅ Hoàn tất cài Playwright."
129
+ echo ""
130
+ echo "Chạy test: npx playwright test"
131
+ echo "Chạy mobile only: npx playwright test --project=mobile"
132
+ echo "UI mode: npx playwright test --ui"
133
+ echo ""
134
+ echo "Lưu ý: setup .env.test với TEST_BYPASS_TOKEN và backend của bạn phải có middleware bypass (xem skill auth-bypass-testing)."