rea/src/domain/browserObservation.test.ts

182 lines
6.1 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
browserEndpointSchema,
browserOriginSchema,
inspectWebPageInputSchema,
isLiteralLoopbackHostname,
listBrowserTargetsInputSchema,
sanitizeEndpointCandidate,
sanitizeBrowserUrl,
} from "./browserObservation.js";
import { captureWebScreenshotInputSchema } from "./webScreenshot.js";
describe("browser observation contracts", () => {
it("normalizes exact HTTP origins without accepting broader URL scopes", () => {
expect(browserOriginSchema.parse("https://Example.COM:443")).toBe(
"https://example.com",
);
expect(browserOriginSchema.parse("http://example.com:80")).toBe(
"http://example.com",
);
for (const value of [
"https://*.example.com",
"https://example.com/path",
"https://example.com/?token=secret",
"https://user:pass@example.com",
"file:///tmp/page.html",
])
expect(browserOriginSchema.safeParse(value).success, value).toBe(false);
});
it("accepts only literal loopback HTTP CDP endpoints", () => {
expect(browserEndpointSchema.parse("http://127.0.0.1:9222")).toBe(
"http://127.0.0.1:9222",
);
expect(browserEndpointSchema.parse("http://[::1]:9222")).toBe(
"http://[::1]:9222",
);
for (const value of [
"http://localhost:9222",
"http://192.168.1.2:9222",
"https://127.0.0.1:9222",
"http://127.0.0.1:9222/json/list",
"http://user:pass@127.0.0.1:9222",
])
expect(browserEndpointSchema.safeParse(value).success, value).toBe(false);
});
it("recognizes bracketed and normalized IPv6 loopback hostnames", () => {
for (const hostname of ["127.0.0.1", "[::1]", "::1"])
expect(isLiteralLoopbackHostname(hostname), hostname).toBe(true);
for (const hostname of ["localhost", "127.0.0.2", "::2"])
expect(isLiteralLoopbackHostname(hostname), hostname).toBe(false);
});
it("applies defaults and rejects unknown public observation input fields", () => {
expect(
listBrowserTargetsInputSchema.parse({
cdp_endpoint: "http://127.0.0.1:9222",
allowed_origins: ["https://app.example.test"],
}),
).toEqual({
cdp_endpoint: "http://127.0.0.1:9222",
allowed_origins: ["https://app.example.test"],
});
expect(
inspectWebPageInputSchema.parse({
cdp_endpoint: "http://127.0.0.1:9222",
allowed_origins: ["https://app.example.test"],
target_id: "page-1",
}),
).toMatchObject({
observation_ms: 500,
include_accessibility_text: false,
include_console_text: false,
include_json_body_shapes: false,
include_websocket_shapes: false,
include_script_sources: false,
include_storage_keys: false,
});
expect(
inspectWebPageInputSchema.safeParse({
cdp_endpoint: "http://127.0.0.1:9222",
allowed_origins: ["https://app.example.test"],
target_id: "page-1",
unknown_field: true,
}).success,
).toBe(false);
});
});
describe("browser observation sensitive surfaces and retention", () => {
it("accepts selected sensitive capture surfaces without extra flags", () => {
const base = {
cdp_endpoint: "http://127.0.0.1:9222",
allowed_origins: ["https://app.example.test"],
target_id: "page-1",
};
for (const input of [
{ include_console_text: true },
{ include_json_body_shapes: true },
{ include_websocket_shapes: true },
])
expect(
inspectWebPageInputSchema.safeParse({ ...base, ...input }).success,
).toBe(true);
expect(
inspectWebPageInputSchema.safeParse({
...base,
include_storage_fingerprints: true,
}).success,
).toBe(false);
});
it("accepts screenshot capture without an extra approval flag", () => {
const input = {
cdp_endpoint: "http://127.0.0.1:9222",
allowed_origins: ["https://app.example.test"],
target_id: "page-1",
};
expect(captureWebScreenshotInputSchema.safeParse(input).success).toBe(true);
});
it("removes only URL userinfo and preserves the original query and fragment", () => {
expect(
sanitizeBrowserUrl(
"https://user:pass@app.example.test/path?token=secret&mode=full&token=again#part",
),
).toEqual({
url: "https://app.example.test/path?token=secret&mode=full&token=again#part",
origin: "https://app.example.test",
query_parameter_names: ["token", "mode", "token"],
redacted: true,
});
const backslashAuthority = String.raw`https:\\user:pass@app.example.test/path?x=a+b&x=two#part`;
const sanitized = sanitizeBrowserUrl(backslashAuthority);
expect(sanitized.url).toBe(
"https://app.example.test/path?x=a+b&x=two#part",
);
expect(sanitized.url).not.toContain("user:pass");
expect(sanitized.redacted).toBe(true);
});
it("retains ordinary query values and malformed URL diagnostics", () => {
const longName = `a${"x".repeat(400)}`;
const parameters = [
`${longName}=secret`,
...Array.from(
{ length: 300 },
(_value, index) => `k${String(index).padStart(3, "0")}=secret`,
),
].join("&");
const sanitized = sanitizeBrowserUrl(
`https://app.example.test/path?${parameters}`,
);
expect(sanitized.query_parameter_names).toHaveLength(301);
expect(sanitized.query_parameter_names).toContain(longName);
expect(sanitized.url).toContain(`${longName}=secret`);
expect(sanitized.url).toContain("k299=secret");
const oversized = sanitizeBrowserUrl(
`https://app.example.test/${"p".repeat(70_000)}`,
);
expect(oversized.origin).toBe("https://app.example.test");
expect(oversized.url.length).toBeGreaterThan(70_000);
expect(oversized.redacted).toBe(false);
const malformed = "https://[invalid?token=diagnostic#fragment";
expect(sanitizeBrowserUrl(malformed)).toEqual({
url: malformed,
origin: null,
query_parameter_names: [],
redacted: false,
});
expect(sanitizeEndpointCandidate(malformed)).toBe(malformed);
expect(sanitizeEndpointCandidate("/api/search?q=rea#results")).toBe(
"/api/search?q=rea#results",
);
});
});