Files
HealthLog/src/lib/day/__tests__/sections.test.ts
T
Marc-André Bombeck f950dffad4 fix(sharing): keep life events to their owner
Life events rode the profile sharing domain, so a profile share, a
legacy whole-record share and a MANAGE grant all read them, and MANAGE
could write them. No share level was ever worded for them, so in this
release they are the owner's only.

The life-event routes take a bare requireAuth, which refuses under an
acting-account switch. The day section and the timeline's life lane map
to no domain, so a delegate sees the section named not_shared and never
gets the lane, its readiness row or the day index marks. The profile
consent copy no longer names life events, the sharing guard and the
mutation inventory drop the four entries, and the OpenAPI descriptions
say owner-only.
2026-10-08 22:37:57 +02:00

119 lines
3.8 KiB
TypeScript

/**
* Which sections of a day a caller sees (v1.42, #613): a switched-off module
* leaves without a word, a section the grant does not cover is named once,
* and the environment rows are the owner's alone.
*/
import { describe, expect, it } from "vitest";
import { DAY_SECTION_KEYS } from "@/lib/day/contract";
import { MODULE_KEYS, type ModuleKey } from "@/lib/modules/registry";
import { SHARE_DOMAINS } from "@/lib/sharing/scope";
import {
DAY_SECTION_SHARE_DOMAIN,
measurementTypeVisible,
resolveDayAccessFrom,
} from "../sections";
const ALL_ON = Object.fromEntries(MODULE_KEYS.map((k) => [k, true])) as Record<
ModuleKey,
boolean
>;
describe("day access", () => {
it("opens every section to the owner", () => {
const access = resolveDayAccessFrom({
modules: ALL_ON,
domainVisible: () => true,
owner: true,
});
expect([...access.readable].sort()).toEqual([...DAY_SECTION_KEYS].sort());
expect(access.notShared).toEqual([]);
});
it("leaves a switched-off module's section out without naming it", () => {
const access = resolveDayAccessFrom({
modules: { ...ALL_ON, mood: false, timeline: false },
domainVisible: () => true,
owner: true,
});
expect(access.readable.has("mood")).toBe(false);
expect(access.readable.has("lifeEvents")).toBe(false);
expect(access.notShared).toEqual([]);
expect(access.moduleOff).toEqual(
expect.arrayContaining(["mood", "lifeEvents"]),
);
});
it("reads only the domains a scoped grant covers and names the rest", () => {
const access = resolveDayAccessFrom({
modules: ALL_ON,
domainVisible: (d) => d === "measurements",
owner: false,
});
for (const section of DAY_SECTION_KEYS) {
const domain = DAY_SECTION_SHARE_DOMAIN[section];
expect(access.readable.has(section), section).toBe(
domain === "measurements",
);
}
expect(access.notShared).toEqual(
expect.arrayContaining([
"medications",
"mood",
"lifeEvents",
"environment",
]),
);
});
it("keeps the environment rows and the life events for the owner even under a full grant", () => {
const access = resolveDayAccessFrom({
modules: ALL_ON,
domainVisible: () => true,
owner: false,
});
expect(access.readable.has("environment")).toBe(false);
expect(access.readable.has("lifeEvents")).toBe(false);
expect(access.notShared).toEqual(["environment", "lifeEvents"]);
});
it("opens the life events to the owner", () => {
const access = resolveDayAccessFrom({
modules: ALL_ON,
domainVisible: () => true,
owner: true,
});
expect(access.readable.has("lifeEvents")).toBe(true);
});
it("maps every section to a real sharing domain or to the owner", () => {
for (const section of DAY_SECTION_KEYS) {
const domain = DAY_SECTION_SHARE_DOMAIN[section];
if (domain !== null) expect(SHARE_DOMAINS).toContain(domain);
}
// Owner-only in v1.42: no share level reaches them.
expect(DAY_SECTION_SHARE_DOMAIN.lifeEvents).toBeNull();
});
it("narrows readings type by type through their own module", () => {
expect(measurementTypeVisible("WEIGHT", { ...ALL_ON })).toBe(true);
expect(
measurementTypeVisible("HEART_RATE_VARIABILITY", {
...ALL_ON,
recovery: false,
}),
).toBe(false);
});
});
describe("life-event coverage", () => {
it("reaches to the end of a coarse date", async () => {
const { lastDayCovered } = await import("@/lib/life-events/dates");
expect(lastDayCovered("2023-09-14", "DAY")).toBe("2023-09-14");
expect(lastDayCovered("2024-02-01", "MONTH")).toBe("2024-02-29");
expect(lastDayCovered("2023-12-01", "MONTH")).toBe("2023-12-31");
expect(lastDayCovered("2019-01-01", "YEAR")).toBe("2019-12-31");
});
});