marmar-context / web / context / test / hosting.test.ts
hosting.test.ts
Raw
import { describe, it, expect } from "vitest";
import {
  hostingConfig,
  requestOwner,
  createPreviewLimiter,
} from "../server/hosting";
import { keepDraft, getDraft, keepReport, getReport } from "../server/sessions";
import { extractLocal, assess } from "../server/context";
import { CASES } from "../server/cases";
const reviewer = "12345678-1234-1234-1234-123456789abc";
const env = {
  CONTEXT_HOSTED: "true",
  PORT: "8080",
  WEBSITE_SITE_NAME: "context-preview",
  CONTEXT_AUTH_PROVIDER: "azure-easyauth",
  CONTEXT_REVIEWER_IDS: reviewer,
  CONTEXT_ALLOWED_ORIGINS:
    "https://context.marmar.life,https://preview.azurewebsites.net",
};
describe("restricted hosted preview", () => {
  it("fails closed with incomplete hosting configuration", () => {
    expect(() => hostingConfig({ CONTEXT_HOSTED: "true" })).toThrow();
    expect(() =>
      hostingConfig({
        ...env,
        CONTEXT_ALLOWED_ORIGINS: "http://context.marmar.life",
      })
    ).toThrow();
    expect(() => hostingConfig({ ...env, PORT: "oops" })).toThrow();
  });
  it("keeps local mode loopback only", () => {
    const config = hostingConfig({});
    expect(config.bind).toBe("127.0.0.1");
    expect(
      requestOwner(config, {
        host: "localhost:4191",
        origin: "http://localhost:4191",
      })
    ).toBe("local");
    expect(() => requestOwner(config, { host: "evil.example" })).toThrow();
    expect(() =>
      requestOwner(config, {
        host: "localhost:4191",
        origin: "https://evil.example",
      })
    ).toThrow();
  });
  it("requires the exact allowed host, origin and approved principal", () => {
    const config = hostingConfig(env);
    const headers = {
      host: "context.marmar.life",
      origin: "https://context.marmar.life",
      "x-ms-client-principal-id": reviewer,
    };
    expect(config.bind).toBe("0.0.0.0");
    expect(requestOwner(config, headers)).toBe(reviewer);
    expect(() =>
      requestOwner(config, { ...headers, host: "other.azurewebsites.net" })
    ).toThrow();
    expect(() =>
      requestOwner(config, { ...headers, origin: "https://evil.example" })
    ).toThrow();
    expect(() =>
      requestOwner(config, {
        ...headers,
        "x-ms-client-principal-id": "unknown",
      })
    ).toThrow();
    expect(() => requestOwner(config, { host: headers.host })).toThrow();
  });
  it("bounds repeated extractions separately for each reviewer", () => {
    const limit = createPreviewLimiter();
    for (let i = 0; i < 6; i++) limit(reviewer, 1000);
    expect(() => limit(reviewer, 1000)).toThrow(/limit/);
    expect(() => limit("other", 1000)).not.toThrow();
    expect(() => limit(reviewer, 61_000)).not.toThrow();
  });
  it("bounds daily calls across minute windows", () => {
    const limit = createPreviewLimiter();
    for (let i = 0; i < 100; i++) limit(reviewer, i * 60_000);
    expect(() => limit(reviewer, 100 * 60_000)).toThrow(/limit/);
    expect(() => limit(reviewer, 86_400_000)).not.toThrow();
  });
  it("isolates drafts and reports between authenticated reviewers", () => {
    const sources = CASES[0].sources;
    const draft = keepDraft(extractLocal(sources), sources, reviewer);
    expect(() => getDraft(draft.id!, sources, "other")).toThrow();
    expect(getDraft(draft.id!, sources, reviewer).id).toBe(draft.id);
    const review = Object.fromEntries(
      draft.facts.map((f) => [f.id, "include" as const])
    );
    const assessment = assess(sources, review, draft.fingerprint, true, draft);
    expect(() => keepReport(draft.id!, assessment, review, "other")).toThrow();
    const saved = keepReport(draft.id!, assessment, review, reviewer);
    expect(() => getReport(saved.reportId!, "other")).toThrow();
    expect(getReport(saved.reportId!, reviewer).synthetic).toBe(true);
  });
});