devperf/backend/tests/api/overview.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

176 lines
5.3 KiB
TypeScript

/**
* API integration tests for the overview endpoint.
* Tests the response structure and data shape of GET /api/overview.
*
* Uses a minimal mock app since the real overview route requires
* database access. Validates that the API contract (response schema)
* matches what the frontend expects.
*/
import { describe, it, expect } from 'bun:test';
import { Hono } from 'hono';
function createOverviewTestApp() {
const app = new Hono();
app.get('/api/overview', async (c) => {
// Return mock data matching the OverviewData type contract
return c.json({
code: 0,
data: {
sprintDelivery: {
cycles: [
{ name: 'Sprint 1', plannedPoints: 40, completedPoints: 32, deliveryRate: 80 },
{ name: 'Sprint 2', plannedPoints: 45, completedPoints: 38, deliveryRate: 84.4 },
{ name: 'Sprint 3', plannedPoints: 50, completedPoints: 42, deliveryRate: 84 },
],
},
taskDistribution: {
todo: 12,
inProgress: 8,
review: 5,
done: 35,
},
projectProgress: [
{
projectId: 'proj-001',
name: 'Avatar Platform',
identifier: 'AVATAR',
currentCycleProgress: 72,
totalPoints: 50,
completedPoints: 36,
},
],
weeklyCodeActivity: {
weeks: [
{
weekStart: '2026-04-01',
members: [
{ userId: 'user-001', name: 'Alice', commits: 12, prs: 3 },
],
},
],
},
okrProgress: [
{
id: 'okr-001',
title: 'Improve delivery rate',
ownerName: 'Bob',
progress: 65,
keyResults: [
{ title: 'Sprint delivery > 80%', current: 80, target: 100, unit: '%' },
],
},
],
prMergeTime: {
weeks: [
{ weekStart: '2026-04-01', avgHours: 24.5, prCount: 8 },
{ weekStart: '2026-03-25', avgHours: 36.2, prCount: 5 },
],
},
},
message: 'success',
});
});
return app;
}
describe('GET /api/overview', () => {
const app = createOverviewTestApp();
it('should return 200 with complete overview data', async () => {
const res = await app.request('/api/overview');
expect(res.status).toBe(200);
const body = await res.json();
expect(body.code).toBe(0);
expect(body.message).toBe('success');
const data = body.data;
expect(data).toBeDefined();
});
it('should contain sprintDelivery with cycles array', async () => {
const res = await app.request('/api/overview');
const body = await res.json();
const sd = body.data.sprintDelivery;
expect(sd).toBeDefined();
expect(Array.isArray(sd.cycles)).toBe(true);
expect(sd.cycles.length).toBeGreaterThan(0);
const cycle = sd.cycles[0];
expect(typeof cycle.name).toBe('string');
expect(typeof cycle.plannedPoints).toBe('number');
expect(typeof cycle.completedPoints).toBe('number');
expect(typeof cycle.deliveryRate).toBe('number');
});
it('should contain taskDistribution with all status counts', async () => {
const res = await app.request('/api/overview');
const body = await res.json();
const td = body.data.taskDistribution;
expect(typeof td.todo).toBe('number');
expect(typeof td.inProgress).toBe('number');
expect(typeof td.review).toBe('number');
expect(typeof td.done).toBe('number');
});
it('should contain projectProgress array', async () => {
const res = await app.request('/api/overview');
const body = await res.json();
const pp = body.data.projectProgress;
expect(Array.isArray(pp)).toBe(true);
expect(pp.length).toBeGreaterThan(0);
const proj = pp[0];
expect(typeof proj.projectId).toBe('string');
expect(typeof proj.name).toBe('string');
expect(typeof proj.currentCycleProgress).toBe('number');
});
it('should contain weeklyCodeActivity with weeks', async () => {
const res = await app.request('/api/overview');
const body = await res.json();
const wca = body.data.weeklyCodeActivity;
expect(Array.isArray(wca.weeks)).toBe(true);
expect(wca.weeks.length).toBeGreaterThan(0);
const week = wca.weeks[0];
expect(typeof week.weekStart).toBe('string');
expect(Array.isArray(week.members)).toBe(true);
});
it('should contain okrProgress array', async () => {
const res = await app.request('/api/overview');
const body = await res.json();
const okr = body.data.okrProgress;
expect(Array.isArray(okr)).toBe(true);
expect(okr.length).toBeGreaterThan(0);
const obj = okr[0];
expect(typeof obj.id).toBe('string');
expect(typeof obj.title).toBe('string');
expect(typeof obj.progress).toBe('number');
expect(Array.isArray(obj.keyResults)).toBe(true);
});
it('should contain prMergeTime with weeks', async () => {
const res = await app.request('/api/overview');
const body = await res.json();
const pmt = body.data.prMergeTime;
expect(Array.isArray(pmt.weeks)).toBe(true);
expect(pmt.weeks.length).toBeGreaterThan(0);
const week = pmt.weeks[0];
expect(typeof week.weekStart).toBe('string');
expect(typeof week.avgHours).toBe('number');
expect(typeof week.prCount).toBe('number');
});
});