Spaces:
Runtime error
Runtime error
File size: 5,010 Bytes
aacc29a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import test from "node:test";
import { validateDeployEnv } from "./validate-deploy-env.mjs";
const validEnv = {
VITE_API_BASE: "https://carepath.hf.space",
};
test("accepts one HTTPS Space origin", () => {
assert.doesNotThrow(() => validateDeployEnv(validEnv));
});
test("accepts an empty API base when the websocket names its own host", () => {
// The production shape after DEC-0021: /api/* is rewritten same-origin by
// Vercel, so no CORS allow-list can take the site down again.
assert.doesNotThrow(() =>
validateDeployEnv({ VITE_API_BASE: "", VITE_WS_BASE: "https://carepath.hf.space" }),
);
});
test("rejects an empty API base with no websocket host to fall back on", () => {
// Both empty means the socket would target the Vercel origin, which cannot
// proxy the upgrade — the visit screen would connect to nothing.
assert.throws(() => validateDeployEnv({}), /VITE_WS_BASE is required/);
});
test("rejects a websocket base with a path", () => {
assert.throws(
() => validateDeployEnv({ VITE_WS_BASE: "https://carepath.hf.space/ws" }),
/VITE_WS_BASE pathname must be \//,
);
});
test("rejects HTTP", () => {
assert.throws(
() => validateDeployEnv({ VITE_API_BASE: "http://api.example.test" }),
/must use HTTPS/,
);
});
test("rejects paths, queries, and fragments that would corrupt runtime URLs", () => {
assert.throws(
() => validateDeployEnv({ VITE_API_BASE: "https://carepath.hf.space/wrong?token=secret" }),
/must not include a query or fragment[\s\S]*pathname must be \//,
);
});
test("Vercel runs validation before the production build", async () => {
const config = JSON.parse(
await readFile(new URL("../vercel.json", import.meta.url), "utf8"),
);
assert.equal(config.buildCommand, "npm run validate:deploy && npm run build");
const spa = config.rewrites.filter((rule) => rule.destination === "/index.html");
assert.deepEqual(spa, [
{ source: "/thu-nghiem", destination: "/index.html" },
{ source: "/thu-nghiem/", destination: "/index.html" },
{ source: "/thu-nghiem/:path*", destination: "/index.html" },
{ source: "/ghi-chep-lam-sang", destination: "/index.html" },
{ source: "/ghi-chep-lam-sang/", destination: "/index.html" },
{ source: "/ghi-chep-lam-sang/:path*", destination: "/index.html" },
{ source: "/kham-song-ngu", destination: "/index.html" },
{ source: "/kham-song-ngu/", destination: "/index.html" },
{ source: "/kham-song-ngu/:path*", destination: "/index.html" },
]);
});
test("the API proxy does not swallow the demo functions", async () => {
// /api/demo/* are serverless functions in this project. If the catch-all
// proxied them to the Space they would 404 there, and the quota that keeps
// anonymous traffic off the paid provider would never run.
const config = JSON.parse(
await readFile(new URL("../vercel.json", import.meta.url), "utf8"),
);
const api = config.rewrites.find((rule) => rule.source.startsWith("/api/"));
const pattern = new RegExp(`^${api.source}$`);
assert.ok(pattern.test("/api/v1/health"), "product API must still be proxied");
assert.ok(pattern.test("/api/sessions"), "product API must still be proxied");
assert.equal(pattern.test("/api/demo/document"), false);
assert.equal(pattern.test("/api/demo/session"), false);
});
test("the API is proxied same-origin so no CORS allow-list can break it", async () => {
// Regression guard for the outage this fixed: moving the site to
// carepath-medicaltranslation.vercel.app while the Space still allowed only
// carepath-omega.vercel.app returned 400 Disallowed CORS origin on every
// call, and both tool routes rendered "Mất kết nối máy chủ".
const config = JSON.parse(
await readFile(new URL("../vercel.json", import.meta.url), "utf8"),
);
const api = config.rewrites.find((rule) => rule.source.startsWith("/api/"));
assert.ok(api, "vercel.json must rewrite /api/* to the API host");
assert.match(api.destination, /^https:\/\/[^/]+\/api\//);
// It has to win before any SPA catch-all could swallow it.
assert.equal(config.rewrites.indexOf(api), 0);
});
test("every SPA route the app serves has a Vercel rewrite", async () => {
// A missing rewrite is a hard 404 on a direct visit, and /kham-song-ngu/ is
// the demo URL. Keep this derived from the app's own route table.
const app = await readFile(new URL("../src/App.tsx", import.meta.url), "utf8");
const routes = [...app.matchAll(/^const \w*_?PATH = "(\/[^"]+)";$/gm)].map((m) => m[1]);
assert.ok(routes.length >= 2, `expected app routes, found ${routes.join(", ")}`);
const config = JSON.parse(
await readFile(new URL("../vercel.json", import.meta.url), "utf8"),
);
const sources = new Set(config.rewrites.map((rule) => rule.source));
for (const route of routes) {
assert.ok(sources.has(route), `vercel.json has no rewrite for ${route}`);
}
});
|