feat: complete TASK-WP0-03 browser gate
This commit is contained in:
@@ -0,0 +1,257 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user