mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 22:26:13 +00:00
feat(medications): let a delegate manage a composition, bypass the proxy for label uploads, and keep the guards current
This commit is contained in:
1 parent
eb3cc4237f
commit
9d32a8acb0
19 files changed
+293
-30
No files matched your search
@@ -31,6 +31,12 @@ vi.mock("@/lib/db", () => ({
|
||||
apiToken: {
|
||||
updateMany: vi.fn().mockResolvedValue({ count: 0 }),
|
||||
},
|
||||
// v1.43 — no composition: the GET carries `ingredients: []` and a PUT
|
||||
// queues no supplement recompute.
|
||||
medicationIngredient: {
|
||||
findFirst: vi.fn().mockResolvedValue(null),
|
||||
findMany: vi.fn().mockResolvedValue([]),
|
||||
},
|
||||
$transaction: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -17,6 +17,8 @@ vi.mock("@/lib/db", () => ({
|
||||
medication: { findUnique: vi.fn() },
|
||||
medicationIntakeEvent: { updateMany: vi.fn() },
|
||||
medicationComplianceRollup: { deleteMany: vi.fn() },
|
||||
// v1.43 — no composition, so no supplement recompute is queued.
|
||||
medicationIngredient: { findFirst: vi.fn().mockResolvedValue(null) },
|
||||
},
|
||||
}));
|
||||
|
||||
|
||||
@@ -890,7 +890,11 @@ describe("GET /api/medications — provenance echo (v1.32.25)", () => {
|
||||
end: new Date("2026-07-24T23:59:59Z"),
|
||||
} as never);
|
||||
vi.mocked(prisma.medication.findMany).mockResolvedValue(
|
||||
medications as never,
|
||||
// v1.43 — every row carries the ingredient count the list reads.
|
||||
medications.map((m) => ({
|
||||
_count: { ingredients: 0 },
|
||||
...(m as object),
|
||||
})) as never,
|
||||
);
|
||||
vi.mocked(prisma.medicationIntakeEvent.groupBy).mockResolvedValue(
|
||||
[] as never,
|
||||
@@ -1018,6 +1022,8 @@ describe("GET /api/medications — intake actions follow the course (#1040)", ()
|
||||
dose: null,
|
||||
},
|
||||
],
|
||||
// v1.43 — the ingredient count the list reads for `hasIngredients`.
|
||||
_count: { ingredients: 0 },
|
||||
...over,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
* per-nutrient summaries (latest day + total, days-with-data), the
|
||||
* module gate, and the `days` query validation.
|
||||
*/
|
||||
import { NUTRIENT_CATALOG } from "@/lib/nutrients/catalog";
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
@@ -111,11 +112,41 @@ describe("GET /api/nutrients", () => {
|
||||
// Rows arrive nutrient ASC, day DESC (the route's orderBy) — the
|
||||
// fold takes the first row per nutrient as the latest.
|
||||
vi.mocked(prisma.nutrientIntakeDay.findMany).mockResolvedValue([
|
||||
{ nutrient: "caffeine", unit: "mg", day: "2026-07-13", amount: 310 },
|
||||
{ nutrient: "caffeine", unit: "mg", day: "2026-07-12", amount: 250 },
|
||||
{ nutrient: "caffeine", unit: "mg", day: "2026-07-11", amount: 400 },
|
||||
{ nutrient: "vitamin_d", unit: "ug", day: "2026-07-13", amount: 22.5 },
|
||||
{ nutrient: "vitamin_d", unit: "ug", day: "2026-07-10", amount: 20 },
|
||||
{
|
||||
nutrient: "caffeine",
|
||||
unit: "mg",
|
||||
day: "2026-07-13",
|
||||
amount: 310,
|
||||
source: "APPLE_HEALTH",
|
||||
},
|
||||
{
|
||||
nutrient: "caffeine",
|
||||
unit: "mg",
|
||||
day: "2026-07-12",
|
||||
amount: 250,
|
||||
source: "APPLE_HEALTH",
|
||||
},
|
||||
{
|
||||
nutrient: "caffeine",
|
||||
unit: "mg",
|
||||
day: "2026-07-11",
|
||||
amount: 400,
|
||||
source: "APPLE_HEALTH",
|
||||
},
|
||||
{
|
||||
nutrient: "vitamin_d",
|
||||
unit: "ug",
|
||||
day: "2026-07-13",
|
||||
amount: 22.5,
|
||||
source: "APPLE_HEALTH",
|
||||
},
|
||||
{
|
||||
nutrient: "vitamin_d",
|
||||
unit: "ug",
|
||||
day: "2026-07-10",
|
||||
amount: 20,
|
||||
source: "APPLE_HEALTH",
|
||||
},
|
||||
] as never);
|
||||
|
||||
const res = await GET(getReq("?days=7"));
|
||||
@@ -129,6 +160,9 @@ describe("GET /api/nutrients", () => {
|
||||
latestDay: "2026-07-13",
|
||||
latestAmount: 22.5,
|
||||
daysWithData: 2,
|
||||
latestBySource: { APPLE_HEALTH: 22.5 },
|
||||
upperLimit: NUTRIENT_CATALOG.vitamin_d.upperLimit,
|
||||
latestAboveUpperLimit: false,
|
||||
},
|
||||
{
|
||||
nutrient: "caffeine",
|
||||
@@ -136,6 +170,9 @@ describe("GET /api/nutrients", () => {
|
||||
latestDay: "2026-07-13",
|
||||
latestAmount: 310,
|
||||
daysWithData: 3,
|
||||
latestBySource: { APPLE_HEALTH: 310 },
|
||||
upperLimit: null,
|
||||
latestAboveUpperLimit: null,
|
||||
},
|
||||
]);
|
||||
});
|
||||
@@ -145,10 +182,34 @@ describe("GET /api/nutrients", () => {
|
||||
// day logged via both APPLE_HEALTH and MANUAL must still count as ONE
|
||||
// day, not two.
|
||||
vi.mocked(prisma.nutrientIntakeDay.findMany).mockResolvedValue([
|
||||
{ nutrient: "water", unit: "ml", day: "2026-07-13", amount: 900 },
|
||||
{ nutrient: "water", unit: "ml", day: "2026-07-12", amount: 600 },
|
||||
{ nutrient: "water", unit: "ml", day: "2026-07-12", amount: 400 },
|
||||
{ nutrient: "water", unit: "ml", day: "2026-07-11", amount: 500 },
|
||||
{
|
||||
nutrient: "water",
|
||||
unit: "ml",
|
||||
day: "2026-07-13",
|
||||
amount: 900,
|
||||
source: "APPLE_HEALTH",
|
||||
},
|
||||
{
|
||||
nutrient: "water",
|
||||
unit: "ml",
|
||||
day: "2026-07-12",
|
||||
amount: 600,
|
||||
source: "APPLE_HEALTH",
|
||||
},
|
||||
{
|
||||
nutrient: "water",
|
||||
unit: "ml",
|
||||
day: "2026-07-12",
|
||||
amount: 400,
|
||||
source: "MANUAL",
|
||||
},
|
||||
{
|
||||
nutrient: "water",
|
||||
unit: "ml",
|
||||
day: "2026-07-11",
|
||||
amount: 500,
|
||||
source: "APPLE_HEALTH",
|
||||
},
|
||||
] as never);
|
||||
|
||||
const res = await GET(getReq("?days=7"));
|
||||
@@ -163,6 +224,59 @@ describe("GET /api/nutrients", () => {
|
||||
// four, since the 12th's second source row didn't match the pinned
|
||||
// `latestDay` (the 13th) and opened a phantom extra day.
|
||||
daysWithData: 3,
|
||||
latestBySource: { APPLE_HEALTH: 900 },
|
||||
upperLimit: null,
|
||||
latestAboveUpperLimit: null,
|
||||
});
|
||||
});
|
||||
|
||||
it("splits the latest day by source and judges it against the upper limit (v1.43)", async () => {
|
||||
vi.mocked(prisma.nutrientIntakeDay.findMany).mockResolvedValue([
|
||||
{
|
||||
nutrient: "vitamin_d",
|
||||
unit: "ug",
|
||||
day: "2026-07-13",
|
||||
amount: 30,
|
||||
source: "APPLE_HEALTH",
|
||||
},
|
||||
{
|
||||
nutrient: "vitamin_d",
|
||||
unit: "ug",
|
||||
day: "2026-07-13",
|
||||
amount: 80,
|
||||
source: "SUPPLEMENT",
|
||||
},
|
||||
{
|
||||
nutrient: "magnesium",
|
||||
unit: "mg",
|
||||
day: "2026-07-13",
|
||||
amount: 300,
|
||||
source: "APPLE_HEALTH",
|
||||
},
|
||||
{
|
||||
nutrient: "magnesium",
|
||||
unit: "mg",
|
||||
day: "2026-07-13",
|
||||
amount: 200,
|
||||
source: "SUPPLEMENT",
|
||||
},
|
||||
] as never);
|
||||
|
||||
const res = await GET(getReq("?days=7"));
|
||||
const body = (await res.json()) as OverviewResponse;
|
||||
const byCode = new Map(body.data.nutrients.map((n) => [n.nutrient, n]));
|
||||
// 110 µg in total against a total-scope limit of 100.
|
||||
expect(byCode.get("vitamin_d")).toMatchObject({
|
||||
latestAmount: 110,
|
||||
latestBySource: { APPLE_HEALTH: 30, SUPPLEMENT: 80 },
|
||||
latestAboveUpperLimit: true,
|
||||
});
|
||||
// 500 mg in total, but the limit covers the supplement share only (200).
|
||||
expect(byCode.get("magnesium")).toMatchObject({
|
||||
latestAmount: 500,
|
||||
latestBySource: { APPLE_HEALTH: 300, SUPPLEMENT: 200 },
|
||||
upperLimit: { value: 250, scope: "supplemental" },
|
||||
latestAboveUpperLimit: false,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user