Files
HealthLog/src/__tests__/sharing-boundary-copy.test.ts
T
Marc-André Bombeck 51f7d38b3c test(i18n): give the per-locale copy guards their pt-BR vocabulary
The delete-confirmation, sleep-debt, mood-prognosis, MCP-token and
AI-consent guards match each locale against its own phrases, and the
sharing-boundary guard against the word for account; pt-BR now has
them. The locale-count floors read nine, and the managed-profile picker
test counts the locale list instead of a literal.
2026-10-22 14:57:32 +02:00

134 lines
5.5 KiB
TypeScript

import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { inviteGrantSchema } from "@/lib/validations/account-sharing";
import {
SETTINGS_DESTINATION_INVENTORY,
isManageDelegateSettingsDestination,
} from "@/lib/record-settings/classification";
/**
* What the product SAYS a manage grant cannot reach agrees with what it cannot
* reach.
*
* Two sentences make that promise, and both were written when it was true:
*
* * the `access` field's description in the sharing request schema, which is
* generated into `docs/api/openapi.yaml` and read by the native client —
* it said MANAGE "never reaches … settings";
* * `recordSharing.unavailable.description`, the paragraph a delegate meets
* on a page sharing does not cover — it said settings "stay with the
* person who owns the account".
*
* v1.37.0 opened one Settings destination to a manage grant, and both
* sentences became false in the most expensive place a sentence can be false:
* one is a consent screen, the other is a published contract. A promise that
* has quietly stopped holding is worse than no promise, because somebody has
* already decided based on it.
*
* So the copy is checked against the classification rather than against
* itself. If a second destination is ever opened, or the anamnesis one is
* closed again, the sentence that describes the boundary has to move with it.
*
* What this cannot check is whether the wording is GOOD. It checks that the
* absolute claim is gone, that the boundary is named the way the code draws it
* (record content versus account configuration), and that all six locales say
* it — which is the part that rots silently.
*/
const ROOT = process.cwd();
const LOCALES = [
"de",
"en",
"es",
"fr",
"it",
"pl",
"ko",
"nl",
"pt-BR",
] as const;
/** The published description of the level, off the Zod schema itself. */
function accessDescription(): string {
const shape = (
inviteGrantSchema as unknown as {
_def: { shape: Record<string, { description?: string }> };
}
)._def.shape;
return shape.access?.description ?? "";
}
function description(locale: string): string {
const bundle = JSON.parse(
readFileSync(join(ROOT, `messages/${locale}.json`), "utf8"),
) as {
recordSharing: { unavailable: { description: string } };
};
return bundle.recordSharing.unavailable.description;
}
describe("the published boundary matches the one the code draws", () => {
const openDestinations = Object.keys(SETTINGS_DESTINATION_INVENTORY).filter(
isManageDelegateSettingsDestination,
);
it("has something to be wrong about", () => {
// The pin every leg below rests on. With no manage-reachable destination
// the old absolute sentence would have been true and this whole file would
// be asserting a style preference.
expect(openDestinations).not.toHaveLength(0);
expect(openDestinations).toContain("anamnesis");
expect(accessDescription().length).toBeGreaterThan(200);
});
it("does not claim a manage grant reaches no settings at all", () => {
const published = accessDescription();
// The exact clause that went stale, and the shape of any replacement for
// it. `settings` may appear — it has to, to say which parts stay with the
// owner — but not as part of a blanket denial.
expect(published).not.toMatch(/never reaches[^.]*\bsettings\b/i);
expect(published).not.toMatch(/no access to settings/i);
});
it("names the boundary the classification actually draws", () => {
// Not a synonym check for its own sake: "record content versus account
// configuration" IS the rule `isManageDelegateSettingsDestination`
// implements, and a description that described some other boundary would
// be a second, disagreeing specification.
expect(accessDescription()).toMatch(
/record content versus account configuration/i,
);
// And the destination that was opened is named, so a reader of the
// contract can tell which part of the record it means.
expect(accessDescription()).toMatch(/allergies, family history/i);
});
it("keeps the refusal paragraph honest in every locale", () => {
// The delegate-facing half. It is shown on a page sharing does NOT cover,
// so a sentence that over-claims there is read at the exact moment
// somebody is working out where the edge is.
const missing: string[] = [];
for (const locale of LOCALES) {
const text = description(locale);
expect(text.length, locale).toBeGreaterThan(40);
// Every locale has to acknowledge the health background, because that
// is the part the old sentence implicitly denied. Matched on the stem
// rather than the English spelling, and short enough to survive the
// accent: the section is "Anamnèse" in French and "Anamneza" in Polish,
// so anything past "anamn" is already language-specific and a matcher
// tuned to one of them reports the others as broken.
// Korean shares no Latin stem for either concept, so it is matched on
// its own words: "문진" is the anamnesis section, "계정" the account.
if (!/anamn|문진/i.test(text)) missing.push(locale);
// And it must still say the account itself stays with its owner —
// dropping that would fix one over-claim by removing the boundary.
expect(text, locale).toMatch(
/konto|account|compte|cuenta|konta|koncie|conta|계정/i,
);
}
expect(missing).toEqual([]);
});
});