The integration project now aliases server-only to a tiny empty stub so tests can import modules that carry 'import "server-only"' without the throwing real implementation firing during prepare. Unit tests do NOT get the alias — a pure module with a server import is exactly the kind of leak the unit project's missing condition is meant to surface.
76 lines
No EOL
2.7 KiB
TypeScript
76 lines
No EOL
2.7 KiB
TypeScript
import { describe, it, expect, beforeAll } from "vitest";
|
|
import PocketBase from "pocketbase";
|
|
|
|
import { recomputeMetrics } from "@/lib/metrics/store";
|
|
import { APP_URL, DEMO, adminPb, requireServices, signIn } from "./helpers";
|
|
|
|
let admin: PocketBase;
|
|
let projectId: string;
|
|
|
|
beforeAll(async () => {
|
|
await requireServices();
|
|
admin = await adminPb();
|
|
const project = await admin
|
|
.collection("projects")
|
|
.getFirstListItem(`slug = "${DEMO.projectSlug}"`);
|
|
projectId = project.id;
|
|
});
|
|
|
|
describe("cached metrics agree with the stored commits", () => {
|
|
it("counts the same non-merge commits the database holds", async () => {
|
|
const metrics = await recomputeMetrics(projectId);
|
|
|
|
const all = await admin.collection("commits").getFullList({
|
|
filter: `project = "${projectId}"`,
|
|
fields: "is_merge",
|
|
});
|
|
const merges = all.filter((c) => c.is_merge).length;
|
|
|
|
expect(metrics.totalCommits).toBe(all.length - merges);
|
|
expect(metrics.mergeCommits).toBe(merges);
|
|
});
|
|
|
|
it("stores the constants alongside the figures", async () => {
|
|
const cached = await admin
|
|
.collection("project_metrics")
|
|
.getFirstListItem(`project = "${projectId}"`);
|
|
const payload = cached.payload as { constants?: Record<string, number> };
|
|
|
|
expect(payload.constants).toBeDefined();
|
|
expect(payload.constants!.sessionGapMinutes).toBeGreaterThan(0);
|
|
});
|
|
|
|
it("never estimates fewer minutes than sessions times padding", async () => {
|
|
// A sanity floor: every session contributes at least its padding, so a
|
|
// total below that means clustering silently dropped commits.
|
|
const cached = await admin
|
|
.collection("project_metrics")
|
|
.getFirstListItem(`project = "${projectId}"`);
|
|
const p = cached.payload as {
|
|
estimatedMinutes: number;
|
|
sessionCount: number;
|
|
constants: { preSessionMinutes: number; postSessionMinutes: number };
|
|
};
|
|
|
|
const floor =
|
|
p.sessionCount * (p.constants.preSessionMinutes + p.constants.postSessionMinutes);
|
|
expect(p.estimatedMinutes).toBeGreaterThanOrEqual(floor);
|
|
});
|
|
});
|
|
|
|
describe("the dashboard is visible to a client manager", () => {
|
|
it("renders with the methodology panel", async () => {
|
|
// Effort figures are shown to clients by explicit decision, so the panel
|
|
// that explains them must render for them too.
|
|
const cookie = await signIn(DEMO.managerEmail, DEMO.managerPassword);
|
|
const response = await fetch(
|
|
`${APP_URL}/p/${DEMO.clientSlug}/${DEMO.projectSlug}/dashboard`,
|
|
{ headers: { cookie } }
|
|
);
|
|
|
|
expect(response.status).toBe(200);
|
|
const html = await response.text();
|
|
expect(html).toContain("How these numbers are worked out");
|
|
expect(html.toLowerCase()).toContain("estimated");
|
|
});
|
|
}); |