test: share browser tab route fixtures

This commit is contained in:
Peter Steinberger
2026-04-20 16:29:20 +01:00
parent 74967abd51
commit 0010b246c0

View File

@@ -13,6 +13,51 @@ vi.mock("../navigation-guard.js", () => navigationGuardMocks);
const { registerBrowserTabRoutes } = await import("./tabs.js");
type ProfileContext = ReturnType<typeof createProfileContext>;
type TabFixture = {
targetId: string;
title: string;
url: string;
type: "page";
};
const publicTab = (overrides: Partial<TabFixture> = {}): TabFixture => ({
targetId: "T1",
title: "Public",
url: "https://example.com",
type: "page",
...overrides,
});
const internalTab = (overrides: Partial<TabFixture> = {}): TabFixture => ({
targetId: "T2",
title: "Internal",
url: "http://169.254.169.254/latest/meta-data/",
type: "page",
...overrides,
});
const createProfileWithTabs = (tabs: TabFixture[]) =>
createProfileContext({
listTabs: vi.fn(async () => tabs),
});
async function expectBrowserNotRunningAction(action: "close" | "select") {
const profileCtx = createProfileContext({
isReachable: vi.fn(async () => false),
});
const response = await callTabsAction({
body: { action, index: 0 },
profileCtx,
});
expect(response.statusCode).toBe(409);
expect(response.body).toEqual({ error: "browser not running" });
expect(profileCtx.listTabs).not.toHaveBeenCalled();
expect(action === "close" ? profileCtx.closeTab : profileCtx.focusTab).not.toHaveBeenCalled();
}
function createProfileContext(overrides?: Partial<ReturnType<typeof baseProfileContext>>) {
return {
...baseProfileContext(),
@@ -55,10 +100,7 @@ function baseProfileContext() {
};
}
function createRouteContext(
profileCtx: ReturnType<typeof createProfileContext>,
options?: { ssrfPolicy?: unknown },
) {
function createRouteContext(profileCtx: ProfileContext, options?: { ssrfPolicy?: unknown }) {
return {
state: () => ({ resolved: { ssrfPolicy: options?.ssrfPolicy } }),
forProfile: () => profileCtx,
@@ -83,57 +125,45 @@ function createRouteContext(
};
}
async function callTabsAction(params: {
body: Record<string, unknown>;
profileCtx: ReturnType<typeof createProfileContext>;
async function callTabsRoute(params: {
method: "get" | "post";
path: "/tabs" | "/tabs/action" | "/tabs/focus";
body?: Record<string, unknown>;
profileCtx: ProfileContext;
ssrfPolicy?: unknown;
}) {
const { app, postHandlers } = createBrowserRouteApp();
const { app, getHandlers, postHandlers } = createBrowserRouteApp();
registerBrowserTabRoutes(
app,
createRouteContext(params.profileCtx, { ssrfPolicy: params.ssrfPolicy }) as never,
);
const handler = postHandlers.get("/tabs/action");
const handler =
params.method === "get" ? getHandlers.get(params.path) : postHandlers.get(params.path);
expect(handler).toBeTypeOf("function");
const response = createBrowserRouteResponse();
await handler?.({ params: {}, query: {}, body: params.body }, response.res);
await handler?.({ params: {}, query: {}, body: params.body ?? {} }, response.res);
return response;
}
async function callTabsList(params: {
profileCtx: ReturnType<typeof createProfileContext>;
async function callTabsAction(params: {
body: Record<string, unknown>;
profileCtx: ProfileContext;
ssrfPolicy?: unknown;
}) {
const { app, getHandlers } = createBrowserRouteApp();
registerBrowserTabRoutes(
app,
createRouteContext(params.profileCtx, { ssrfPolicy: params.ssrfPolicy }) as never,
);
const handler = getHandlers.get("/tabs");
expect(handler).toBeTypeOf("function");
return await callTabsRoute({ ...params, method: "post", path: "/tabs/action" });
}
const response = createBrowserRouteResponse();
await handler?.({ params: {}, query: {}, body: {} }, response.res);
return response;
async function callTabsList(params: { profileCtx: ProfileContext; ssrfPolicy?: unknown }) {
return await callTabsRoute({ ...params, method: "get", path: "/tabs" });
}
async function callTabsFocus(params: {
profileCtx: ReturnType<typeof createProfileContext>;
profileCtx: ProfileContext;
body: Record<string, unknown>;
ssrfPolicy?: unknown;
}) {
const { app, postHandlers } = createBrowserRouteApp();
registerBrowserTabRoutes(
app,
createRouteContext(params.profileCtx, { ssrfPolicy: params.ssrfPolicy }) as never,
);
const handler = postHandlers.get("/tabs/focus");
expect(handler).toBeTypeOf("function");
const response = createBrowserRouteResponse();
await handler?.({ params: {}, query: {}, body: params.body }, response.res);
return response;
return await callTabsRoute({ ...params, method: "post", path: "/tabs/focus" });
}
describe("browser tab routes", () => {
@@ -147,35 +177,11 @@ describe("browser tab routes", () => {
});
it("returns browser-not-running for close when the browser is not reachable", async () => {
const profileCtx = createProfileContext({
isReachable: vi.fn(async () => false),
});
const response = await callTabsAction({
body: { action: "close", index: 0 },
profileCtx,
});
expect(response.statusCode).toBe(409);
expect(response.body).toEqual({ error: "browser not running" });
expect(profileCtx.listTabs).not.toHaveBeenCalled();
expect(profileCtx.closeTab).not.toHaveBeenCalled();
await expectBrowserNotRunningAction("close");
});
it("returns browser-not-running for select when the browser is not reachable", async () => {
const profileCtx = createProfileContext({
isReachable: vi.fn(async () => false),
});
const response = await callTabsAction({
body: { action: "select", index: 0 },
profileCtx,
});
expect(response.statusCode).toBe(409);
expect(response.body).toEqual({ error: "browser not running" });
expect(profileCtx.listTabs).not.toHaveBeenCalled();
expect(profileCtx.focusTab).not.toHaveBeenCalled();
await expectBrowserNotRunningAction("select");
});
it("redacts blocked tab URLs from GET /tabs", async () => {
@@ -187,22 +193,7 @@ describe("browser tab routes", () => {
}
},
);
const profileCtx = createProfileContext({
listTabs: vi.fn(async () => [
{
targetId: "T1",
title: "Public",
url: "https://example.com",
type: "page",
},
{
targetId: "T2",
title: "Internal",
url: "http://169.254.169.254/latest/meta-data/",
type: "page",
},
]),
});
const profileCtx = createProfileWithTabs([publicTab(), internalTab()]);
const response = await callTabsList({
profileCtx,
@@ -214,16 +205,11 @@ describe("browser tab routes", () => {
running: true,
tabs: [
{
targetId: "T1",
title: "Public",
url: "https://example.com",
type: "page",
...publicTab(),
},
{
targetId: "T2",
title: "Internal",
...internalTab(),
url: "",
type: "page",
},
],
});
@@ -234,16 +220,7 @@ describe("browser tab routes", () => {
navigationGuardMocks.assertBrowserNavigationResultAllowed.mockRejectedValueOnce(
new Error("blocked"),
);
const profileCtx = createProfileContext({
listTabs: vi.fn(async () => [
{
targetId: "T2",
title: "Internal",
url: "http://169.254.169.254/latest/meta-data/",
type: "page",
},
]),
});
const profileCtx = createProfileWithTabs([internalTab()]);
const response = await callTabsFocus({
profileCtx,
@@ -303,22 +280,7 @@ describe("browser tab routes", () => {
navigationGuardMocks.assertBrowserNavigationResultAllowed.mockRejectedValueOnce(
new Error("blocked"),
);
const profileCtx = createProfileContext({
listTabs: vi.fn(async () => [
{
targetId: "T1",
title: "Public",
url: "https://example.com",
type: "page",
},
{
targetId: "T2",
title: "Internal",
url: "http://169.254.169.254/latest/meta-data/",
type: "page",
},
]),
});
const profileCtx = createProfileWithTabs([publicTab(), internalTab()]);
const response = await callTabsAction({
body: { action: "select", index: 1 },
@@ -332,14 +294,7 @@ describe("browser tab routes", () => {
it("does not run SSRF result validation for /tabs/focus when policy is not configured", async () => {
const profileCtx = createProfileContext({
listTabs: vi.fn(async () => [
{
targetId: "T2",
title: "Internal",
url: "http://169.254.169.254/latest/meta-data/",
type: "page",
},
]),
listTabs: vi.fn(async () => [internalTab()]),
});
const response = await callTabsFocus({
@@ -356,20 +311,7 @@ describe("browser tab routes", () => {
it("does not run SSRF result validation for /tabs/action select when policy is not configured", async () => {
const profileCtx = createProfileContext({
listTabs: vi.fn(async () => [
{
targetId: "T1",
title: "Public",
url: "https://example.com",
type: "page",
},
{
targetId: "T2",
title: "Internal",
url: "http://169.254.169.254/latest/meta-data/",
type: "page",
},
]),
listTabs: vi.fn(async () => [publicTab(), internalTab()]),
});
const response = await callTabsAction({
@@ -394,18 +336,11 @@ describe("browser tab routes", () => {
);
const profileCtx = createProfileContext({
listTabs: vi.fn(async () => [
{
targetId: "T1",
title: "Public",
url: "https://example.com",
type: "page",
},
{
targetId: "T2",
publicTab(),
internalTab({
title: "Private Admin",
url: "http://10.0.0.5/admin",
type: "page",
},
}),
]),
});
@@ -420,16 +355,11 @@ describe("browser tab routes", () => {
ok: true,
tabs: [
{
targetId: "T1",
title: "Public",
url: "https://example.com",
type: "page",
...publicTab(),
},
{
targetId: "T2",
title: "Private Admin",
...internalTab({ title: "Private Admin" }),
url: "",
type: "page",
},
],
});