mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 13:22:14 +00:00
ensureDbCompatibility ran ALTER TABLE / CREATE TABLE IF NOT EXISTS on the login, register, token and session paths, mirroring columns every migration under prisma/migrations already creates. The entrypoint applies those migrations before the server listens and stops the boot when one fails, so the mirror never had work to do, and DDL on a request path needs the table's owner: a connection that may only read and write rows fails every login on it. Drop the module, its call sites and the mocks that stubbed it out.
182 lines
5.3 KiB
TypeScript
182 lines
5.3 KiB
TypeScript
/**
|
|
* 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);
|
|
},
|
|
})),
|
|
};
|
|
});
|
|
|
|
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",
|
|
);
|
|
});
|
|
});
|