mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 06:06:10 +00:00
Merge branch 'v1424/cards' into release/v1.42.4
This commit is contained in:
commit
f95c2bfd41
60 files changed
+5248
-5087
No files matched your search
@@ -96,12 +96,13 @@ beforeEach(async () => {
|
||||
|
||||
describe("integration watchdog", () => {
|
||||
it("flags a normally-daily wearable gone silent, alerts once, then sends one recovery note", async () => {
|
||||
// Fourteen consecutive days of data before the window, newest 30 h ago.
|
||||
// Thirteen consecutive days of data before the window, newest 54 h ago:
|
||||
// past Oura's 48 h window (it uploads only when its phone app is opened).
|
||||
await seedDailyReadings(
|
||||
USER,
|
||||
"OURA",
|
||||
Array.from({ length: 13 }, (_, i) => i + 2),
|
||||
30,
|
||||
Array.from({ length: 13 }, (_, i) => i + 3),
|
||||
54,
|
||||
);
|
||||
vi.useFakeTimers({ toFake: ["Date"] });
|
||||
vi.setSystemTime(NOW);
|
||||
@@ -170,7 +171,7 @@ describe("integration watchdog", () => {
|
||||
});
|
||||
|
||||
it("leaves a wearable alone when its owner wears it only now and then", async () => {
|
||||
await seedDailyReadings(USER, "OURA", [3, 7, 11], 30);
|
||||
await seedDailyReadings(USER, "OURA", [3, 7, 11], 54);
|
||||
await recordSyncSuccess(USER, "oura");
|
||||
const summary = await runIntegrationWatchdog({ mode: "hourly", now: NOW });
|
||||
expect(summary.silencesFlagged).toBe(0);
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* A guardian reads the managed record's connections through the same
|
||||
* server-resolved `health` the record's owner would see, and only the
|
||||
* record's: a connection on the guardian's own account must not appear on
|
||||
* the record, and the record's connection must answer from the record's rows.
|
||||
*/
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { cookieJar, headerJar } from "./mock-next-headers";
|
||||
import { getPrismaClient, truncateAllTables, switchSessionTo } from "./setup";
|
||||
|
||||
vi.mock("next/headers", async () => {
|
||||
const { cookieJar, headerJar } = await import("./mock-next-headers");
|
||||
return {
|
||||
headers: vi.fn(async () => ({
|
||||
get: (name: string) => headerJar.get(name.toLowerCase()) ?? null,
|
||||
})),
|
||||
cookies: vi.fn(async () => ({
|
||||
get: (name: string) => {
|
||||
const value = cookieJar.get(name);
|
||||
return value ? { name, value } : undefined;
|
||||
},
|
||||
set: (name: string, value: string) => {
|
||||
cookieJar.set(name, value);
|
||||
},
|
||||
delete: (name: string) => {
|
||||
cookieJar.delete(name);
|
||||
},
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@/lib/db-compat", () => ({
|
||||
ensureDbCompatibility: vi.fn().mockResolvedValue(undefined),
|
||||
}));
|
||||
|
||||
interface ManagedIntegration {
|
||||
integration: string;
|
||||
state: string;
|
||||
health: { state: string; reason: string } | null;
|
||||
}
|
||||
|
||||
async function readIntegrations(): Promise<{
|
||||
recordId: string;
|
||||
integrations: ManagedIntegration[];
|
||||
}> {
|
||||
const { GET } = await import("@/app/api/record-settings/integrations/route");
|
||||
const res = await GET();
|
||||
expect(res.status).toBe(200);
|
||||
return (await res.json()).data;
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
await truncateAllTables(getPrismaClient());
|
||||
cookieJar.clear();
|
||||
headerJar.clear();
|
||||
});
|
||||
|
||||
describe("GET /api/record-settings/integrations", () => {
|
||||
it("publishes the record's own health per provider, never the guardian's", async () => {
|
||||
const prisma = getPrismaClient();
|
||||
const guardian = await prisma.user.create({
|
||||
data: {
|
||||
username: "health-guardian",
|
||||
email: "health-guardian@example.test",
|
||||
role: "USER",
|
||||
timezone: "Europe/Berlin",
|
||||
locale: "en",
|
||||
onboardingCompletedAt: new Date(),
|
||||
// The guardian's own Polar: must not surface on the record.
|
||||
polarAccessTokenEncrypted: "not-a-real-token",
|
||||
},
|
||||
});
|
||||
const { createManagedProfile } =
|
||||
await import("@/lib/managed-profiles/create");
|
||||
const { profile } = await createManagedProfile({
|
||||
creatorId: guardian.id,
|
||||
displayName: "Managed record",
|
||||
dateOfBirth: null,
|
||||
locale: "en",
|
||||
timezone: "UTC",
|
||||
gender: null,
|
||||
});
|
||||
// The record's own Oura, synced once.
|
||||
await prisma.user.update({
|
||||
where: { id: profile.id },
|
||||
data: { ouraAccessTokenEncrypted: "not-a-real-token" },
|
||||
});
|
||||
const { recordSyncSuccess } = await import("@/lib/integrations/status");
|
||||
await recordSyncSuccess(profile.id, "oura");
|
||||
|
||||
const session = await prisma.session.create({
|
||||
data: {
|
||||
userId: guardian.id,
|
||||
expiresAt: new Date(Date.now() + 60_000),
|
||||
},
|
||||
});
|
||||
cookieJar.set("healthlog_session", session.id);
|
||||
await switchSessionTo(session.id, profile.id);
|
||||
|
||||
const data = await readIntegrations();
|
||||
expect(data.recordId).toBe(profile.id);
|
||||
expect(data.integrations).toHaveLength(8);
|
||||
|
||||
const byKey = new Map(data.integrations.map((i) => [i.integration, i]));
|
||||
const oura = byKey.get("oura");
|
||||
expect(oura?.health).not.toBeNull();
|
||||
expect(oura?.health?.state).toBe("ok");
|
||||
// Health and the legacy state agree on "a pipe exists".
|
||||
expect(oura?.state).toBe("connected");
|
||||
|
||||
// The guardian's Polar is the guardian's, not the record's.
|
||||
expect(byKey.get("polar")?.health).toBeNull();
|
||||
for (const key of [
|
||||
"withings",
|
||||
"whoop",
|
||||
"fitbit",
|
||||
"nightscout",
|
||||
"google-health",
|
||||
"strava",
|
||||
]) {
|
||||
expect(byKey.get(key)?.health, key).toBeNull();
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user