marmar-context / web / context / scripts / hosting-smoke.mjs
hosting-smoke.mjs
Raw
import fs from "node:fs/promises";
import http from "node:http";
import { spawn } from "node:child_process";
import { setTimeout as pause } from "node:timers/promises";
const owner = "12345678-1234-1234-1234-123456789abc";
const second = "12345678-1234-1234-1234-123456789def";
const child = spawn(
  process.execPath,
  ["release/context-preview/dist-server/index.mjs"],
  {
    env: {
      ...process.env,
      PORT: "4199",
      CONTEXT_HOSTED: "true",
      WEBSITE_SITE_NAME: "packaged-test",
      CONTEXT_AUTH_PROVIDER: "azure-easyauth",
      CONTEXT_ALLOWED_ORIGINS: "https://context.marmar.life",
      CONTEXT_REVIEWER_IDS: owner + "," + second,
    },
    stdio: ["ignore", "pipe", "pipe"],
  }
);
const results = [];
const base = "http://127.0.0.1:4199";
async function req(path, body, override = {}) {
  return await new Promise((resolve, reject) => {
    const request = http.request(
      base + path,
      {
        method: body === undefined ? "GET" : "POST",
        headers: {
          host: "context.marmar.life",
          origin: "https://context.marmar.life",
          "x-ms-client-principal-id": owner,
          ...(body === undefined ? {} : { "content-type": "application/json" }),
          ...override,
        },
      },
      (response) => {
        let text = "";
        response.on("data", (chunk) => (text += chunk));
        response.on("end", () => {
          let data;
          try {
            data = JSON.parse(text);
          } catch {
            data = text;
          }
          resolve({
            status: response.statusCode,
            data,
            headers: new Headers(response.headers),
          });
        });
      }
    );
    request.on("error", reject);
    request.end(body === undefined ? undefined : JSON.stringify(body));
  });
}

const check = (name, passed) => results.push({ name, passed });
try {
  for (let i = 0; i < 50; i++) {
    try {
      await fetch(base + "/healthz");
      break;
    } catch {
      await pause(100);
    }
  }
  check(
    "Unauthenticated application blocked",
    (await req("/api/status", undefined, { "x-ms-client-principal-id": "" }))
      .status === 401
  );
  check(
    "Unapproved reviewer blocked",
    (
      await req("/api/status", undefined, {
        "x-ms-client-principal-id": "unapproved",
      })
    ).status === 401
  );
  check(
    "Unrelated origin blocked",
    (await req("/api/status", undefined, { origin: "https://evil.example" }))
      .status === 403
  );
  check(
    "Unapproved Host blocked",
    (await req("/api/status", undefined, { host: "evil.example" })).status ===
      403
  );
  const status = await req("/api/status");
  check(
    "Hosted status and administrator-managed model",
    status.status === 200 &&
      status.data.hosted &&
      status.data.modelConnectionManaged
  );
  check(
    "Browser model changes blocked",
    (await req("/api/model", {})).status === 403
  );
  const index = await req("/");
  check(
    "Standalone frontend served",
    index.status === 200 && index.data.includes('<div id="root">')
  );
  check(
    "Security response headers",
    index.headers.get("x-frame-options") === "DENY" &&
      index.headers.has("strict-transport-security")
  );
  const caseResponse = await req("/api/cases");
  if (!Array.isArray(caseResponse.data))
    throw new Error(JSON.stringify({ results, caseResponse }));
  const cases = caseResponse.data;
  for (const item of cases) {
    const sources = item.sources;
    const extracted = await req("/api/extract", {
      sources,
      syntheticOnly: true,
      useModel: false,
    });
    check(item.id + " extraction", extracted.status === 200);
    const draft = extracted.data;
    const review = Object.fromEntries(
      draft.facts.map((f) => [f.id, f.problem ? "exclude" : "include"])
    );
    const payload = {
      sources,
      syntheticOnly: true,
      draftId: draft.id,
      review,
      fingerprint: draft.fingerprint,
      acknowledgeUnparsed: true,
    };
    const assessment = await req("/api/assess", payload);
    if (item.id === "conflicting-records" || item.id === "narrative-context") {
      check(
        item.id + " blocked pending reconciliation or narrative model",
        assessment.status === 400
      );
      continue;
    }
    check(item.id + " assessment", assessment.status === 200);
    check(
      item.id + " report export",
      (await req("/api/reports/" + assessment.data.reportId)).status === 200
    );
    check(
      item.id + " cross-reviewer draft denied",
      (
        await req("/api/assess", payload, {
          "x-ms-client-principal-id": second,
        })
      ).status === 400
    );
    check(
      item.id + " cross-reviewer report denied",
      (
        await req("/api/reports/" + assessment.data.reportId, undefined, {
          "x-ms-client-principal-id": second,
        })
      ).status === 400
    );
  }
  for (let i = 0; i < 2; i++)
    await req("/api/extract", {
      sources: cases[0].sources,
      syntheticOnly: true,
    });
  check(
    "Repeated extraction rate limited",
    (
      await req("/api/extract", {
        sources: cases[0].sources,
        syntheticOnly: true,
      })
    ).status === 429
  );
  check(
    "Evaluation succeeds under deployed package",
    (await req("/api/evaluate", {})).data.passed === 14
  );
  await fs.writeFile(
    "validation/hosted-package-smoke-2026-10-25.json",
    JSON.stringify(
      {
        scope:
          "Local packaged-server checks using simulated Azure proxy identity headers; Azure authentication and public URL not yet verified",
        results,
      },
      null,
      2
    ) + "\n"
  );
  console.log(
    JSON.stringify(
      {
        passed: results.filter((x) => x.passed).length,
        total: results.length,
        failed: results.filter((x) => !x.passed),
      },
      null,
      2
    )
  );
  if (results.some((x) => !x.passed)) process.exitCode = 1;
} finally {
  child.kill("SIGTERM");
}