mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 13:01:00 +00:00
test: add sharing security principals
This commit is contained in:
1 parent
d9e68e7ee3
commit
bd6220bbba
4 files changed
+222
No files matched your search
@@ -4,6 +4,12 @@ import {
|
||||
ADMITTED_MUTATING_HANDLERS,
|
||||
SHARING_DOMAINS,
|
||||
} from "../../tests/fixtures/v137/sharing-matrix";
|
||||
import { SECURITY_PRINCIPALS } from "../../tests/fixtures/v137/security-principals";
|
||||
import { LEGACY_ACCOUNT_PAYLOADS } from "../../tests/fixtures/v137/legacy-account-payloads";
|
||||
import {
|
||||
NOTIFICATION_CHANNELS,
|
||||
NOTIFICATION_DELIVERY_MATRIX,
|
||||
} from "../../tests/fixtures/v137/notification-matrix";
|
||||
|
||||
describe("sharing and handler inventories", () => {
|
||||
it("freezes the exact sharing-domain vocabulary", () => {
|
||||
@@ -41,3 +47,53 @@ describe("sharing and handler inventories", () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("security principals and compatibility", () => {
|
||||
it("keeps actor, record, recipient, origin, grant, and job identities distinct", () => {
|
||||
const { activeDelegation, revokedDelegation } = SECURITY_PRINCIPALS;
|
||||
|
||||
expect(new Set(Object.values(activeDelegation.identities)).size).toBe(3);
|
||||
expect(activeDelegation.origin).toBe("delegated-request");
|
||||
expect(activeDelegation.grant.state).toBe("active");
|
||||
expect(revokedDelegation.grant.state).toBe("revoked");
|
||||
expect(activeDelegation.job.id).not.toBe(revokedDelegation.job.id);
|
||||
});
|
||||
|
||||
it("covers every guardian, profile, and delivery channel exactly once", () => {
|
||||
expect(NOTIFICATION_CHANNELS).toEqual([
|
||||
"apns",
|
||||
"web-push",
|
||||
"telegram",
|
||||
"ntfy",
|
||||
]);
|
||||
expect(NOTIFICATION_DELIVERY_MATRIX).toHaveLength(16);
|
||||
expect(
|
||||
new Set(
|
||||
NOTIFICATION_DELIVERY_MATRIX.map(
|
||||
({ channel, recipientUserId, recordUserId }) =>
|
||||
`${recordUserId}:${recipientUserId}:${channel}`,
|
||||
),
|
||||
).size,
|
||||
).toBe(NOTIFICATION_DELIVERY_MATRIX.length);
|
||||
expect(
|
||||
NOTIFICATION_DELIVERY_MATRIX.every(
|
||||
({ interactive, recipientUserId, recordUserId }) =>
|
||||
!interactive && recipientUserId !== recordUserId,
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("retains legacy access compatibility and fails closed for scoped payloads", () => {
|
||||
expect(LEGACY_ACCOUNT_PAYLOADS.map(({ name }) => name)).toEqual([
|
||||
"whole-read",
|
||||
"whole-write",
|
||||
"manage-as-write",
|
||||
"scoped-fail-closed",
|
||||
]);
|
||||
expect(LEGACY_ACCOUNT_PAYLOADS[2]).toMatchObject({
|
||||
access: "write",
|
||||
level: "manage",
|
||||
});
|
||||
expect(LEGACY_ACCOUNT_PAYLOADS[3]?.legacyDecoder).toBe("deny");
|
||||
});
|
||||
});
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
export interface LegacyAccountPayloadFixture {
|
||||
readonly name:
|
||||
"whole-read" | "whole-write" | "manage-as-write" | "scoped-fail-closed";
|
||||
readonly access: "read" | "write";
|
||||
readonly level: "read" | "write" | "manage";
|
||||
readonly sections: readonly string[] | null;
|
||||
readonly canWrite: boolean;
|
||||
readonly recordKind: "shared" | "managed";
|
||||
readonly legacyDecoder: "allow" | "deny";
|
||||
}
|
||||
|
||||
export const LEGACY_ACCOUNT_PAYLOADS = [
|
||||
{
|
||||
name: "whole-read",
|
||||
access: "read",
|
||||
level: "read",
|
||||
sections: null,
|
||||
canWrite: false,
|
||||
recordKind: "shared",
|
||||
legacyDecoder: "allow",
|
||||
},
|
||||
{
|
||||
name: "whole-write",
|
||||
access: "write",
|
||||
level: "write",
|
||||
sections: null,
|
||||
canWrite: true,
|
||||
recordKind: "shared",
|
||||
legacyDecoder: "allow",
|
||||
},
|
||||
{
|
||||
name: "manage-as-write",
|
||||
access: "write",
|
||||
level: "manage",
|
||||
sections: null,
|
||||
canWrite: true,
|
||||
recordKind: "managed",
|
||||
legacyDecoder: "allow",
|
||||
},
|
||||
{
|
||||
name: "scoped-fail-closed",
|
||||
access: "read",
|
||||
level: "read",
|
||||
sections: ["measurements"],
|
||||
canWrite: false,
|
||||
recordKind: "shared",
|
||||
legacyDecoder: "deny",
|
||||
},
|
||||
] as const satisfies readonly LegacyAccountPayloadFixture[];
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
export const NOTIFICATION_CHANNELS = [
|
||||
"apns",
|
||||
"web-push",
|
||||
"telegram",
|
||||
"ntfy",
|
||||
] as const;
|
||||
|
||||
export type NotificationChannel = (typeof NOTIFICATION_CHANNELS)[number];
|
||||
|
||||
const GUARDIAN_RECIPIENTS = [
|
||||
{ id: "guardian-alpha", locale: "en" },
|
||||
{ id: "guardian-beta", locale: "de" },
|
||||
] as const;
|
||||
|
||||
const MANAGED_RECORDS = [
|
||||
"managed-profile-alpha",
|
||||
"managed-profile-beta",
|
||||
] as const;
|
||||
|
||||
export interface NotificationDeliveryFixture {
|
||||
readonly recordUserId: (typeof MANAGED_RECORDS)[number];
|
||||
readonly recipientUserId: (typeof GUARDIAN_RECIPIENTS)[number]["id"];
|
||||
readonly recipientLocale: (typeof GUARDIAN_RECIPIENTS)[number]["locale"];
|
||||
readonly channel: NotificationChannel;
|
||||
readonly interactive: false;
|
||||
}
|
||||
|
||||
export const NOTIFICATION_DELIVERY_MATRIX = MANAGED_RECORDS.flatMap(
|
||||
(recordUserId) =>
|
||||
GUARDIAN_RECIPIENTS.flatMap(({ id: recipientUserId, locale }) =>
|
||||
NOTIFICATION_CHANNELS.map((channel) => ({
|
||||
recordUserId,
|
||||
recipientUserId,
|
||||
recipientLocale: locale,
|
||||
channel,
|
||||
interactive: false,
|
||||
})),
|
||||
),
|
||||
) as readonly NotificationDeliveryFixture[];
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
export type ExternalOrigin = "owner-request" | "delegated-request";
|
||||
export type GrantState = "active" | "revoked" | "not-required";
|
||||
|
||||
export interface SecurityPrincipalFixture {
|
||||
readonly identities: {
|
||||
readonly actorUserId: string;
|
||||
readonly recordUserId: string;
|
||||
readonly recipientUserId: string;
|
||||
};
|
||||
readonly origin: ExternalOrigin;
|
||||
readonly grant: {
|
||||
readonly id: string | null;
|
||||
readonly state: GrantState;
|
||||
};
|
||||
readonly job: {
|
||||
readonly id: string;
|
||||
readonly origin: ExternalOrigin;
|
||||
};
|
||||
}
|
||||
|
||||
export const SECURITY_PRINCIPALS = {
|
||||
activeDelegation: {
|
||||
identities: {
|
||||
actorUserId: "guardian-alpha",
|
||||
recordUserId: "managed-profile-alpha",
|
||||
recipientUserId: "guardian-beta",
|
||||
},
|
||||
origin: "delegated-request",
|
||||
grant: { id: "grant-active-alpha", state: "active" },
|
||||
job: { id: "job-active-alpha", origin: "delegated-request" },
|
||||
},
|
||||
revokedDelegation: {
|
||||
identities: {
|
||||
actorUserId: "guardian-alpha",
|
||||
recordUserId: "managed-profile-alpha",
|
||||
recipientUserId: "guardian-beta",
|
||||
},
|
||||
origin: "delegated-request",
|
||||
grant: { id: "grant-revoked-alpha", state: "revoked" },
|
||||
job: { id: "job-revoked-alpha", origin: "delegated-request" },
|
||||
},
|
||||
ownerProviderPositiveControl: {
|
||||
identities: {
|
||||
actorUserId: "owner-alpha",
|
||||
recordUserId: "owner-alpha",
|
||||
recipientUserId: "owner-alpha",
|
||||
},
|
||||
origin: "owner-request",
|
||||
grant: { id: null, state: "not-required" },
|
||||
job: { id: "job-owner-provider", origin: "owner-request" },
|
||||
},
|
||||
ownerIdempotencyPositiveControl: {
|
||||
identities: {
|
||||
actorUserId: "owner-beta",
|
||||
recordUserId: "owner-beta",
|
||||
recipientUserId: "owner-beta",
|
||||
},
|
||||
origin: "owner-request",
|
||||
grant: { id: null, state: "not-required" },
|
||||
job: { id: "job-owner-idempotency", origin: "owner-request" },
|
||||
},
|
||||
} as const satisfies Record<string, SecurityPrincipalFixture>;
|
||||
|
||||
export async function mutateThenRestore<T>(
|
||||
mutate: () => Promise<T>,
|
||||
restore: (result: T) => Promise<void>,
|
||||
): Promise<T> {
|
||||
let result: T;
|
||||
let completed = false;
|
||||
|
||||
try {
|
||||
result = await mutate();
|
||||
completed = true;
|
||||
return result;
|
||||
} finally {
|
||||
if (completed) await restore(result as T);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user