feat: complete TASK-WP0-03 browser gate
This commit is contained in:
@@ -3,10 +3,11 @@ import { describe, expect, it } from "vitest";
|
||||
import { createApp } from "../../apps/api/src/app.js";
|
||||
|
||||
describe("Fastify skeleton", () => {
|
||||
it("becomes ready without adding a product route", async () => {
|
||||
it("becomes ready with only the Supervisor health exception", async () => {
|
||||
const app = await createApp();
|
||||
await app.ready();
|
||||
expect(app.printRoutes()).not.toContain("health");
|
||||
expect(app.printRoutes()).toContain("healthz");
|
||||
expect(app.printRoutes()).not.toContain("api/v1/health");
|
||||
await app.close();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,11 +14,14 @@ afterAll(async () => {
|
||||
const evidenceDirectory = process.env.DADA_EVIDENCE_DIR_API;
|
||||
if (!evidenceDirectory) return;
|
||||
|
||||
const app = await createApp();
|
||||
const app = await createApp({ browserGate: false });
|
||||
await app.ready();
|
||||
const document = app.swagger();
|
||||
const response = await app.inject({
|
||||
headers: { "x-correlation-id": "018f6d52-3348-7a3a-a741-7f65f31f9218" },
|
||||
headers: {
|
||||
host: "127.0.0.1:43121",
|
||||
"x-correlation-id": "018f6d52-3348-7a3a-a741-7f65f31f9218",
|
||||
},
|
||||
method: "GET",
|
||||
url: "/api/v1/bootstrap",
|
||||
});
|
||||
@@ -52,7 +55,7 @@ afterAll(async () => {
|
||||
|
||||
describe("TDD-WP0-API-001 schema envelope", () => {
|
||||
it("generates the committed OpenAPI 3.1 snapshot from runtime schemas", async () => {
|
||||
const app = await createApp();
|
||||
const app = await createApp({ browserGate: false });
|
||||
await app.ready();
|
||||
|
||||
const document = app.swagger();
|
||||
@@ -79,16 +82,19 @@ describe("TDD-WP0-API-001 schema envelope", () => {
|
||||
});
|
||||
|
||||
it("echoes a valid correlation UUID and replaces an invalid one", async () => {
|
||||
const app = await createApp();
|
||||
const app = await createApp({ browserGate: false });
|
||||
await app.ready();
|
||||
|
||||
const accepted = await app.inject({
|
||||
headers: { "x-correlation-id": "018f6d52-3348-7a3a-a741-7f65f31f9218" },
|
||||
headers: {
|
||||
host: "127.0.0.1:43121",
|
||||
"x-correlation-id": "018f6d52-3348-7a3a-a741-7f65f31f9218",
|
||||
},
|
||||
method: "GET",
|
||||
url: "/api/v1/bootstrap",
|
||||
});
|
||||
const replaced = await app.inject({
|
||||
headers: { "x-correlation-id": "not-a-uuid" },
|
||||
headers: { host: "127.0.0.1:43121", "x-correlation-id": "not-a-uuid" },
|
||||
method: "GET",
|
||||
url: "/api/v1/bootstrap",
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { createApp } from "../../apps/api/src/app.js";
|
||||
|
||||
describe("TDD-WP0-BND-001 loopback origin", () => {
|
||||
it("serves only the minimal gate shell on the fixed loopback authority", async () => {
|
||||
const app = await createApp();
|
||||
const response = await app.inject({
|
||||
headers: { host: "127.0.0.1:43121" },
|
||||
method: "GET",
|
||||
url: "/",
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.headers["accept-ch"]).toContain("Sec-CH-UA-Full-Version-List");
|
||||
expect(response.headers["content-security-policy"]).toContain("default-src 'self'");
|
||||
expect(response.body).toContain("当前浏览器无法使用 Dada");
|
||||
expect(response.body).not.toContain("/src/main.tsx");
|
||||
expect(response.body).toContain("没有“仍然继续”入口,也不会提供绕过令牌或参数。");
|
||||
expect(response.body).not.toMatch(/<(?:a|button)[^>]*>[^<]*仍然继续/u);
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
headers: { host: "192.168.1.10:43121" },
|
||||
method: "GET" as const,
|
||||
name: "LAN Host",
|
||||
url: "/",
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
host: "127.0.0.1:43121",
|
||||
origin: "http://attacker.invalid",
|
||||
},
|
||||
method: "POST" as const,
|
||||
name: "foreign Origin",
|
||||
url: "/api/v1/support/check",
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
"access-control-request-method": "POST",
|
||||
host: "127.0.0.1:43121",
|
||||
origin: "http://attacker.invalid",
|
||||
},
|
||||
method: "OPTIONS" as const,
|
||||
name: "CORS preflight",
|
||||
url: "/api/v1/support/check",
|
||||
},
|
||||
])("rejects $name without a CORS grant", async ({ headers, method, url }) => {
|
||||
const app = await createApp();
|
||||
const response = await app.inject({ headers, method, url });
|
||||
|
||||
expect(response.statusCode).toBe(426);
|
||||
expect(response.headers).not.toHaveProperty("access-control-allow-origin");
|
||||
expect(response.json()).toMatchObject({
|
||||
error: {
|
||||
code: "BROWSER_UNSUPPORTED",
|
||||
details: { reason: "identity_unavailable" },
|
||||
message_key: "browser.unsupported",
|
||||
},
|
||||
});
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it("keeps the production and Supervisor endpoint fixed", () => {
|
||||
const main = readFileSync("apps/api/src/main.ts", "utf8");
|
||||
const supervisor = readFileSync("supervisor/Dada.Supervisor/LoopbackEndpoint.cs", "utf8");
|
||||
|
||||
expect(main).toContain('host: "127.0.0.1"');
|
||||
expect(main).toContain("port: 43121");
|
||||
expect(supervisor).toContain('Host = "127.0.0.1"');
|
||||
expect(supervisor).toContain("Port = 43121");
|
||||
expect(supervisor).not.toContain("0.0.0.0");
|
||||
});
|
||||
});
|
||||
@@ -13,7 +13,11 @@ let vite: ViteDevServer;
|
||||
let webUrl: string;
|
||||
|
||||
test.beforeAll(async () => {
|
||||
app = await createApp({ eventHub });
|
||||
app = await createApp({
|
||||
browserGate: false,
|
||||
eventHub,
|
||||
networkBoundary: { allowTestPort: true },
|
||||
});
|
||||
app.get("/__test/entity", async (request) => ({ entity_ref: (request.query as { ref?: string }).ref ?? null }));
|
||||
app.get("/__test/models", async () => ({ source: "rest" }));
|
||||
const apiUrl = await app.listen({ host: "127.0.0.1", port: 0 });
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import { mkdirSync, writeFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
import { createApp } from "../../apps/api/src/app.js";
|
||||
|
||||
test("a real Edge profile passes the candidate support flow", async ({ browser, context, page }) => {
|
||||
const fullVersion = browser.version();
|
||||
const release = {
|
||||
appVersion: "1.2.3-candidate",
|
||||
browsers: [
|
||||
{ brand: "Google Chrome", fullVersion: "150.0.0.0" },
|
||||
{ brand: "Microsoft Edge", fullVersion },
|
||||
],
|
||||
} as const;
|
||||
const app = await createApp({
|
||||
browserSupportRelease: release,
|
||||
networkBoundary: { allowTestPort: true },
|
||||
} as never);
|
||||
const url = await app.listen({ host: "127.0.0.1", port: 0 });
|
||||
|
||||
try {
|
||||
await page.goto(url);
|
||||
await expect.poll(() => page.locator("html").getAttribute("data-support-status")).toBe("supported");
|
||||
await expect(page.locator("h1")).toHaveText("浏览器支持检查已通过");
|
||||
await expect(page.locator("#status-code")).toBeHidden();
|
||||
await expect(page.locator("#no-bypass")).toBeHidden();
|
||||
await expect(page.locator("#supported-value")).toContainText("Google Chrome");
|
||||
await expect(page.locator("#supported-value")).toContainText("Microsoft Edge");
|
||||
const bootstrapStatus = await page.evaluate(async () => (await fetch("/api/v1/bootstrap")).status);
|
||||
expect(bootstrapStatus).toBe(200);
|
||||
|
||||
const cookie = (await context.cookies()).find(({ name }) => name === "dada_browser_support");
|
||||
expect(cookie).toMatchObject({ httpOnly: true, sameSite: "Strict" });
|
||||
|
||||
const evidenceDirectory = process.env.DADA_EVIDENCE_DIR_BRW_SUPPORTED;
|
||||
if (evidenceDirectory) {
|
||||
mkdirSync(resolve(evidenceDirectory, "edge", "screenshots"), { recursive: true });
|
||||
await page.screenshot({
|
||||
path: resolve(evidenceDirectory, "edge", "screenshots", "supported.png"),
|
||||
});
|
||||
writeFileSync(
|
||||
resolve(evidenceDirectory, "edge", "environment.json"),
|
||||
`${JSON.stringify({ browser: "Microsoft Edge", full_version: fullVersion, release_kind: "test_candidate", status: "partial" }, null, 2)}\n`,
|
||||
);
|
||||
writeFileSync(
|
||||
resolve(evidenceDirectory, "edge", "response.json"),
|
||||
`${JSON.stringify({ bootstrap_status: bootstrapStatus, cookie_http_only: cookie?.httpOnly, status: "passed" }, null, 2)}\n`,
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
await app.close();
|
||||
}
|
||||
});
|
||||
|
||||
test("the hard-block shell is usable without a product bundle", async ({ browser, page }) => {
|
||||
const currentMajor = Number.parseInt(browser.version().split(".")[0], 10);
|
||||
const app = await createApp({
|
||||
browserSupportRelease: {
|
||||
appVersion: "1.2.3-test",
|
||||
browsers: [
|
||||
{ brand: "Google Chrome", fullVersion: `${currentMajor + 1}.0.0.0` },
|
||||
{ brand: "Microsoft Edge", fullVersion: `${currentMajor + 1}.0.0.0` },
|
||||
],
|
||||
},
|
||||
networkBoundary: { allowTestPort: true },
|
||||
} as never);
|
||||
const url = await app.listen({ host: "127.0.0.1", port: 0 });
|
||||
|
||||
try {
|
||||
await page.goto(url);
|
||||
await expect.poll(() => page.locator("html").getAttribute("data-support-status")).toBe("blocked");
|
||||
await expect(page.locator("#reason-value")).toHaveText("version_unsupported");
|
||||
await expect(page.getByText("没有“仍然继续”入口,也不会提供绕过令牌或参数。")).toBeVisible();
|
||||
await expect(page.locator('script[src*="/src/main"]')).toHaveCount(0);
|
||||
await page.keyboard.press("Tab");
|
||||
await expect(page.locator("h1")).toBeFocused();
|
||||
await page.keyboard.press("Tab");
|
||||
await expect(page.locator("summary")).toBeFocused();
|
||||
await page.keyboard.press("Tab");
|
||||
await expect(page.getByRole("button", { name: "重新检测" })).toBeFocused();
|
||||
await page.keyboard.press("Tab");
|
||||
await expect(page.getByRole("button", { name: "复制本机地址" })).toBeFocused();
|
||||
|
||||
const evidenceDirectory = process.env.DADA_EVIDENCE_DIR_BRW_BLOCKED;
|
||||
if (evidenceDirectory) {
|
||||
mkdirSync(resolve(evidenceDirectory, "screenshots"), { recursive: true });
|
||||
await page.screenshot({ path: resolve(evidenceDirectory, "screenshots", "blocked.png") });
|
||||
}
|
||||
|
||||
await page.setViewportSize({ height: 844, width: 390 });
|
||||
expect(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)).toBe(true);
|
||||
await expect(page.getByRole("button", { name: "重新检测" })).toBeVisible();
|
||||
if (evidenceDirectory) {
|
||||
await page.screenshot({ path: resolve(evidenceDirectory, "screenshots", "blocked-mobile.png") });
|
||||
}
|
||||
} finally {
|
||||
await app.close();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
export const testBrowserSupportRelease = {
|
||||
appVersion: "1.2.3-test",
|
||||
browsers: [
|
||||
{ brand: "Google Chrome", fullVersion: "150.0.7339.1" },
|
||||
{ brand: "Microsoft Edge", fullVersion: "150.0.4078.99" },
|
||||
],
|
||||
} as const;
|
||||
|
||||
interface BrowserIdentityFixture {
|
||||
brand: string;
|
||||
fullVersion: string;
|
||||
platform?: string;
|
||||
}
|
||||
|
||||
function major(version: string) {
|
||||
return version.split(".")[0];
|
||||
}
|
||||
|
||||
function serialize(values: Array<{ brand: string; version: string }>) {
|
||||
return values.map(({ brand, version }) => `"${brand}";v="${version}"`).join(", ");
|
||||
}
|
||||
|
||||
export function browserSupportFixture(input: BrowserIdentityFixture) {
|
||||
const platform = input.platform ?? "Windows";
|
||||
const brands = [
|
||||
{ brand: "Not_A Brand", version: "99" },
|
||||
{ brand: "Chromium", version: major(input.fullVersion) },
|
||||
{ brand: input.brand, version: major(input.fullVersion) },
|
||||
];
|
||||
const fullVersionList = [
|
||||
{ brand: "Not_A Brand", version: "99.0.0.0" },
|
||||
{ brand: "Chromium", version: input.fullVersion },
|
||||
{ brand: input.brand, version: input.fullVersion },
|
||||
];
|
||||
return {
|
||||
body: {
|
||||
brands,
|
||||
full_version_list: fullVersionList,
|
||||
platform,
|
||||
},
|
||||
headers: {
|
||||
host: "127.0.0.1:43121",
|
||||
origin: "http://127.0.0.1:43121",
|
||||
"sec-ch-ua": serialize(brands),
|
||||
"sec-ch-ua-full-version-list": serialize(fullVersionList),
|
||||
"sec-ch-ua-platform": `"${platform}"`,
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user