mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 06:40:50 +00:00
Introduce a retrospective illness/condition journal: episodes with a lifecycle (acute / chronic-ongoing / recurring / flare) and a daily symptom timeline forked structurally from the cycle log-day surface. The journal is a record, not a medical device — no prediction or diagnosis UI. - Register `illness` as a born-gated (opt-in / default-off) module. The gate resolver flips the disabled-allowlist default to off for born-gated keys, so the surface stays dark until the account opts in from the Modules hub. `isIllnessEnabled` + `requireIllnessEnabled` delegate to the module foundation (one source of truth) and re-stamp an illness errorCode. - Episode CRUD under /api/illness: list / create / get / edit / resolve / delete, plus a per-episode-day day-log upsert on (episodeId, date). Every route wraps apiHandler, Zod-validates with returnAllZodIssues, narrows userId from auth, builds data field-by-field, and gates the module. Free-text notes encrypt at rest through the shared Bytes codec. - Log-day sheet (cycle fork): ~8 Jackson/WURSS symptom chips 0-3 + a functional-impact group + fever + an encrypted note, one row per day. - Episode history page + a born-gated nav entry, neutral palette, status via a neutral Badge, retrospective-only copy. i18n across all six locales. - Cover the new routes in the module-route-gate inventory; unit tests for the day-log upsert, the gate enforcement, and the born-gated resolver. The /api/auth/me module map now projects the `illness` flag (openapi.yaml regenerated).
56 lines
1.8 KiB
TypeScript
56 lines
1.8 KiB
TypeScript
/**
|
|
* v1.18.1 — illness route gate.
|
|
*
|
|
* `requireIllnessEnabled` delegates to the module gate but re-stamps the
|
|
* illness-specific errorCode. Asserts the enabled pass, the disabled 403
|
|
* envelope shape, and that `isIllnessEnabled` mirrors `isModuleEnabled`.
|
|
*/
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const moduleGate = vi.hoisted(() => ({
|
|
isModuleEnabled: vi.fn(),
|
|
requireModuleEnabled: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("@/lib/modules/gate", () => moduleGate);
|
|
|
|
import {
|
|
ILLNESS_DISABLED_ERROR_CODE,
|
|
isIllnessEnabled,
|
|
requireIllnessEnabled,
|
|
} from "@/lib/illness/gate";
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
describe("isIllnessEnabled", () => {
|
|
it("delegates to isModuleEnabled('illness')", async () => {
|
|
moduleGate.isModuleEnabled.mockResolvedValue(true);
|
|
await expect(isIllnessEnabled("u1")).resolves.toBe(true);
|
|
expect(moduleGate.isModuleEnabled).toHaveBeenCalledWith("u1", "illness");
|
|
});
|
|
});
|
|
|
|
describe("requireIllnessEnabled", () => {
|
|
it("passes when the module gate passes", async () => {
|
|
moduleGate.requireModuleEnabled.mockResolvedValue({ enabled: true });
|
|
const gate = await requireIllnessEnabled("u1");
|
|
expect(gate.enabled).toBe(true);
|
|
});
|
|
|
|
it("returns a 403 with the illness errorCode when disabled", async () => {
|
|
moduleGate.requireModuleEnabled.mockResolvedValue({
|
|
enabled: false,
|
|
response: new Response(null, { status: 403 }),
|
|
});
|
|
const gate = await requireIllnessEnabled("u1");
|
|
expect(gate.enabled).toBe(false);
|
|
if (gate.enabled) throw new Error("expected disabled");
|
|
expect(gate.response.status).toBe(403);
|
|
const body = await gate.response.json();
|
|
expect(body.meta.errorCode).toBe(ILLNESS_DISABLED_ERROR_CODE);
|
|
expect(body.meta.module).toBe("illness");
|
|
});
|
|
});
|