- 后端: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>
176 lines
5.3 KiB
TypeScript
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');
|
|
});
|
|
});
|