fix(meds): glp1 GET routes through assertMedicationOwnership

The destructive sweep in commit af224964 missed the glp1 GET
branch; the handler still hand-rolled findUnique + userId compare.
Lifting it to the same ownership helper closes the §10 invariant
24 gap and aligns the GET with the POST.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marc-André BombeckandClaude Opus 4.7 committed 2026-05-29 00:53:23 +02:00
1 parent 043c335ba3
commit 359b103e90
2 files changed
+56 -6

No files matched your search

@@ -19,6 +19,10 @@ vi.mock("@/lib/db", () => ({
},
}));
vi.mock("@/lib/medications/route-guards", () => ({
assertMedicationOwnership: vi.fn().mockResolvedValue(null),
}));
vi.mock("@/lib/auth/session", () => ({ getSession: vi.fn() }));
vi.mock("@/lib/auth/audit", () => ({
@@ -49,11 +53,12 @@ vi.mock("next/headers", () => ({
})),
}));
import { POST } from "../route";
import { GET, POST } from "../route";
import { prisma } from "@/lib/db";
import { getSession } from "@/lib/auth/session";
import { auditLog } from "@/lib/auth/audit";
import { checkRateLimit } from "@/lib/rate-limit";
import { assertMedicationOwnership } from "@/lib/medications/route-guards";
const SESSION_OK = {
session: { id: "sess-1", expiresAt: new Date(Date.now() + 3_600_000) },
@@ -77,6 +82,7 @@ beforeEach(() => {
remaining: 29,
resetAt: Date.now() + 60_000,
});
vi.mocked(assertMedicationOwnership).mockResolvedValue(null);
});
describe("POST /api/medications/[id]/glp1", () => {
@@ -97,10 +103,9 @@ describe("POST /api/medications/[id]/glp1", () => {
it("returns 404 when the medication belongs to a different user", async () => {
vi.mocked(getSession).mockResolvedValue(SESSION_OK as never);
vi.mocked(prisma.medication.findUnique).mockResolvedValue({
id: "med-1",
userId: "OTHER",
} as never);
vi.mocked(assertMedicationOwnership).mockResolvedValueOnce(
new Response(null, { status: 404 }) as never,
);
const res = await POST(
jsonReq({
doseChange: {
@@ -387,3 +392,39 @@ describe("POST /api/medications/[id]/glp1 — 422 multi-issue (v1.4.43 W6)", ()
expect(body.details.issues.length).toBeGreaterThanOrEqual(3);
});
});
describe("GET /api/medications/[id]/glp1 — ownership helper (F-1 C-4)", () => {
function getReq(): NextRequest {
return new NextRequest("http://localhost/api/medications/med-1/glp1");
}
it("routes ownership through assertMedicationOwnership", async () => {
vi.mocked(getSession).mockResolvedValue(SESSION_OK as never);
vi.mocked(prisma.medication.findUnique).mockResolvedValue({
id: "med-1",
userId: "user-1",
doseChanges: [],
inventoryEvents: [],
intakeEvents: [],
schedules: [],
dosesPerUnit: null,
} as never);
const res = await GET(getReq(), {
params: Promise.resolve({ id: "med-1" }),
});
expect(res.status).toBe(200);
expect(assertMedicationOwnership).toHaveBeenCalledWith("med-1", "user-1");
});
it("returns the shared helper's 404 without reading the medication", async () => {
vi.mocked(getSession).mockResolvedValue(SESSION_OK as never);
vi.mocked(assertMedicationOwnership).mockResolvedValueOnce(
new Response(null, { status: 404 }) as never,
);
const res = await GET(getReq(), {
params: Promise.resolve({ id: "med-1" }),
});
expect(res.status).toBe(404);
expect(prisma.medication.findUnique).not.toHaveBeenCalled();
});
});
+10 -1
View File
@@ -41,6 +41,15 @@ export const GET = apiHandler(
const { user } = await requireAuth();
const { id } = await params;
// v1.5.5 F-1 C-4 — close the §10 invariant 24 gap. The destructive
// sweep in af224964 lifted every sibling `[id]/**` handler onto the
// shared ownership helper; the glp1 GET still hand-rolled the
// `findUnique + userId compare` pattern. Routing it through the
// same predicate keeps the 404 leak shape identical across every
// handler in the directory.
const guard = await assertMedicationOwnership(id, user.id);
if (guard) return guard;
const medication = await prisma.medication.findUnique({
where: { id },
include: {
@@ -56,7 +65,7 @@ export const GET = apiHandler(
},
});
if (!medication || medication.userId !== user.id) {
if (!medication) {
return apiError("Medication not found", 404);
}