mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
The eight journeys were enumerated and one of them was wired. The other
seven existed as claims in a fixture file, which is the gap the enumeration
was written to close: a browser suite reports what it ran, never what it did
not, and a journey nobody wired reads exactly like a journey that passed.
All eight are wired now, and the list is checked in both directions — an
enumerated journey missing from the wiring fails, not just a wiring entry
naming a journey nobody declared. Before this, a subset was legal, so a
dropped journey and a not-yet-written one looked the same.
A journey names more than one file where more than one file carries it. J1
claims a grant runs through invite, accept, switch, expiry, revoke and a
direct URL; the browser proves the switch, what each level opens and the
direct-URL refusal, and the transitions either side are database facts
proved against real Postgres. Folding that into one file would mean a
browser spec driving expiry through the clock or an integration file
asserting what a page renders, and both are worse tests than the two that
exist.
Three journeys ride Playwright and five ride Vitest, so a green run of this
guard means all eight are claimed and here is which have executed in the
ordinary gate — never that all eight passed. The runner is derived from the
path rather than trusted, so it cannot become a label set to whichever value
made the split read better.
The extractor now reads `it(` as well as `test(`, and the parameterised
`it.each(table)("title")` form alongside the single-call one. It saw neither
before: five of the journeys are proved by Vitest files, every per-file leg
is over a matched set, and an empty match set satisfied them all. The
synthetic spec that proves the parse grew a Vitest half, and it is what
caught the parameterised form being invisible. What the matcher still cannot
see is written into its own doc comment.
404 lines
12 KiB
TypeScript
404 lines
12 KiB
TypeScript
/**
|
|
* Provider work keeps the record subject and initiating authority together.
|
|
*
|
|
* A shared-record mutation can invalidate a generated assessment. The queue
|
|
* boundary must therefore refuse delegate work before it is admitted and the
|
|
* worker must refuse it again if an invalid payload reaches dispatch. These
|
|
* cases exercise both boundaries with the live record resolver and grants.
|
|
*/
|
|
import { NextRequest, NextResponse } from "next/server";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
import { cookieJar, headerJar } from "./mock-next-headers";
|
|
import { getPrismaClient, truncateAllTables } from "./setup";
|
|
|
|
const send = vi.fn(async () => "provider-origin-job");
|
|
|
|
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;
|
|
},
|
|
})),
|
|
};
|
|
});
|
|
|
|
vi.mock("@/lib/jobs/boss-instance", () => ({
|
|
getGlobalBoss: vi.fn(() => ({ send })),
|
|
}));
|
|
|
|
vi.mock("@/lib/db-compat", () => ({
|
|
ensureDbCompatibility: vi.fn().mockResolvedValue(undefined),
|
|
}));
|
|
|
|
vi.mock("@/lib/feature-flags", () => ({
|
|
getAssistantFlags: vi.fn().mockResolvedValue({
|
|
briefing: true,
|
|
insightStatus: false,
|
|
}),
|
|
}));
|
|
|
|
import {
|
|
ACCOUNT_SELECTOR_HEADER,
|
|
apiHandler,
|
|
requireRecordAuth,
|
|
} from "@/lib/api-handler";
|
|
import { hashToken } from "@/lib/auth/hmac";
|
|
import {
|
|
providerCredentialPolicy,
|
|
type ProviderWorkAuthority,
|
|
withProviderWorkAuthority,
|
|
} from "@/lib/sharing/provider-work-authority";
|
|
import { runInsightStatusGenerate } from "@/lib/jobs/insight-status-generate";
|
|
import type { InsightStatusGeneratePayload } from "@/lib/jobs/insight-status-generate";
|
|
import {
|
|
hasAnyConfiguredProvider,
|
|
resolveProvider,
|
|
resolveProviderAvailability,
|
|
resolveProviderChain,
|
|
} from "@/lib/ai/provider";
|
|
import { invalidateStatusInsightsForTypes } from "@/lib/insights/status-invalidation";
|
|
import { warmOneNarrative } from "@/lib/jobs/period-narrative-warm";
|
|
|
|
const PROVIDER_ORIGIN_KEY = "provider-origin-test-key";
|
|
let tokenNumber = 0;
|
|
|
|
async function createUser(id: string, managed = false): Promise<void> {
|
|
await getPrismaClient().user.create({
|
|
data: {
|
|
id,
|
|
username: `provider-${id}`,
|
|
email: `provider-${id}@example.test`,
|
|
role: "USER",
|
|
...(managed ? { managedProfileAt: new Date() } : {}),
|
|
},
|
|
});
|
|
}
|
|
|
|
async function mintToken(userId: string): Promise<string> {
|
|
const raw = `${PROVIDER_ORIGIN_KEY}-${tokenNumber++}`.padEnd(28, "0");
|
|
await getPrismaClient().apiToken.create({
|
|
data: {
|
|
userId,
|
|
name: "provider-origin-test",
|
|
tokenHash: hashToken(raw),
|
|
permissions: ["*"],
|
|
},
|
|
});
|
|
return raw;
|
|
}
|
|
|
|
async function grantManage(input: {
|
|
id: string;
|
|
recordUserId: string;
|
|
actorUserId: string;
|
|
}): Promise<void> {
|
|
await getPrismaClient().accountGrant.create({
|
|
data: {
|
|
id: input.id,
|
|
grantorId: input.recordUserId,
|
|
granteeId: input.actorUserId,
|
|
access: "MANAGE",
|
|
acceptedAt: new Date(Date.now() - 60_000),
|
|
},
|
|
});
|
|
}
|
|
|
|
const enqueueFromSharedMutation: (request: NextRequest) => Promise<Response> =
|
|
apiHandler(async () => {
|
|
const { user } = await requireRecordAuth("manage", "medications");
|
|
await invalidateStatusInsightsForTypes(user.id, ["WEIGHT"]);
|
|
return NextResponse.json({ data: { queued: true }, error: null });
|
|
});
|
|
|
|
const resolveFromSharedRead: (request: NextRequest) => Promise<Response> =
|
|
apiHandler(async () => {
|
|
const { user } = await requireRecordAuth("manage", "record");
|
|
const provider = await resolveProvider(user.id);
|
|
return NextResponse.json({
|
|
data: { provider: provider.type },
|
|
error: null,
|
|
});
|
|
});
|
|
|
|
function runMutation(): Promise<Response> {
|
|
return enqueueFromSharedMutation(
|
|
new NextRequest("http://localhost/api/test/provider-origin", {
|
|
method: "POST",
|
|
}),
|
|
);
|
|
}
|
|
|
|
function runSharedRead(): Promise<Response> {
|
|
return resolveFromSharedRead(
|
|
new NextRequest("http://localhost/api/test/provider-origin-read"),
|
|
);
|
|
}
|
|
|
|
function authority(input: ProviderWorkAuthority): ProviderWorkAuthority {
|
|
return input;
|
|
}
|
|
|
|
beforeEach(async () => {
|
|
await truncateAllTables(getPrismaClient());
|
|
cookieJar.clear();
|
|
headerJar.clear();
|
|
send.mockClear();
|
|
tokenNumber = 0;
|
|
});
|
|
|
|
describe("sharing provider origin", () => {
|
|
it("[J6-no-delegated-provider-egress] enqueues and dispatches an owner-origin mutation as the positive control", async () => {
|
|
await createUser("provider-owner");
|
|
headerJar.set(
|
|
"authorization",
|
|
`Bearer ${await mintToken("provider-owner")}`,
|
|
);
|
|
|
|
const response = await runMutation();
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(send).toHaveBeenCalledTimes(3);
|
|
const payload = (
|
|
send.mock.calls as unknown as Array<
|
|
[string, InsightStatusGeneratePayload, unknown]
|
|
>
|
|
)[0]![1];
|
|
expect(payload.userId).toBe("provider-owner");
|
|
expect(payload.authority).toEqual(
|
|
authority({
|
|
origin: "owner",
|
|
recordUserId: "provider-owner",
|
|
actorUserId: "provider-owner",
|
|
grantId: null,
|
|
}),
|
|
);
|
|
|
|
const generate = vi.fn().mockResolvedValue(undefined);
|
|
await runInsightStatusGenerate(payload, {
|
|
general: vi.fn(),
|
|
"blood-pressure": vi.fn(),
|
|
weight: generate,
|
|
pulse: vi.fn(),
|
|
bmi: vi.fn(),
|
|
mood: vi.fn(),
|
|
"medication-compliance": vi.fn(),
|
|
});
|
|
|
|
expect(generate).toHaveBeenCalledWith("provider-owner", {
|
|
locale: "en",
|
|
force: true,
|
|
});
|
|
});
|
|
|
|
it("[J6-no-delegated-provider-egress] enqueues and dispatches no provider work for a delegate, including a mutation-to-red payload", async () => {
|
|
await createUser("provider-record");
|
|
await createUser("provider-delegate");
|
|
await grantManage({
|
|
id: "provider-delegated-grant",
|
|
recordUserId: "provider-record",
|
|
actorUserId: "provider-delegate",
|
|
});
|
|
headerJar.set(
|
|
"authorization",
|
|
`Bearer ${await mintToken("provider-delegate")}`,
|
|
);
|
|
headerJar.set(ACCOUNT_SELECTOR_HEADER, "provider-record");
|
|
|
|
const response = await runMutation();
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(send).not.toHaveBeenCalled();
|
|
|
|
const generate = vi.fn().mockResolvedValue(undefined);
|
|
await runInsightStatusGenerate(
|
|
{
|
|
userId: "provider-record",
|
|
metric: "weight",
|
|
locale: "en",
|
|
authority: authority({
|
|
origin: "delegate",
|
|
recordUserId: "provider-record",
|
|
actorUserId: "provider-delegate",
|
|
grantId: "provider-delegated-grant",
|
|
}),
|
|
},
|
|
{
|
|
general: vi.fn(),
|
|
"blood-pressure": vi.fn(),
|
|
weight: generate,
|
|
pulse: vi.fn(),
|
|
bmi: vi.fn(),
|
|
mood: vi.fn(),
|
|
"medication-compliance": vi.fn(),
|
|
},
|
|
);
|
|
|
|
expect(generate).not.toHaveBeenCalled();
|
|
|
|
const narrativeGenerate = vi.fn().mockResolvedValue(null);
|
|
await warmOneNarrative(
|
|
{
|
|
userId: "provider-record",
|
|
period: "week",
|
|
authority: authority({
|
|
origin: "delegate",
|
|
recordUserId: "provider-record",
|
|
actorUserId: "provider-delegate",
|
|
grantId: "provider-delegated-grant",
|
|
}),
|
|
},
|
|
narrativeGenerate,
|
|
);
|
|
|
|
expect(narrativeGenerate).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("keeps a managed-profile Guardian on operator/default provider configuration", async () => {
|
|
const guardian = authority({
|
|
origin: "guardian",
|
|
recordUserId: "managed-record",
|
|
actorUserId: "guardian-actor",
|
|
grantId: "guardian-grant",
|
|
});
|
|
|
|
expect(providerCredentialPolicy(guardian)).toBe("operator-default");
|
|
expect(
|
|
providerCredentialPolicy(
|
|
authority({
|
|
origin: "delegate",
|
|
recordUserId: "adult-record",
|
|
actorUserId: "adult-delegate",
|
|
grantId: "adult-grant",
|
|
}),
|
|
),
|
|
).toBe("deny");
|
|
|
|
await getPrismaClient().user.create({
|
|
data: {
|
|
id: "managed-record",
|
|
username: "managed-record",
|
|
email: "managed-record@example.test",
|
|
role: "USER",
|
|
managedProfileAt: new Date(),
|
|
aiProvider: "LOCAL",
|
|
aiBaseUrl: "https://guardian-byok.example.test/v1",
|
|
},
|
|
});
|
|
await createUser("guardian-actor");
|
|
await grantManage({
|
|
id: "guardian-grant",
|
|
recordUserId: "managed-record",
|
|
actorUserId: "guardian-actor",
|
|
});
|
|
|
|
const provider = await withProviderWorkAuthority(guardian, () =>
|
|
resolveProvider("managed-record"),
|
|
);
|
|
|
|
expect(provider.type).toBe("none");
|
|
});
|
|
|
|
it("returns no personal provider from the shared comprehensive-read origin", async () => {
|
|
await getPrismaClient().user.create({
|
|
data: {
|
|
id: "provider-read-record",
|
|
username: "provider-read-record",
|
|
email: "provider-read-record@example.test",
|
|
role: "USER",
|
|
aiProvider: "LOCAL",
|
|
aiBaseUrl: "https://llm.example.test/v1",
|
|
},
|
|
});
|
|
await createUser("provider-read-delegate");
|
|
await grantManage({
|
|
id: "provider-read-grant",
|
|
recordUserId: "provider-read-record",
|
|
actorUserId: "provider-read-delegate",
|
|
});
|
|
|
|
headerJar.set(
|
|
"authorization",
|
|
`Bearer ${await mintToken("provider-read-record")}`,
|
|
);
|
|
const owner = await runSharedRead();
|
|
expect((await owner.json()).data.provider).not.toBe("none");
|
|
|
|
headerJar.set(
|
|
"authorization",
|
|
`Bearer ${await mintToken("provider-read-delegate")}`,
|
|
);
|
|
headerJar.set(ACCOUNT_SELECTOR_HEADER, "provider-read-record");
|
|
const delegated = await runSharedRead();
|
|
|
|
expect((await delegated.json()).data.provider).toBe("none");
|
|
});
|
|
|
|
it("allows explicit system narrative work as the nightly positive control", async () => {
|
|
await createUser("provider-nightly");
|
|
const narrativeGenerate = vi.fn().mockResolvedValue(null);
|
|
|
|
await warmOneNarrative(
|
|
{
|
|
userId: "provider-nightly",
|
|
period: "week",
|
|
locale: "en",
|
|
authority: authority({
|
|
origin: "system",
|
|
recordUserId: "provider-nightly",
|
|
actorUserId: null,
|
|
grantId: null,
|
|
}),
|
|
},
|
|
narrativeGenerate,
|
|
);
|
|
|
|
expect(narrativeGenerate).toHaveBeenCalledWith("provider-nightly", {
|
|
period: "week",
|
|
locale: "en",
|
|
force: true,
|
|
});
|
|
});
|
|
|
|
it("keeps system work for a managed record off stale personal providers", async () => {
|
|
await getPrismaClient().user.create({
|
|
data: {
|
|
id: "managed-system-record",
|
|
username: "managed-system-record",
|
|
email: "managed-system-record@example.test",
|
|
role: "USER",
|
|
managedProfileAt: new Date(),
|
|
aiProvider: "LOCAL",
|
|
aiBaseUrl: "https://stale-managed-provider.example.test/v1",
|
|
},
|
|
});
|
|
const system = authority({
|
|
origin: "system",
|
|
recordUserId: "managed-system-record",
|
|
actorUserId: null,
|
|
grantId: null,
|
|
});
|
|
|
|
const [provider, chain, configured, availability] =
|
|
await withProviderWorkAuthority(system, async () =>
|
|
Promise.all([
|
|
resolveProvider("managed-system-record"),
|
|
resolveProviderChain("managed-system-record"),
|
|
hasAnyConfiguredProvider("managed-system-record"),
|
|
resolveProviderAvailability("managed-system-record"),
|
|
]),
|
|
);
|
|
|
|
expect(provider.type).toBe("none");
|
|
expect(chain).toEqual([]);
|
|
expect(configured).toBe(false);
|
|
expect(availability).toEqual({ aiAvailable: false, managedBy: null });
|
|
});
|
|
});
|