用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/getsentry/abacus --skill write-tests命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
基于 SOC 职业分类
正在显示 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 using Vitest. Tests are colocated next to source files.
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 is globally mocked. Use helpers to control auth state:
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
Uses PGlite (in-memory PostgreSQL). No Docker required.
insertUsageRecord from @/lib/queries to seed dataimport { 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,
});
});
pnpm test # run all
pnpm test:watch # watch mode
foo.ts → foo.test.ts)mockAuthenticated()/mockUnauthenticated() for authinsertUsageRecord for seeding databasesrc/test-utils/msw-handlers.tsEvery protected route MUST have an auth test. This is non-negotiable.
Routes using getSession() must verify 401 on unauthenticated requests:
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);
});
Routes using CRON_SECRET must verify auth:
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);
});
Routes with signature verification must test invalid signatures:
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.