mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 13:22:14 +00:00
fix(settings): list record content only where the grant manages it
The anamnesis page writes through requireRecordAuth("manage",
"profile"), which refuses a grant whose sections do not reach profile.
The Settings list and the navigation entry checked only the level and
the record kind, so a MANAGE grant scoped away from profile would be
offered a page whose saves are refused.
Record-content destinations now name the section they write, and are
listed only when the active record's manageable sections include it.
A destination without a named section is never listed. The shell and
the navigation read the same predicate. MANAGE grants are whole-record
today, so managed profiles and adult MANAGE shares list what they did
before.
Refs #939
This commit is contained in:
1 parent
4a49120fcf
commit
45ee70acb7
7 files changed
+224
-27
No files matched your search
@@ -25,7 +25,11 @@ const mockModulesRef: { value: Record<string, boolean> | undefined } = {
|
||||
// server has already resolved which record is active, so it applies on the SSR
|
||||
// pass and is assertable from a static render.
|
||||
const mockActiveRecordRef: {
|
||||
value: { recordKind: "managed" | "shared"; level: string } | null;
|
||||
value: {
|
||||
recordKind: "managed" | "shared";
|
||||
level: string;
|
||||
manageableDomains: string[];
|
||||
} | null;
|
||||
} = { value: null };
|
||||
vi.mock("@/hooks/use-auth", () => ({
|
||||
useAuth: () => ({
|
||||
@@ -48,6 +52,7 @@ vi.mock("@/hooks/use-auth", () => ({
|
||||
}));
|
||||
|
||||
import { I18nProvider } from "@/lib/i18n/context";
|
||||
import { delegatedDomains } from "@/lib/sharing/domain-write-support";
|
||||
import {
|
||||
SETTINGS_SECTION_SLUGS,
|
||||
SETTINGS_SECTIONS,
|
||||
@@ -60,7 +65,11 @@ function renderShell(props: {
|
||||
pathname?: string;
|
||||
locale?: "en" | "de";
|
||||
modules?: Record<string, boolean>;
|
||||
activeRecord?: { recordKind: "managed" | "shared"; level: string } | null;
|
||||
activeRecord?: {
|
||||
recordKind: "managed" | "shared";
|
||||
level: string;
|
||||
manageableDomains: string[];
|
||||
} | null;
|
||||
}) {
|
||||
mockPathnameRef.value = props.pathname ?? "/settings/account";
|
||||
mockModulesRef.value = props.modules;
|
||||
@@ -358,7 +367,11 @@ describe("<SettingsShell>", () => {
|
||||
it("offers the Modules destination to a guardian on a managed record", () => {
|
||||
const html = renderShell({
|
||||
active: "account",
|
||||
activeRecord: { recordKind: "managed", level: "manage" },
|
||||
activeRecord: {
|
||||
recordKind: "managed",
|
||||
level: "manage",
|
||||
manageableDomains: delegatedDomains("manage", null, "manage"),
|
||||
},
|
||||
});
|
||||
expect(html).toContain('href="/settings/modules"');
|
||||
// Its own configuration is not the record's, and stays away.
|
||||
@@ -372,12 +385,36 @@ describe("<SettingsShell>", () => {
|
||||
// here — both are `managed-guardian`, which an adult record never opens.
|
||||
const html = renderShell({
|
||||
active: "account",
|
||||
activeRecord: { recordKind: "shared", level: "manage" },
|
||||
activeRecord: {
|
||||
recordKind: "shared",
|
||||
level: "manage",
|
||||
manageableDomains: delegatedDomains("manage", null, "manage"),
|
||||
},
|
||||
});
|
||||
expect(html).not.toContain('href="/settings/modules"');
|
||||
expect(html).not.toContain('href="/settings/integrations"');
|
||||
expect(html).toContain('href="/settings/anamnesis"');
|
||||
});
|
||||
|
||||
it("lists nothing for a MANAGE grant that does not manage profile", () => {
|
||||
// The anamnesis forms write through `requireRecordAuth("manage",
|
||||
// "profile")`, which refuses a grant whose sections miss `profile`, so
|
||||
// the shell does not list the page. The navigation reads the same
|
||||
// predicate and offers no Settings entry either.
|
||||
const html = renderShell({
|
||||
active: "account",
|
||||
activeRecord: {
|
||||
recordKind: "shared",
|
||||
level: "manage",
|
||||
manageableDomains: ["labs"],
|
||||
},
|
||||
});
|
||||
expect(html).not.toContain('href="/settings/anamnesis"');
|
||||
expect(html).not.toContain('href="/settings/modules"');
|
||||
// Non-zero: the shell rendered, and still names the active section in
|
||||
// its heading, which it resolves from the unfiltered list.
|
||||
expect(html).toContain('id="settings-section-account-title"');
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to `account` when the pathname doesn't match a known slug", () => {
|
||||
|
||||
Reference in new issue
Block a user