marmar-context / web / context / test / hosted-demo.test.ts
hosted-demo.test.ts
Raw
// ACC-11: the hosted product has no shared password and no "Connect Azure model" form, and the
// old /preview route is not in a production build. ACC-2: Try a fictional patient still works
// with no account, on the fictional records only, with the local reader only.
import { mkdtemp, readdir, readFile, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import { build } from "vite";
import { afterEach, describe, expect, it, vi } from "vitest";
import { CASES } from "../server/cases";
import { createNetlifyHandler } from "../server/netlify-handler";

class MemoryBlobs {
  rows = new Map<string, { value: unknown; etag: string }>();
  n = 0;
  async get(key: string) { return this.rows.get(key)?.value ?? null; }
  async getWithMetadata(key: string) { const row = this.rows.get(key); return row ? { data: row.value, etag: row.etag, metadata: {} } : null; }
  async setJSON(key: string, value: unknown, opts: { onlyIfNew?: boolean; onlyIfMatch?: string } = {}) {
    const row = this.rows.get(key);
    if ((opts.onlyIfNew && row) || (opts.onlyIfMatch && row?.etag !== opts.onlyIfMatch)) return { modified: false };
    this.rows.set(key, { value, etag: `e${++this.n}` });
    return { modified: true, etag: `e${this.n}` };
  }
  async delete(key: string) { this.rows.delete(key); }
  async list() { return { blobs: [...this.rows.entries()].map(([key, row]) => ({ key, etag: row.etag })) }; }
}

const req = (path: string, body?: unknown, cookie?: string) =>
  new Request("https://context.marmar.life/api/" + path, {
    method: body === undefined ? "GET" : "POST",
    headers: { Origin: "https://context.marmar.life", ...(body === undefined ? {} : { "Content-Type": "application/json" }), ...(cookie ? { Cookie: cookie } : {}) },
    body: body === undefined ? undefined : JSON.stringify(body),
  });

describe("the hosted demo (CONTEXT_AUTH_MODE=demo)", () => {
  afterEach(() => vi.unstubAllEnvs());
  it("runs a fictional patient with no account and no model", async () => {
    vi.stubEnv("CONTEXT_AUTH_MODE", "demo");
    const handler = createNetlifyHandler(() => new MemoryBlobs() as never);
    const status = await handler(req("status"));
    expect(status.status).toBe(200);
    expect(await status.json()).toMatchObject({ modelConfigured: false, model: null, demo: true });
    const cookie = status.headers.get("set-cookie")!;
    expect(cookie).toMatch(/^__Host-marmar-context-demo=[a-f0-9-]{36}; Secure; HttpOnly; SameSite=Strict/);
    const sources = CASES[0].sources;
    const draft = await handler(req("extract", { sources, syntheticOnly: true, useModel: false }, cookie.split(";")[0]));
    expect(draft.status).toBe(200);
    expect((await draft.json()).mode).toBe("local");
  });
  it("refuses the model, pasted or changed text, and has no password sign-in", async () => {
    vi.stubEnv("CONTEXT_AUTH_MODE", "demo");
    const handler = createNetlifyHandler(() => new MemoryBlobs() as never);
    const sources = CASES[0].sources;
    expect((await handler(req("extract", { sources, syntheticOnly: true, useModel: true }))).status).toBe(403);
    const pasted = [{ ...sources[0], text: "Patient: a real person\nMedication: Warfarin 5 mg" }];
    expect((await handler(req("extract", { sources: pasted, syntheticOnly: true, useModel: false }))).status).toBe(403);
    expect((await handler(req("auth", { password: "anything" }))).status).not.toBe(200);
    expect((await handler(req("model", {}))).status).toBe(403);
  });
  it("still fails closed without Cloudflare Access outside the demo", async () => {
    vi.stubEnv("CONTEXT_AUTH_MODE", "password");
    vi.stubEnv("CONTEXT_ACCESS_ISSUER", "https://marmar-test.cloudflareaccess.com");
    vi.stubEnv("CONTEXT_ACCESS_AUD", "test-audience");
    expect((await createNetlifyHandler(() => new MemoryBlobs() as never)(req("status"))).status).toBe(401);
  });
});

describe("the production build", () => {
  it("has no /preview route, no shared password and no model connection form", async () => {
    const outDir = await mkdtemp(join(tmpdir(), "context-build-"));
    // Vitest runs with NODE_ENV=test; a real build runs with production, which makes import.meta.env.DEV false.
    vi.stubEnv("NODE_ENV", "production");
    try {
      await build({ root: fileURLToPath(new URL("..", import.meta.url)), logLevel: "silent", mode: "production", build: { outDir, emptyOutDir: true } });
      const files = (await readdir(join(outDir, "assets"))).filter((f) => f.endsWith(".js"));
      const code = (await Promise.all(files.map((f) => readFile(join(outDir, "assets", f), "utf8")))).join("\n");
      for (const banned of ['"/preview"', "Preview password", "preview-password", "Reviewer sign-in", "Connect your Azure model", "Save session connection", "Azure endpoint"])
        expect(code.includes(banned), banned).toBe(false);
      // Try a fictional patient is still there.
      expect(code.includes('"/demo"')).toBe(true);
      expect(code.includes("Fictional demo")).toBe(true);
    } finally {
      vi.unstubAllEnvs();
      await rm(outDir, { recursive: true, force: true });
    }
  }, 60_000);
});