mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 05:42:19 +00:00
Publish share-selection group membership in capabilities
GET /api/meta/capabilities dropped group membership at the wire: it shipped a flat group-id list and a flat leaf list with no boundary between them, so a native client could not tell which leaves live under which group, or which group is the fenced sensitive tier. That made it unsafe for the client to offer a "select all" affordance for any group, since it could not mirror the server's fence against bundling more than one sensitive leaf behind a single control. Add SHARE_GROUPS to the report-selection catalogue, derived from the existing REPORT_GROUPS/MEASUREMENT_LEAF_GROUP/STRUCTURED_LEAF_GROUP maps: each group now carries its member leaf ids, and the group whose id equals SENSITIVE_GROUP_ID carries sensitive: true. share.groups in the capabilities response now ships this shape instead of bare ids; share.leaves is unchanged. Update the OpenAPI schema and regenerate docs/api/openapi.yaml. Add an invariant test asserting every leaf in the flat leaves set appears in exactly one group and no group carries a leaf outside that set, plus a check that exactly one group is flagged sensitive.
This commit is contained in:
1 parent
b767cff187
commit
c9c824ca4d
6 files changed
+139
-11
No files matched your search
@@ -61,6 +61,7 @@ import { SHARE_LINK_MAX_DAYS } from "@/lib/validations/clinician-share-link";
|
||||
import {
|
||||
ALL_LEAF_IDS,
|
||||
REPORT_GROUP_ORDER,
|
||||
SENSITIVE_GROUP_ID,
|
||||
} from "@/lib/report-selection/catalogue";
|
||||
|
||||
const SESSION_OK = {
|
||||
@@ -96,7 +97,7 @@ type CapabilitiesBody = {
|
||||
maxDays: number;
|
||||
reportDownload: string[];
|
||||
selectionVersion: number;
|
||||
groups: string[];
|
||||
groups: { id: string; leaves: string[]; sensitive?: boolean }[];
|
||||
leaves: string[];
|
||||
};
|
||||
};
|
||||
@@ -187,12 +188,37 @@ describe("GET /api/meta/capabilities — drift guards", () => {
|
||||
// that was never built.
|
||||
expect(body.data.share.reportDownload).toEqual(["fhir", "pdf"]);
|
||||
expect(body.data.share.selectionVersion).toBe(2);
|
||||
expect(body.data.share.groups).toEqual([...REPORT_GROUP_ORDER]);
|
||||
expect(body.data.share.groups.map((g) => g.id)).toEqual([
|
||||
...REPORT_GROUP_ORDER,
|
||||
]);
|
||||
expect([...body.data.share.leaves].sort()).toEqual(
|
||||
[...ALL_LEAF_IDS].sort(),
|
||||
);
|
||||
});
|
||||
|
||||
it("share.groups carries membership: every leaf in exactly one group, fence flagged once", async () => {
|
||||
const res = await call();
|
||||
const body = (await res.json()) as CapabilitiesBody;
|
||||
|
||||
const seen = new Set<string>();
|
||||
for (const group of body.data.share.groups) {
|
||||
for (const leaf of group.leaves) {
|
||||
expect(seen.has(leaf), `${leaf} appears in more than one group`).toBe(
|
||||
false,
|
||||
);
|
||||
seen.add(leaf);
|
||||
}
|
||||
}
|
||||
// Every leaf in the flat set is in exactly one group, and no group holds
|
||||
// a leaf absent from the flat set — the invariant that makes a native
|
||||
// client's fence trustworthy.
|
||||
expect([...seen].sort()).toEqual([...body.data.share.leaves].sort());
|
||||
|
||||
const sensitiveGroups = body.data.share.groups.filter((g) => g.sensitive);
|
||||
expect(sensitiveGroups).toHaveLength(1);
|
||||
expect(sensitiveGroups[0]?.id).toBe(SENSITIVE_GROUP_ID);
|
||||
});
|
||||
|
||||
it("quantityTypes are sourced from the HealthKit ingest mapping", async () => {
|
||||
const res = await call();
|
||||
const body = (await res.json()) as CapabilitiesBody;
|
||||
|
||||
@@ -46,10 +46,7 @@ import {
|
||||
FHIR_SEARCH_PARAMS,
|
||||
} from "@/lib/fhir/rest";
|
||||
import { SHARE_LINK_MAX_DAYS } from "@/lib/validations/clinician-share-link";
|
||||
import {
|
||||
ALL_LEAF_IDS,
|
||||
REPORT_GROUP_ORDER,
|
||||
} from "@/lib/report-selection/catalogue";
|
||||
import { ALL_LEAF_IDS, SHARE_GROUPS } from "@/lib/report-selection/catalogue";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
@@ -135,7 +132,12 @@ export const GET = apiHandler(async () => {
|
||||
maxDays: SHARE_LINK_MAX_DAYS,
|
||||
reportDownload: ["fhir", "pdf"],
|
||||
selectionVersion: 2,
|
||||
groups: REPORT_GROUP_ORDER,
|
||||
// Each group carries its own member leaves, and the fenced tier carries
|
||||
// `sensitive: true` — the membership the panel, the PDF and the guard
|
||||
// tests already enforce, published so the native client can mirror the
|
||||
// fence instead of guessing which leaves it may bundle behind one
|
||||
// control. `leaves` stays the flat total set for back-compat.
|
||||
groups: SHARE_GROUPS,
|
||||
leaves: ALL_LEAF_IDS,
|
||||
},
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user