mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
test(db): expand the real-Postgres integration coverage
v1.4.30 backstop. Every new endpoint introduced earlier in the patch run rides the existing real-Postgres container fixture (v1.4.29's contribution): the SyncMode handshake, the mood-entries bulk upsert, the medication-intake bulk insert with idempotency-key collision, the mood-note column round-trip, and the drain helper itself. This commit adds the missing slot — the admin endpoint POST /api/admin/drain-per-sample-cumulative — so the route's admin gate, dryRun default, and commit path all run against real SQL end-to-end. Mocked-prisma siblings would have hidden the v1.4.29 lesson (the date_trunc bound-parameter bug); the route's transactional upsert + delete needs the same fence. Coverage delta: 3 new integration specs, all green.
This commit is contained in:
1 parent
dd487cfddc
commit
a3b00f49ef
1 file changed
+188
@@ -0,0 +1,188 @@
|
||||
/**
|
||||
* v1.4.30 — `POST /api/admin/drain-per-sample-cumulative` real-
|
||||
* Postgres integration. The library-level helper rides
|
||||
* `tests/integration/drain-per-sample-cumulative.test.ts`; this
|
||||
* suite pins the route's admin gate + payload shape + audit log
|
||||
* trail end-to-end.
|
||||
*/
|
||||
import { NextRequest } from "next/server";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { cookieJar } from "./mock-next-headers";
|
||||
import { getPrismaClient, truncateAllTables } from "./setup";
|
||||
|
||||
const ADMIN_USER_ID = "user-admin-drain";
|
||||
const STANDARD_USER_ID = "user-standard-drain";
|
||||
|
||||
vi.mock("next/headers", async () => {
|
||||
const { cookieJar, headerJar } = await import("./mock-next-headers");
|
||||
return {
|
||||
headers: vi.fn(async () => ({
|
||||
get: (name: string) => headerJar.get(name.toLowerCase()) ?? null,
|
||||
})),
|
||||
cookies: vi.fn(async () => ({
|
||||
get: (name: string) => {
|
||||
const value = cookieJar.get(name);
|
||||
return value ? { name, value } : undefined;
|
||||
},
|
||||
set: (name: string, value: string) => {
|
||||
cookieJar.set(name, value);
|
||||
},
|
||||
delete: (name: string) => {
|
||||
cookieJar.delete(name);
|
||||
},
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@/lib/db-compat", () => ({
|
||||
ensureDbCompatibility: vi.fn().mockResolvedValue(undefined),
|
||||
}));
|
||||
|
||||
beforeEach(async () => {
|
||||
await truncateAllTables(getPrismaClient());
|
||||
cookieJar.clear();
|
||||
await getPrismaClient().user.createMany({
|
||||
data: [
|
||||
{
|
||||
id: ADMIN_USER_ID,
|
||||
username: "admin-drain",
|
||||
email: "admin-drain@example.test",
|
||||
role: "ADMIN",
|
||||
timezone: "Europe/Berlin",
|
||||
},
|
||||
{
|
||||
id: STANDARD_USER_ID,
|
||||
username: "standard-drain",
|
||||
email: "standard-drain@example.test",
|
||||
role: "USER",
|
||||
timezone: "Europe/Berlin",
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
async function setSessionFor(userId: string): Promise<void> {
|
||||
const session = await getPrismaClient().session.create({
|
||||
data: {
|
||||
userId,
|
||||
expiresAt: new Date(Date.now() + 60 * 60 * 1000),
|
||||
},
|
||||
});
|
||||
cookieJar.clear();
|
||||
cookieJar.set("healthlog_session", session.id);
|
||||
}
|
||||
|
||||
function makeRequest(body: unknown): NextRequest {
|
||||
return new NextRequest(
|
||||
"http://localhost/api/admin/drain-per-sample-cumulative",
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
describe("POST /api/admin/drain-per-sample-cumulative (real Postgres)", () => {
|
||||
it("returns 403 when the session belongs to a standard (non-admin) user", async () => {
|
||||
await setSessionFor(STANDARD_USER_ID);
|
||||
const { POST } = await import(
|
||||
"@/app/api/admin/drain-per-sample-cumulative/route"
|
||||
);
|
||||
const res = await POST(makeRequest({ dryRun: true }));
|
||||
expect(res.status).toBe(403);
|
||||
});
|
||||
|
||||
it("dry-runs by default and reports buckets without writing", async () => {
|
||||
const prisma = getPrismaClient();
|
||||
await prisma.measurement.createMany({
|
||||
data: [
|
||||
{
|
||||
userId: STANDARD_USER_ID,
|
||||
type: "ACTIVITY_STEPS",
|
||||
value: 1200,
|
||||
unit: "steps",
|
||||
source: "APPLE_HEALTH",
|
||||
measuredAt: new Date("2026-05-16T08:00:00.000Z"),
|
||||
externalId: "hk-uuid-1",
|
||||
},
|
||||
{
|
||||
userId: STANDARD_USER_ID,
|
||||
type: "ACTIVITY_STEPS",
|
||||
value: 800,
|
||||
unit: "steps",
|
||||
source: "APPLE_HEALTH",
|
||||
measuredAt: new Date("2026-05-16T14:00:00.000Z"),
|
||||
externalId: "hk-uuid-2",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await setSessionFor(ADMIN_USER_ID);
|
||||
const { POST } = await import(
|
||||
"@/app/api/admin/drain-per-sample-cumulative/route"
|
||||
);
|
||||
// No body = dryRun defaults to true.
|
||||
const res = await POST(makeRequest({ userId: STANDARD_USER_ID }));
|
||||
expect(res.status).toBe(200);
|
||||
const json = (await res.json()) as {
|
||||
data: {
|
||||
dryRun: boolean;
|
||||
totals: { bucketsCollapsed: number };
|
||||
};
|
||||
};
|
||||
expect(json.data.dryRun).toBe(true);
|
||||
expect(json.data.totals.bucketsCollapsed).toBe(1);
|
||||
|
||||
// DB unchanged.
|
||||
const remaining = await prisma.measurement.findMany({
|
||||
where: { userId: STANDARD_USER_ID, type: "ACTIVITY_STEPS" },
|
||||
});
|
||||
expect(remaining).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("commits when dryRun=false is passed explicitly", async () => {
|
||||
const prisma = getPrismaClient();
|
||||
await prisma.measurement.createMany({
|
||||
data: [
|
||||
{
|
||||
userId: STANDARD_USER_ID,
|
||||
type: "ACTIVITY_STEPS",
|
||||
value: 1200,
|
||||
unit: "steps",
|
||||
source: "APPLE_HEALTH",
|
||||
measuredAt: new Date("2026-05-16T08:00:00.000Z"),
|
||||
externalId: "hk-uuid-1",
|
||||
},
|
||||
{
|
||||
userId: STANDARD_USER_ID,
|
||||
type: "ACTIVITY_STEPS",
|
||||
value: 800,
|
||||
unit: "steps",
|
||||
source: "APPLE_HEALTH",
|
||||
measuredAt: new Date("2026-05-16T14:00:00.000Z"),
|
||||
externalId: "hk-uuid-2",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await setSessionFor(ADMIN_USER_ID);
|
||||
const { POST } = await import(
|
||||
"@/app/api/admin/drain-per-sample-cumulative/route"
|
||||
);
|
||||
const res = await POST(
|
||||
makeRequest({ userId: STANDARD_USER_ID, dryRun: false }),
|
||||
);
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
const remaining = await prisma.measurement.findMany({
|
||||
where: { userId: STANDARD_USER_ID, type: "ACTIVITY_STEPS" },
|
||||
});
|
||||
expect(remaining).toHaveLength(1);
|
||||
expect(remaining[0].value).toBe(2000);
|
||||
expect(remaining[0].externalId).toBe(
|
||||
"stats:HKQuantityTypeIdentifierStepCount:2026-05-16",
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user