mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
test: freeze sharing authorization inventories
This commit is contained in:
1 parent
e05bad0ec2
commit
d9e68e7ee3
2 files changed
+630
No files matched your search
@@ -0,0 +1,43 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
ADMITTED_MUTATING_HANDLERS,
|
||||
SHARING_DOMAINS,
|
||||
} from "../../tests/fixtures/v137/sharing-matrix";
|
||||
|
||||
describe("sharing and handler inventories", () => {
|
||||
it("freezes the exact sharing-domain vocabulary", () => {
|
||||
expect(SHARING_DOMAINS).toEqual([
|
||||
"measurements",
|
||||
"medications",
|
||||
"labs",
|
||||
"profile",
|
||||
"illness",
|
||||
"mind",
|
||||
"cycle",
|
||||
"documents",
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps every admitted mutation uniquely addressable and fully controlled", () => {
|
||||
expect(ADMITTED_MUTATING_HANDLERS).toHaveLength(62);
|
||||
expect(
|
||||
new Set(
|
||||
ADMITTED_MUTATING_HANDLERS.map(
|
||||
({ route, action }) => `${route}#${action}`,
|
||||
),
|
||||
).size,
|
||||
).toBe(ADMITTED_MUTATING_HANDLERS.length);
|
||||
|
||||
for (const handler of ADMITTED_MUTATING_HANDLERS) {
|
||||
expect(handler.ownRecord.expected).toBe("allow");
|
||||
expect(handler.deniedLevel.expected).toBe("deny");
|
||||
expect(handler.admittedLevel.expected).toBe("allow");
|
||||
expect(handler.actorAttribution.field).toBe("actorUserId");
|
||||
expect(handler.resultingEffect.expected).toBe("applied-once");
|
||||
expect(handler.encryptedFieldCarveOut).toBe(
|
||||
"never-assert-decrypted-field-content",
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
+587
@@ -0,0 +1,587 @@
|
||||
export const SHARING_DOMAINS = [
|
||||
"measurements",
|
||||
"medications",
|
||||
"labs",
|
||||
"profile",
|
||||
"illness",
|
||||
"mind",
|
||||
"cycle",
|
||||
"documents",
|
||||
] as const;
|
||||
|
||||
export type SharingDomain = (typeof SHARING_DOMAINS)[number];
|
||||
export type SharingScope = SharingDomain | "record";
|
||||
export type DelegatedLevel = "write" | "manage";
|
||||
export type MutatingAction = "POST" | "PUT" | "PATCH" | "DELETE";
|
||||
export type RecordKind = "shared" | "managed";
|
||||
export type MutationEffect =
|
||||
"create" | "update" | "delete" | "restore" | "upsert" | "export";
|
||||
|
||||
export interface MutatingHandlerContract {
|
||||
readonly handlerModule: `app/api/${string}/route.ts`;
|
||||
readonly route: `/api/${string}`;
|
||||
readonly action: MutatingAction;
|
||||
readonly recordKind: RecordKind;
|
||||
readonly scope: SharingScope;
|
||||
readonly level: DelegatedLevel;
|
||||
readonly ownRecord: {
|
||||
readonly actor: "record-owner";
|
||||
readonly expected: "allow";
|
||||
};
|
||||
readonly deniedLevel: {
|
||||
readonly actor: "delegate";
|
||||
readonly level: "read" | "write";
|
||||
readonly expected: "deny";
|
||||
};
|
||||
readonly admittedLevel: {
|
||||
readonly actor: "delegate";
|
||||
readonly expected: "allow";
|
||||
};
|
||||
readonly actorAttribution: {
|
||||
readonly field: "actorUserId";
|
||||
readonly expected: "acting-delegate";
|
||||
};
|
||||
readonly resultingEffect: {
|
||||
readonly expected: "applied-once";
|
||||
readonly mutation: MutationEffect;
|
||||
};
|
||||
readonly sideEffect: {
|
||||
readonly recordTarget: "resolved-record";
|
||||
readonly delegatedProviderEgress: "deny";
|
||||
};
|
||||
readonly encryptedFieldCarveOut: "never-assert-decrypted-field-content";
|
||||
}
|
||||
|
||||
function handler(
|
||||
handlerModule: `app/api/${string}/route.ts`,
|
||||
route: `/api/${string}`,
|
||||
action: MutatingAction,
|
||||
scope: SharingScope,
|
||||
level: DelegatedLevel,
|
||||
mutation: MutationEffect,
|
||||
): MutatingHandlerContract {
|
||||
return {
|
||||
handlerModule,
|
||||
route,
|
||||
action,
|
||||
recordKind: "shared",
|
||||
scope,
|
||||
level,
|
||||
ownRecord: { actor: "record-owner", expected: "allow" },
|
||||
deniedLevel: {
|
||||
actor: "delegate",
|
||||
level: level === "write" ? "read" : "write",
|
||||
expected: "deny",
|
||||
},
|
||||
admittedLevel: { actor: "delegate", expected: "allow" },
|
||||
actorAttribution: {
|
||||
field: "actorUserId",
|
||||
expected: "acting-delegate",
|
||||
},
|
||||
resultingEffect: { expected: "applied-once", mutation },
|
||||
sideEffect: {
|
||||
recordTarget: "resolved-record",
|
||||
delegatedProviderEgress: "deny",
|
||||
},
|
||||
encryptedFieldCarveOut: "never-assert-decrypted-field-content",
|
||||
};
|
||||
}
|
||||
|
||||
export const ADMITTED_MUTATING_HANDLERS = [
|
||||
handler(
|
||||
"app/api/measurements/route.ts",
|
||||
"/api/measurements",
|
||||
"POST",
|
||||
"measurements",
|
||||
"write",
|
||||
"create",
|
||||
),
|
||||
handler(
|
||||
"app/api/labs/route.ts",
|
||||
"/api/labs",
|
||||
"POST",
|
||||
"labs",
|
||||
"write",
|
||||
"create",
|
||||
),
|
||||
handler(
|
||||
"app/api/biomarkers/route.ts",
|
||||
"/api/biomarkers",
|
||||
"POST",
|
||||
"labs",
|
||||
"write",
|
||||
"create",
|
||||
),
|
||||
handler(
|
||||
"app/api/illness/episodes/route.ts",
|
||||
"/api/illness/episodes",
|
||||
"POST",
|
||||
"illness",
|
||||
"write",
|
||||
"create",
|
||||
),
|
||||
handler(
|
||||
"app/api/medications/[id]/side-effects/route.ts",
|
||||
"/api/medications/[id]/side-effects",
|
||||
"POST",
|
||||
"medications",
|
||||
"write",
|
||||
"create",
|
||||
),
|
||||
handler(
|
||||
"app/api/medications/route.ts",
|
||||
"/api/medications",
|
||||
"POST",
|
||||
"medications",
|
||||
"write",
|
||||
"create",
|
||||
),
|
||||
handler(
|
||||
"app/api/medications/intake/route.ts",
|
||||
"/api/medications/intake",
|
||||
"POST",
|
||||
"medications",
|
||||
"write",
|
||||
"create",
|
||||
),
|
||||
handler(
|
||||
"app/api/medications/[id]/intake/route.ts",
|
||||
"/api/medications/[id]/intake",
|
||||
"POST",
|
||||
"medications",
|
||||
"write",
|
||||
"create",
|
||||
),
|
||||
handler(
|
||||
"app/api/measurements/[id]/route.ts",
|
||||
"/api/measurements/[id]",
|
||||
"PUT",
|
||||
"measurements",
|
||||
"manage",
|
||||
"update",
|
||||
),
|
||||
handler(
|
||||
"app/api/measurements/[id]/route.ts",
|
||||
"/api/measurements/[id]",
|
||||
"DELETE",
|
||||
"measurements",
|
||||
"manage",
|
||||
"delete",
|
||||
),
|
||||
handler(
|
||||
"app/api/measurements/bulk-delete/route.ts",
|
||||
"/api/measurements/bulk-delete",
|
||||
"POST",
|
||||
"measurements",
|
||||
"manage",
|
||||
"delete",
|
||||
),
|
||||
handler(
|
||||
"app/api/measurements/restore/route.ts",
|
||||
"/api/measurements/restore",
|
||||
"POST",
|
||||
"measurements",
|
||||
"manage",
|
||||
"restore",
|
||||
),
|
||||
handler(
|
||||
"app/api/measurement-reminders/route.ts",
|
||||
"/api/measurement-reminders",
|
||||
"POST",
|
||||
"measurements",
|
||||
"manage",
|
||||
"create",
|
||||
),
|
||||
handler(
|
||||
"app/api/measurement-reminders/[id]/route.ts",
|
||||
"/api/measurement-reminders/[id]",
|
||||
"PATCH",
|
||||
"measurements",
|
||||
"manage",
|
||||
"update",
|
||||
),
|
||||
handler(
|
||||
"app/api/measurement-reminders/[id]/route.ts",
|
||||
"/api/measurement-reminders/[id]",
|
||||
"DELETE",
|
||||
"measurements",
|
||||
"manage",
|
||||
"delete",
|
||||
),
|
||||
handler(
|
||||
"app/api/measurement-reminders/[id]/complete/route.ts",
|
||||
"/api/measurement-reminders/[id]/complete",
|
||||
"POST",
|
||||
"measurements",
|
||||
"manage",
|
||||
"update",
|
||||
),
|
||||
handler(
|
||||
"app/api/measurement-reminders/[id]/satisfy/route.ts",
|
||||
"/api/measurement-reminders/[id]/satisfy",
|
||||
"POST",
|
||||
"measurements",
|
||||
"manage",
|
||||
"update",
|
||||
),
|
||||
handler(
|
||||
"app/api/labs/[id]/route.ts",
|
||||
"/api/labs/[id]",
|
||||
"PUT",
|
||||
"labs",
|
||||
"manage",
|
||||
"update",
|
||||
),
|
||||
handler(
|
||||
"app/api/labs/[id]/route.ts",
|
||||
"/api/labs/[id]",
|
||||
"DELETE",
|
||||
"labs",
|
||||
"manage",
|
||||
"delete",
|
||||
),
|
||||
handler(
|
||||
"app/api/labs/restore/route.ts",
|
||||
"/api/labs/restore",
|
||||
"POST",
|
||||
"labs",
|
||||
"manage",
|
||||
"restore",
|
||||
),
|
||||
handler(
|
||||
"app/api/biomarkers/[id]/route.ts",
|
||||
"/api/biomarkers/[id]",
|
||||
"PUT",
|
||||
"labs",
|
||||
"manage",
|
||||
"update",
|
||||
),
|
||||
handler(
|
||||
"app/api/allergies/route.ts",
|
||||
"/api/allergies",
|
||||
"POST",
|
||||
"profile",
|
||||
"manage",
|
||||
"create",
|
||||
),
|
||||
handler(
|
||||
"app/api/allergies/[id]/route.ts",
|
||||
"/api/allergies/[id]",
|
||||
"PATCH",
|
||||
"profile",
|
||||
"manage",
|
||||
"update",
|
||||
),
|
||||
handler(
|
||||
"app/api/allergies/[id]/route.ts",
|
||||
"/api/allergies/[id]",
|
||||
"DELETE",
|
||||
"profile",
|
||||
"manage",
|
||||
"delete",
|
||||
),
|
||||
handler(
|
||||
"app/api/family-history/route.ts",
|
||||
"/api/family-history",
|
||||
"POST",
|
||||
"profile",
|
||||
"manage",
|
||||
"create",
|
||||
),
|
||||
handler(
|
||||
"app/api/family-history/[id]/route.ts",
|
||||
"/api/family-history/[id]",
|
||||
"PATCH",
|
||||
"profile",
|
||||
"manage",
|
||||
"update",
|
||||
),
|
||||
handler(
|
||||
"app/api/family-history/[id]/route.ts",
|
||||
"/api/family-history/[id]",
|
||||
"DELETE",
|
||||
"profile",
|
||||
"manage",
|
||||
"delete",
|
||||
),
|
||||
handler(
|
||||
"app/api/illness/episodes/[id]/route.ts",
|
||||
"/api/illness/episodes/[id]",
|
||||
"PATCH",
|
||||
"illness",
|
||||
"manage",
|
||||
"update",
|
||||
),
|
||||
handler(
|
||||
"app/api/illness/episodes/[id]/route.ts",
|
||||
"/api/illness/episodes/[id]",
|
||||
"DELETE",
|
||||
"illness",
|
||||
"manage",
|
||||
"delete",
|
||||
),
|
||||
handler(
|
||||
"app/api/illness/episodes/[id]/resolve/route.ts",
|
||||
"/api/illness/episodes/[id]/resolve",
|
||||
"PATCH",
|
||||
"illness",
|
||||
"manage",
|
||||
"update",
|
||||
),
|
||||
handler(
|
||||
"app/api/illness/episodes/[id]/restore/route.ts",
|
||||
"/api/illness/episodes/[id]/restore",
|
||||
"POST",
|
||||
"illness",
|
||||
"manage",
|
||||
"restore",
|
||||
),
|
||||
handler(
|
||||
"app/api/illness/episodes/[id]/day-logs/route.ts",
|
||||
"/api/illness/episodes/[id]/day-logs",
|
||||
"POST",
|
||||
"illness",
|
||||
"manage",
|
||||
"upsert",
|
||||
),
|
||||
handler(
|
||||
"app/api/medications/[id]/route.ts",
|
||||
"/api/medications/[id]",
|
||||
"PUT",
|
||||
"medications",
|
||||
"manage",
|
||||
"update",
|
||||
),
|
||||
handler(
|
||||
"app/api/medications/[id]/schedule-revisions/route.ts",
|
||||
"/api/medications/[id]/schedule-revisions",
|
||||
"POST",
|
||||
"medications",
|
||||
"manage",
|
||||
"create",
|
||||
),
|
||||
handler(
|
||||
"app/api/medications/[id]/schedule-revisions/[revisionId]/route.ts",
|
||||
"/api/medications/[id]/schedule-revisions/[revisionId]",
|
||||
"PATCH",
|
||||
"medications",
|
||||
"manage",
|
||||
"update",
|
||||
),
|
||||
handler(
|
||||
"app/api/medications/[id]/schedule-revisions/[revisionId]/route.ts",
|
||||
"/api/medications/[id]/schedule-revisions/[revisionId]",
|
||||
"DELETE",
|
||||
"medications",
|
||||
"manage",
|
||||
"delete",
|
||||
),
|
||||
handler(
|
||||
"app/api/medications/[id]/inventory/route.ts",
|
||||
"/api/medications/[id]/inventory",
|
||||
"POST",
|
||||
"medications",
|
||||
"manage",
|
||||
"create",
|
||||
),
|
||||
handler(
|
||||
"app/api/medications/[id]/inventory/[itemId]/route.ts",
|
||||
"/api/medications/[id]/inventory/[itemId]",
|
||||
"PATCH",
|
||||
"medications",
|
||||
"manage",
|
||||
"update",
|
||||
),
|
||||
handler(
|
||||
"app/api/medications/[id]/inventory/[itemId]/route.ts",
|
||||
"/api/medications/[id]/inventory/[itemId]",
|
||||
"DELETE",
|
||||
"medications",
|
||||
"manage",
|
||||
"delete",
|
||||
),
|
||||
handler(
|
||||
"app/api/medications/[id]/side-effects/[logId]/route.ts",
|
||||
"/api/medications/[id]/side-effects/[logId]",
|
||||
"DELETE",
|
||||
"medications",
|
||||
"manage",
|
||||
"delete",
|
||||
),
|
||||
handler(
|
||||
"app/api/medications/[id]/glp1/route.ts",
|
||||
"/api/medications/[id]/glp1",
|
||||
"POST",
|
||||
"medications",
|
||||
"manage",
|
||||
"create",
|
||||
),
|
||||
handler(
|
||||
"app/api/medications/[id]/intake/[eventId]/route.ts",
|
||||
"/api/medications/[id]/intake/[eventId]",
|
||||
"PUT",
|
||||
"medications",
|
||||
"manage",
|
||||
"update",
|
||||
),
|
||||
handler(
|
||||
"app/api/medications/[id]/intake/[eventId]/route.ts",
|
||||
"/api/medications/[id]/intake/[eventId]",
|
||||
"DELETE",
|
||||
"medications",
|
||||
"manage",
|
||||
"delete",
|
||||
),
|
||||
handler(
|
||||
"app/api/medications/[id]/intake/bulk-delete/route.ts",
|
||||
"/api/medications/[id]/intake/bulk-delete",
|
||||
"POST",
|
||||
"medications",
|
||||
"manage",
|
||||
"delete",
|
||||
),
|
||||
handler(
|
||||
"app/api/medications/[id]/intake/purge/route.ts",
|
||||
"/api/medications/[id]/intake/purge",
|
||||
"DELETE",
|
||||
"medications",
|
||||
"manage",
|
||||
"delete",
|
||||
),
|
||||
handler(
|
||||
"app/api/medications/[id]/intake/import/route.ts",
|
||||
"/api/medications/[id]/intake/import",
|
||||
"POST",
|
||||
"medications",
|
||||
"manage",
|
||||
"create",
|
||||
),
|
||||
handler(
|
||||
"app/api/medications/intake/dose-history-import/route.ts",
|
||||
"/api/medications/intake/dose-history-import",
|
||||
"POST",
|
||||
"medications",
|
||||
"manage",
|
||||
"create",
|
||||
),
|
||||
handler(
|
||||
"app/api/mood-entries/route.ts",
|
||||
"/api/mood-entries",
|
||||
"POST",
|
||||
"mind",
|
||||
"manage",
|
||||
"create",
|
||||
),
|
||||
handler(
|
||||
"app/api/mood-entries/[id]/route.ts",
|
||||
"/api/mood-entries/[id]",
|
||||
"PUT",
|
||||
"mind",
|
||||
"manage",
|
||||
"update",
|
||||
),
|
||||
handler(
|
||||
"app/api/mood-entries/[id]/route.ts",
|
||||
"/api/mood-entries/[id]",
|
||||
"DELETE",
|
||||
"mind",
|
||||
"manage",
|
||||
"delete",
|
||||
),
|
||||
handler(
|
||||
"app/api/mood-entries/bulk-delete/route.ts",
|
||||
"/api/mood-entries/bulk-delete",
|
||||
"POST",
|
||||
"mind",
|
||||
"manage",
|
||||
"delete",
|
||||
),
|
||||
handler(
|
||||
"app/api/mood-entries/restore/route.ts",
|
||||
"/api/mood-entries/restore",
|
||||
"POST",
|
||||
"mind",
|
||||
"manage",
|
||||
"restore",
|
||||
),
|
||||
handler(
|
||||
"app/api/mental-health/assessments/route.ts",
|
||||
"/api/mental-health/assessments",
|
||||
"POST",
|
||||
"mind",
|
||||
"manage",
|
||||
"create",
|
||||
),
|
||||
handler(
|
||||
"app/api/cycle/cycles/[id]/route.ts",
|
||||
"/api/cycle/cycles/[id]",
|
||||
"DELETE",
|
||||
"cycle",
|
||||
"manage",
|
||||
"delete",
|
||||
),
|
||||
handler(
|
||||
"app/api/cycle/day-logs/route.ts",
|
||||
"/api/cycle/day-logs",
|
||||
"POST",
|
||||
"cycle",
|
||||
"manage",
|
||||
"upsert",
|
||||
),
|
||||
handler(
|
||||
"app/api/cycle/day-logs/[id]/route.ts",
|
||||
"/api/cycle/day-logs/[id]",
|
||||
"PATCH",
|
||||
"cycle",
|
||||
"manage",
|
||||
"update",
|
||||
),
|
||||
handler(
|
||||
"app/api/cycle/day-logs/[id]/route.ts",
|
||||
"/api/cycle/day-logs/[id]",
|
||||
"DELETE",
|
||||
"cycle",
|
||||
"manage",
|
||||
"delete",
|
||||
),
|
||||
handler(
|
||||
"app/api/cycle/period/route.ts",
|
||||
"/api/cycle/period",
|
||||
"POST",
|
||||
"cycle",
|
||||
"manage",
|
||||
"update",
|
||||
),
|
||||
handler(
|
||||
"app/api/cycle/symptoms/custom/route.ts",
|
||||
"/api/cycle/symptoms/custom",
|
||||
"POST",
|
||||
"cycle",
|
||||
"manage",
|
||||
"create",
|
||||
),
|
||||
handler(
|
||||
"app/api/nutrients/water/route.ts",
|
||||
"/api/nutrients/water",
|
||||
"POST",
|
||||
"measurements",
|
||||
"manage",
|
||||
"upsert",
|
||||
),
|
||||
handler(
|
||||
"app/api/insights/patterns/[id]/route.ts",
|
||||
"/api/insights/patterns/[id]",
|
||||
"PATCH",
|
||||
"record",
|
||||
"manage",
|
||||
"update",
|
||||
),
|
||||
handler(
|
||||
"app/api/export/health-record/route.ts",
|
||||
"/api/export/health-record",
|
||||
"POST",
|
||||
"record",
|
||||
"manage",
|
||||
"export",
|
||||
),
|
||||
] as const satisfies readonly MutatingHandlerContract[];
|
||||
Reference in new issue
Block a user