mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 06:40:50 +00:00
Merge the release with the security review fixes into the data-safety review fixes
This commit is contained in:
commit
bcb31c80db
98 files changed
+3166
-608
No files matched your search
@@ -105,7 +105,9 @@ describe("demo seed, short-window shape", () => {
|
||||
it("leaves the median of the window in the settled band", () => {
|
||||
// The usual range is the median ± 3 MAD of the last 30 days. A stretch
|
||||
// that fills half the window moves that median onto the stretch and
|
||||
// widens the band to something like 87-181 mmHg.
|
||||
// widens the band to something like 87-181 mmHg. The seed's own spread
|
||||
// reaches about ±22 in one run of 150, so the bound sits at 24, still far
|
||||
// below what a stretch that long produces.
|
||||
const sorted = [...systolic].sort((a, b) => a - b);
|
||||
const median = sorted[Math.floor(sorted.length / 2)];
|
||||
expect(median).toBeLessThanOrEqual(124);
|
||||
@@ -113,7 +115,7 @@ describe("demo seed, short-window shape", () => {
|
||||
.map((v) => Math.abs(v - median))
|
||||
.sort((a, b) => a - b);
|
||||
const mad = deviations[Math.floor(deviations.length / 2)];
|
||||
expect(3 * 1.4826 * mad).toBeLessThanOrEqual(20);
|
||||
expect(3 * 1.4826 * mad).toBeLessThanOrEqual(24);
|
||||
});
|
||||
|
||||
it("raises ramipril on the day the stretch turns", () => {
|
||||
|
||||
@@ -540,6 +540,68 @@ describe("the reaper", () => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* A run's deadline is model time. The document reader moves it by the time
|
||||
* a read waited for the reader and took, so a person's lab scan is not
|
||||
* timed out because other work held the reader; never past the job's own
|
||||
* expiry, and only while the run is running.
|
||||
*/
|
||||
describe("the deadline while the document reader works", () => {
|
||||
async function runningRun(expiresAt: Date) {
|
||||
const user = await makeUser();
|
||||
await grantConsent(user.id);
|
||||
await signIn(user.id);
|
||||
const queued = await postLabScanText("Glucose 95 mg/dL");
|
||||
const { runId } = queued.body.data as { runId: string };
|
||||
await getPrismaClient().documentAiRun.update({
|
||||
where: { id: runId },
|
||||
data: { status: "RUNNING", expiresAt },
|
||||
});
|
||||
return runId;
|
||||
}
|
||||
const expiry = async (runId: string) =>
|
||||
(
|
||||
await getPrismaClient().documentAiRun.findUniqueOrThrow({
|
||||
where: { id: runId },
|
||||
})
|
||||
).expiresAt.getTime();
|
||||
|
||||
it("moves a running run's deadline by the reader's time, so the reaper leaves it alone", async () => {
|
||||
const { moveRunDeadline } = await import("@/lib/jobs/document-ai-run");
|
||||
const { reapAiRuns } = await import("@/lib/documents/ai-runs/store");
|
||||
const armed = new Date(Date.now() + 5_000);
|
||||
const runId = await runningRun(armed);
|
||||
|
||||
// The slot is granted after a 60 s wait, with 70 s allowed for the read.
|
||||
await moveRunDeadline(runId, 130_000, undefined);
|
||||
expect(await expiry(runId)).toBe(armed.getTime() + 130_000);
|
||||
// Without the move the reaper would have taken it a minute later.
|
||||
expect(
|
||||
(await reapAiRuns(new Date(armed.getTime() + 60_000))).timedOut,
|
||||
).toBe(0);
|
||||
|
||||
// The read took 10 s of its 70: the unused 60 s go back.
|
||||
await moveRunDeadline(runId, -60_000, undefined);
|
||||
expect(await expiry(runId)).toBe(armed.getTime() + 70_000);
|
||||
});
|
||||
|
||||
it("never moves it past the job's own expiry, and leaves a run that is not running alone", async () => {
|
||||
const { moveRunDeadline } = await import("@/lib/jobs/document-ai-run");
|
||||
const armed = new Date(Date.now() + 5_000);
|
||||
const runId = await runningRun(armed);
|
||||
const jobEnds = armed.getTime() + 20_000;
|
||||
await moveRunDeadline(runId, 130_000, jobEnds);
|
||||
expect(await expiry(runId)).toBe(jobEnds);
|
||||
|
||||
await getPrismaClient().documentAiRun.update({
|
||||
where: { id: runId },
|
||||
data: { status: "FAILED" },
|
||||
});
|
||||
await moveRunDeadline(runId, 130_000, undefined);
|
||||
expect(await expiry(runId)).toBe(jobEnds);
|
||||
});
|
||||
});
|
||||
|
||||
describe("a summary in the background", () => {
|
||||
it("marks a stored summary PENDING while queued and READY when it lands", async () => {
|
||||
const user = await makeUser();
|
||||
|
||||
@@ -22,7 +22,7 @@ import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
||||
import { GET } from "@/app/api/auth/oidc/callback/route";
|
||||
import { encrypt } from "@/lib/crypto";
|
||||
import { OIDC_STATE_COOKIE } from "@/lib/auth/oidc-cookie";
|
||||
import { SESSION_COOKIE_NAME } from "@/lib/auth/session";
|
||||
import { sessionCookieName } from "@/lib/auth/session-cookie";
|
||||
|
||||
import { anonymous, callRoute } from "../../../kit/call-route";
|
||||
import { makeSession, makeUser } from "../../../kit/factories";
|
||||
@@ -312,7 +312,7 @@ describe("GET /api/auth/oidc/callback parity", () => {
|
||||
const session = await makeSession(first);
|
||||
await step("12-duplicate-with-session", {
|
||||
state: null,
|
||||
cookies: { [SESSION_COOKIE_NAME]: session.cookie },
|
||||
cookies: { [sessionCookieName()]: session.cookie },
|
||||
});
|
||||
});
|
||||
|
||||
@@ -382,7 +382,7 @@ describe("GET /api/auth/oidc/callback parity", () => {
|
||||
identity("sub-claimer", "claimer@example.test", true);
|
||||
await step("23-claim-signed-in", {
|
||||
state: { intent: "claim", claimBinding: "binding-1" },
|
||||
cookies: { [SESSION_COOKIE_NAME]: session.cookie },
|
||||
cookies: { [sessionCookieName()]: session.cookie },
|
||||
});
|
||||
await step("24-claim-no-cookie", {
|
||||
state: { intent: "claim", claimBinding: "binding-1" },
|
||||
|
||||
@@ -25,7 +25,7 @@ import { NextRequest } from "next/server";
|
||||
import type { z } from "zod";
|
||||
|
||||
import { openApiComponents, openApiPaths } from "@/lib/openapi/routes";
|
||||
import { SESSION_COOKIE_NAME } from "@/lib/auth/session";
|
||||
import { sessionCookieName } from "@/lib/auth/session-cookie";
|
||||
|
||||
import { cookieJar, headerJar } from "../integration/mock-next-headers";
|
||||
import type { BearerFixture, SessionFixture } from "./factories";
|
||||
@@ -122,7 +122,7 @@ export async function callRoute<T = unknown>(
|
||||
headers.set("authorization", `Bearer ${options.auth.token}`);
|
||||
}
|
||||
if (options.auth.kind === "session") {
|
||||
headers.set("cookie", `${SESSION_COOKIE_NAME}=${options.auth.cookie}`);
|
||||
headers.set("cookie", `${sessionCookieName()}=${options.auth.cookie}`);
|
||||
}
|
||||
|
||||
// `next/headers` is served from the shared jars; fill them for this call
|
||||
@@ -133,7 +133,7 @@ export async function callRoute<T = unknown>(
|
||||
const savedHeaders = new Map(headerJar);
|
||||
cookieJar.clear();
|
||||
if (options.auth.kind === "session") {
|
||||
cookieJar.set(SESSION_COOKIE_NAME, options.auth.cookie);
|
||||
cookieJar.set(sessionCookieName(), options.auth.cookie);
|
||||
}
|
||||
headerJar.delete("authorization");
|
||||
headers.forEach((value, name) => headerJar.set(name.toLowerCase(), value));
|
||||
|
||||
@@ -27,7 +27,8 @@ import type {
|
||||
Workout,
|
||||
} from "@/generated/prisma/client";
|
||||
import { prisma } from "@/lib/db";
|
||||
import { createSession, SESSION_COOKIE_NAME } from "@/lib/auth/session";
|
||||
import { createSession } from "@/lib/auth/session";
|
||||
import { sessionCookieName } from "@/lib/auth/session-cookie";
|
||||
import { issueApiToken } from "@/lib/auth/issue-token";
|
||||
|
||||
import { cookieJar } from "../integration/mock-next-headers";
|
||||
@@ -91,7 +92,7 @@ export async function makeSession(
|
||||
"healthlog-kit",
|
||||
options.mfaVerifiedAt ?? null,
|
||||
);
|
||||
const cookie = cookieJar.get(SESSION_COOKIE_NAME);
|
||||
const cookie = cookieJar.get(sessionCookieName());
|
||||
cookieJar.clear();
|
||||
for (const [name, value] of before) cookieJar.set(name, value);
|
||||
if (!cookie) {
|
||||
|
||||
Reference in new issue
Block a user