mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 22:32:04 +00:00
A guardian acting on a managed profile can now connect and disconnect its Withings, WHOOP, Oura, Polar, Strava and Google Health from the profile's integration cards, and each card says whose record it connects for. The connect starts with a fenced fetch that mints a state naming the profile; the provider's callback lands tokens and data there only after checking that the browser belongs to a guardian with a live MANAGE grant on it. Without a provider app of its own, the profile borrows the guardian's, with an audit row. Withings and Google Health callbacks read the session directly, since a provider redirect cannot carry the record-session header.
71 lines
2.5 KiB
TypeScript
71 lines
2.5 KiB
TypeScript
import { readdir, readFile } from "node:fs/promises";
|
|
import { resolve } from "node:path";
|
|
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
import { MANAGED_RECORD_SETTINGS_FIELD_ALLOWLIST } from "@/lib/record-settings";
|
|
|
|
const fromRoot = (...segments: string[]) => resolve(process.cwd(), ...segments);
|
|
|
|
const RECORD_SETTINGS_ROUTE_ROOT = fromRoot("src/app/api/record-settings");
|
|
|
|
describe("record settings route guard", () => {
|
|
it("freezes the non-empty guardian-only route inventory", async () => {
|
|
const discovered = (
|
|
await readdir(RECORD_SETTINGS_ROUTE_ROOT, {
|
|
recursive: true,
|
|
})
|
|
)
|
|
.filter((entry) => entry.endsWith("route.ts"))
|
|
.map((entry) => `app/api/record-settings/${entry}`)
|
|
.sort();
|
|
|
|
expect(discovered).toHaveLength(5);
|
|
expect(discovered).toEqual([
|
|
"app/api/record-settings/[family]/route.ts",
|
|
"app/api/record-settings/integrations/[provider]/connect/route.ts",
|
|
"app/api/record-settings/integrations/[provider]/disconnect/route.ts",
|
|
"app/api/record-settings/integrations/route.ts",
|
|
"app/api/record-settings/route.ts",
|
|
]);
|
|
});
|
|
|
|
it("resolves every record-settings route through the Guardian fence", async () => {
|
|
const routes = [
|
|
"route.ts",
|
|
"integrations/route.ts",
|
|
"integrations/[provider]/connect/route.ts",
|
|
"integrations/[provider]/disconnect/route.ts",
|
|
"[family]/route.ts",
|
|
] as const;
|
|
const sources = await Promise.all(
|
|
routes.map((route) =>
|
|
readFile(resolve(RECORD_SETTINGS_ROUTE_ROOT, route), "utf8"),
|
|
),
|
|
);
|
|
|
|
for (const source of sources) {
|
|
expect(source).toContain("requireGuardianAuth");
|
|
expect(source).toContain("resolveGuardianRecordSettingsAccess");
|
|
}
|
|
});
|
|
|
|
it("makes the field-specific route exhaustive over the approved DTO families", async () => {
|
|
const source = await readFile(
|
|
resolve(RECORD_SETTINGS_ROUTE_ROOT, "[family]/route.ts"),
|
|
"utf8",
|
|
);
|
|
|
|
const families = Object.keys(MANAGED_RECORD_SETTINGS_FIELD_ALLOWLIST);
|
|
expect(families.length).toBeGreaterThan(0);
|
|
for (const family of families) {
|
|
expect(source).toContain(`case \"${family}\"`);
|
|
}
|
|
expect(source).toContain("if (!isManagedRecordSettingsFamily(rawFamily))");
|
|
expect(source).toContain('apiError("Settings family not found", 404)');
|
|
expect(
|
|
source.indexOf("isManagedRecordSettingsFamily(rawFamily)"),
|
|
).toBeLessThan(source.indexOf("const context = await requireGuardianAuth"));
|
|
});
|
|
});
|