258 lines
8.4 KiB
TypeScript
258 lines
8.4 KiB
TypeScript
import { mkdirSync, writeFileSync } from "node:fs";
|
|
import { resolve } from "node:path";
|
|
|
|
import { afterAll, describe, expect, it, vi } from "vitest";
|
|
|
|
import { createApp } from "../../apps/api/src/app.js";
|
|
import {
|
|
browserSupportFixture,
|
|
testBrowserSupportRelease,
|
|
} from "../helpers/browser-support-fixture.js";
|
|
|
|
const supportedEdge = browserSupportFixture({
|
|
brand: "Microsoft Edge",
|
|
fullVersion: "150.0.4078.99",
|
|
});
|
|
const rejectedIdentityCases = [
|
|
{
|
|
expectedReason: "platform_unsupported",
|
|
fixture: browserSupportFixture({
|
|
brand: "Microsoft Edge",
|
|
fullVersion: "150.0.4078.99",
|
|
platform: "macOS",
|
|
}),
|
|
name: "non-Windows platform",
|
|
},
|
|
{
|
|
expectedReason: "brand_unsupported",
|
|
fixture: browserSupportFixture({ brand: "Opera", fullVersion: "150.0.0.0" }),
|
|
name: "other brand",
|
|
},
|
|
{
|
|
expectedReason: "version_unsupported",
|
|
fixture: browserSupportFixture({ brand: "Microsoft Edge", fullVersion: "149.0.0.0" }),
|
|
name: "wrong major",
|
|
},
|
|
] as const;
|
|
const unavailableIdentityCases = [
|
|
{ mutate: () => ({ headers: { host: "127.0.0.1:43121" }, payload: supportedEdge.body }), name: "missing UA-CH" },
|
|
{
|
|
mutate: () => ({
|
|
headers: { ...supportedEdge.headers, "sec-ch-ua-full-version-list": '"broken"' },
|
|
payload: supportedEdge.body,
|
|
}),
|
|
name: "unparseable UA-CH",
|
|
},
|
|
{
|
|
mutate: () => ({
|
|
headers: supportedEdge.headers,
|
|
payload: { ...supportedEdge.body, platform: "macOS" },
|
|
}),
|
|
name: "conflicting UA-CH",
|
|
},
|
|
] as const;
|
|
|
|
function supportCookie(response: { headers: Record<string, string | string[] | undefined> }) {
|
|
const header = response.headers["set-cookie"];
|
|
const value = Array.isArray(header) ? header[0] : header;
|
|
return value?.split(";", 1)[0];
|
|
}
|
|
|
|
afterAll(async () => {
|
|
const evidenceDirectory = process.env.DADA_EVIDENCE_DIR_BRW_BLOCKED;
|
|
if (!evidenceDirectory) return;
|
|
|
|
const responses = [];
|
|
const app = await createApp({ browserSupportRelease: testBrowserSupportRelease } as never);
|
|
for (const testCase of rejectedIdentityCases) {
|
|
const response = await app.inject({
|
|
headers: testCase.fixture.headers,
|
|
method: "POST",
|
|
payload: testCase.fixture.body,
|
|
url: "/api/v1/support/check",
|
|
});
|
|
responses.push({ body: response.json(), name: testCase.name, status_code: response.statusCode });
|
|
}
|
|
for (const testCase of unavailableIdentityCases) {
|
|
const input = testCase.mutate();
|
|
const response = await app.inject({
|
|
headers: input.headers,
|
|
method: "POST",
|
|
payload: input.payload,
|
|
url: "/api/v1/support/check",
|
|
});
|
|
responses.push({ body: response.json(), name: testCase.name, status_code: response.statusCode });
|
|
}
|
|
|
|
const bootstrap = vi.fn(() => ({
|
|
app_version: "0.0.0",
|
|
dependencies: [],
|
|
model_summary: {
|
|
config_set_version: null,
|
|
configured_default_model_id: null,
|
|
recommended_model_id: null,
|
|
runtime_availability_version: null,
|
|
},
|
|
public_features: [],
|
|
}));
|
|
const rawApp = await createApp({ bootstrap, browserSupportRelease: testBrowserSupportRelease } as never);
|
|
const rawResponse = await rawApp.inject({
|
|
headers: { host: "127.0.0.1:43121", "user-agent": "raw-client" },
|
|
method: "GET",
|
|
url: "/api/v1/bootstrap",
|
|
});
|
|
responses.push({ body: rawResponse.json(), name: "raw product API", status_code: rawResponse.statusCode });
|
|
await rawApp.close();
|
|
await app.close();
|
|
|
|
if (responses.some(({ status_code }) => status_code !== 426) || bootstrap.mock.calls.length !== 0) {
|
|
throw new Error("Hard-block evidence did not preserve the 426 zero-business-access boundary.");
|
|
}
|
|
mkdirSync(evidenceDirectory, { recursive: true });
|
|
writeFileSync(
|
|
resolve(evidenceDirectory, "response.json"),
|
|
`${JSON.stringify({ cases: responses, status: "passed" }, null, 2)}\n`,
|
|
);
|
|
writeFileSync(
|
|
resolve(evidenceDirectory, "db-access.json"),
|
|
`${JSON.stringify({ business_provider_calls: 0, reads: 0, status: "passed", writes: 0 }, null, 2)}\n`,
|
|
);
|
|
writeFileSync(
|
|
resolve(evidenceDirectory, "external-calls.json"),
|
|
`${JSON.stringify({ configured_clients: [], observed_calls: 0, status: "passed" }, null, 2)}\n`,
|
|
);
|
|
});
|
|
|
|
describe("TDD-WP0-BRW-001 supported browser contract", () => {
|
|
it("cross-checks UA-CH and issues only a short-lived signed support cookie", async () => {
|
|
const app = await createApp({ browserSupportRelease: testBrowserSupportRelease } as never);
|
|
const checked = await app.inject({
|
|
headers: supportedEdge.headers,
|
|
method: "POST",
|
|
payload: supportedEdge.body,
|
|
url: "/api/v1/support/check",
|
|
});
|
|
|
|
expect(checked.statusCode).toBe(200);
|
|
expect(checked.json()).toEqual({
|
|
app_version: "1.2.3-test",
|
|
browser: { brand: "Microsoft Edge", major: 150 },
|
|
status: "supported",
|
|
supported_browsers: [
|
|
{ brand: "Google Chrome", major: 150 },
|
|
{ brand: "Microsoft Edge", major: 150 },
|
|
],
|
|
});
|
|
expect(checked.headers["set-cookie"]).toContain("dada_browser_support=");
|
|
expect(checked.headers["set-cookie"]).toContain("HttpOnly");
|
|
expect(checked.headers["set-cookie"]).toContain("SameSite=Strict");
|
|
expect(checked.headers["set-cookie"]).toContain("Max-Age=86400");
|
|
|
|
const cookie = supportCookie(checked);
|
|
expect(cookie).toBeDefined();
|
|
const product = await app.inject({
|
|
headers: {
|
|
cookie,
|
|
host: "127.0.0.1:43121",
|
|
"sec-ch-ua": supportedEdge.headers["sec-ch-ua"],
|
|
},
|
|
method: "GET",
|
|
url: "/api/v1/bootstrap",
|
|
});
|
|
expect(product.statusCode).toBe(200);
|
|
await app.close();
|
|
|
|
const restarted = await createApp({ browserSupportRelease: testBrowserSupportRelease } as never);
|
|
const staleCookie = await restarted.inject({
|
|
headers: {
|
|
cookie,
|
|
host: "127.0.0.1:43121",
|
|
"sec-ch-ua": supportedEdge.headers["sec-ch-ua"],
|
|
},
|
|
method: "GET",
|
|
url: "/api/v1/bootstrap",
|
|
});
|
|
expect(staleCookie.statusCode).toBe(426);
|
|
await restarted.close();
|
|
});
|
|
});
|
|
|
|
describe("TDD-WP0-BRW-002 hard block", () => {
|
|
it.each(rejectedIdentityCases)("returns 426 for $name", async ({ expectedReason, fixture }) => {
|
|
const app = await createApp({ browserSupportRelease: testBrowserSupportRelease } as never);
|
|
const response = await app.inject({
|
|
headers: fixture.headers,
|
|
method: "POST",
|
|
payload: fixture.body,
|
|
url: "/api/v1/support/check",
|
|
});
|
|
|
|
expect(response.statusCode).toBe(426);
|
|
expect(response.json()).toMatchObject({
|
|
error: {
|
|
code: "BROWSER_UNSUPPORTED",
|
|
details: {
|
|
reason: expectedReason,
|
|
supported_browsers: [
|
|
{ brand: "Google Chrome", major: 150 },
|
|
{ brand: "Microsoft Edge", major: 150 },
|
|
],
|
|
},
|
|
message_key: "browser.unsupported",
|
|
},
|
|
});
|
|
expect(response.body).not.toContain("Opera");
|
|
await app.close();
|
|
});
|
|
|
|
it.each(unavailableIdentityCases)("fails closed for $name", async ({ mutate }) => {
|
|
const app = await createApp({ browserSupportRelease: testBrowserSupportRelease } as never);
|
|
const input = mutate();
|
|
const response = await app.inject({
|
|
headers: input.headers,
|
|
method: "POST",
|
|
payload: input.payload,
|
|
url: "/api/v1/support/check",
|
|
});
|
|
|
|
expect(response.statusCode).toBe(426);
|
|
expect(response.json()).toMatchObject({
|
|
error: { details: { reason: "identity_unavailable" } },
|
|
});
|
|
await app.close();
|
|
});
|
|
|
|
it("blocks raw product API access before any business provider runs", async () => {
|
|
const bootstrap = vi.fn(() => ({
|
|
app_version: "0.0.0",
|
|
dependencies: [],
|
|
model_summary: {
|
|
config_set_version: null,
|
|
configured_default_model_id: null,
|
|
recommended_model_id: null,
|
|
runtime_availability_version: null,
|
|
},
|
|
public_features: [],
|
|
}));
|
|
const app = await createApp({
|
|
bootstrap,
|
|
browserSupportRelease: testBrowserSupportRelease,
|
|
} as never);
|
|
const response = await app.inject({
|
|
headers: { host: "127.0.0.1:43121", "user-agent": "Mozilla/5.0 Chrome/150" },
|
|
method: "GET",
|
|
url: "/api/v1/bootstrap",
|
|
});
|
|
|
|
expect(response.statusCode).toBe(426);
|
|
expect(response.json()).toMatchObject({
|
|
error: {
|
|
code: "BROWSER_UNSUPPORTED",
|
|
details: { reason: "identity_unavailable" },
|
|
},
|
|
});
|
|
expect(bootstrap).not.toHaveBeenCalled();
|
|
await app.close();
|
|
});
|
|
});
|