Skip to main content

write-tests

Write tests following project conventions. Use when adding new tests or modifying existing ones.

来源信息

仓库
getsentry/abacus
最近来源活动
2026年1月10日 20:51
检测到的 SKILL.md 语言
英语
星标
77
分支
5

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
write-tests
description
Write tests following project conventions. Use when adding new tests or modifying existing ones.
allowed-tools
Read, Grep, Glob, Edit, Write, Bash
# Write Tests Skill Write tests using Vitest. Tests are **colocated** next to source files. ## Structure ``` src/lib/utils.ts src/lib/utils.test.ts # colocated src/app/api/stats/route.ts src/app/api/stats/route.test.ts src/test-utils/ ├── setup.ts # global setup (PGlite, MSW, auth mock) ├── msw-handlers.ts # external API mocks └── auth.ts # auth test helpers ``` ## Auth Testing Auth is **globally mocked**. Use helpers to control auth state: ```typescript import { mockAuthenticated, mockUnauthenticated } from '@/test-utils/auth'; it('returns 401 when unauthenticated', async () => { await mockUnauthenticated(); const response = await GET(new Request('http://localhost/api/foo')); expect(response.status).toBe(401); }); it('returns data when authenticated', async () => { await mockAuthenticated(); // uses default test user // or with overrides: await mockAuthenticated({ email: 'custom@example.com' }); const response = await GET(new Request('http://localhost/api/foo')); expect(response.status).toBe(200); }); ``` Default test user: `test@example.com` / `Test User` ## Database Testing Uses **PGlite** (in-memory PostgreSQL). No Docker required. - Schema pushed automatically on boot - Each test runs in a transaction that rolls back - Use `insertUsageRecord` from `@/lib/queries` to seed data ```typescript import { insertUsageRecord, getOverallStats } from '@/lib/queries'; beforeEach(async () => { await insertUsageRecord({ date: '2025-01-01', email: 'user@example.com', tool: 'claude_code', model: 'sonnet-4', rawModel: 'claude-sonnet-4-20250514', inputTokens: 1000, outputTokens: 500, cacheWriteTokens: 0, cacheReadTokens: 0, cost: 0.01, }); }); ``` ## Running Tests ```bash pnpm test # run all pnpm test:watch # watch mode ``` ## Key Rules 1. Colocate tests next to source (`foo.ts` → `foo.test.ts`) 2. Use `mockAuthenticated()`/`mockUnauthenticated()` for auth 3. Use `insertUsageRecord` for seeding database 4. External APIs mocked via MSW in `src/test-utils/msw-handlers.ts` ## CRITICAL: Auth Tests Required **Every protected route MUST have an auth test.** This is non-negotiable. ### Session-Authenticated Routes Routes using `getSession()` must verify 401 on unauthenticated requests: ```typescript it('returns 401 for unauthenticated requests', async () => { await mockUnauthenticated(); const response = await GET(new Request('http://localhost/api/your-route')); expect(response.status).toBe(401); }); ``` ### Cron Routes Routes using `CRON_SECRET` must verify auth: ```typescript beforeEach(() => { vi.stubEnv('CRON_SECRET', 'test-secret'); }); it('returns 401 without authorization header', async () => { const response = await GET(new Request('http://localhost/api/cron/your-route')); expect(response.status).toBe(401); }); it('returns 401 with invalid authorization', async () => { const response = await GET( new Request('http://localhost/api/cron/your-route', { headers: { Authorization: 'Bearer wrong-secret' }, }) ); expect(response.status).toBe(401); }); ``` ### Webhook Routes Routes with signature verification must test invalid signatures: ```typescript it('returns 401 without signature', async () => { const response = await POST( new Request('http://localhost/api/webhooks/github', { method: 'POST', body: '{}', }) ); expect(response.status).toBe(401); }); it('returns 401 with invalid signature', async () => { const response = await POST( new Request('http://localhost/api/webhooks/github', { method: 'POST', body: '{}', headers: { 'x-hub-signature-256': 'sha256=invalid' }, }) ); expect(response.status).toBe(401); }); ``` **When adding a new route, always ask: "What auth does this route require?" and add the corresponding auth test.**
在 GitHub 查看