devperf/backend/tests/unit/auth-service.test.ts
zyc 44464dd334 feat: DevPerf Dashboard 研发人效看板 v1.0
- 后端:Bun + Hono + Drizzle ORM + SQLite
- 前端:Vue 3 + Naive UI + ECharts
- 项目管理:创建项目 + 绑定 Git 仓库
- OKR 系统:目标/关键结果 CRUD + 进度追踪
- Git 同步:Gitea API 自动同步 commit/PR + 作者关联
- 数据看板:项目 OKR 进度 + KR 状态分布 + 代码活动
- 权限体系:admin/manager/developer/viewer 四级
- Docker 部署:docker-compose + nginx

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 17:57:14 +08:00

108 lines
3.3 KiB
TypeScript

/**
* Unit tests for JWT token generation and verification logic.
* Tests the jose-based JWT flow independent of the database.
*/
import { describe, it, expect } from 'bun:test';
import { SignJWT, jwtVerify } from 'jose';
const TEST_SECRET = new TextEncoder().encode(
'test-secret-for-unit-tests-at-least-16-chars',
);
describe('JWT Token Generation', () => {
it('should generate a valid JWT with expected claims', async () => {
const token = await new SignJWT({
sub: 'user-001',
email: 'test@example.com',
role: 'admin',
displayName: 'Test User',
})
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setExpirationTime('7d')
.sign(TEST_SECRET);
expect(typeof token).toBe('string');
expect(token.split('.').length).toBe(3); // JWT has 3 parts
// Verify the token
const { payload } = await jwtVerify(token, TEST_SECRET);
expect(payload.sub).toBe('user-001');
expect(payload.email).toBe('test@example.com');
expect(payload.role).toBe('admin');
expect(payload.displayName).toBe('Test User');
expect(payload.exp).toBeDefined();
expect(payload.iat).toBeDefined();
});
it('should reject tampered tokens', async () => {
const token = await new SignJWT({ sub: 'user-001' })
.setProtectedHeader({ alg: 'HS256' })
.setExpirationTime('1h')
.sign(TEST_SECRET);
// Tamper with the token by changing a character
const parts = token.split('.');
parts[1] = parts[1].slice(0, -1) + (parts[1].slice(-1) === 'A' ? 'B' : 'A');
const tampered = parts.join('.');
try {
await jwtVerify(tampered, TEST_SECRET);
expect(true).toBe(false); // Should not reach here
} catch (err: any) {
expect(err.code).toBe('ERR_JWS_SIGNATURE_VERIFICATION_FAILED');
}
});
it('should reject tokens signed with wrong secret', async () => {
const wrongSecret = new TextEncoder().encode('wrong-secret-that-is-long-enough');
const token = await new SignJWT({ sub: 'user-001' })
.setProtectedHeader({ alg: 'HS256' })
.setExpirationTime('1h')
.sign(wrongSecret);
try {
await jwtVerify(token, TEST_SECRET);
expect(true).toBe(false);
} catch (err: any) {
expect(err.code).toBe('ERR_JWS_SIGNATURE_VERIFICATION_FAILED');
}
});
it('should reject expired tokens', async () => {
const token = await new SignJWT({ sub: 'user-001' })
.setProtectedHeader({ alg: 'HS256' })
.setExpirationTime('0s') // Already expired
.sign(TEST_SECRET);
// Wait a tiny bit to ensure expiration
await new Promise((resolve) => setTimeout(resolve, 10));
try {
await jwtVerify(token, TEST_SECRET);
expect(true).toBe(false);
} catch (err: any) {
expect(err.code).toBe('ERR_JWT_EXPIRED');
}
});
it('should include all role types in valid token payloads', async () => {
const roles = ['admin', 'manager', 'developer', 'viewer'];
for (const role of roles) {
const token = await new SignJWT({
sub: `user-${role}`,
email: `${role}@test.com`,
role,
displayName: `Test ${role}`,
})
.setProtectedHeader({ alg: 'HS256' })
.setExpirationTime('1h')
.sign(TEST_SECRET);
const { payload } = await jwtVerify(token, TEST_SECRET);
expect(payload.role).toBe(role);
}
});
});