refactor(browser): extract enrichTabResponseBody + add 10 unit tests

Extracted the URL enrichment logic from withRouteTabContext into a pure,
testable function. Tests cover: enrichment of ok responses, postRunUrl
preference, tab.url fallback, existing field preservation, non-ok/null/
array/primitive rejection, and missing URL handling.
This commit is contained in:
zeroaltitude 2026-03-04 15:45:36 -07:00
parent 450ed33f94
commit a8c59f3c58
No known key found for this signature in database
GPG Key ID: 77592FB1C703882E
2 changed files with 111 additions and 32 deletions

View File

@ -0,0 +1,63 @@
import { describe, expect, it } from "vitest";
import { enrichTabResponseBody } from "./agent.shared.js";
const TAB = { targetId: "tid-1", url: "https://example.com/page" };
describe("enrichTabResponseBody", () => {
it("adds targetId and url to successful response", () => {
const body: Record<string, unknown> = { ok: true, data: "snapshot" };
const result = enrichTabResponseBody(body, TAB);
expect(result).toBe(true);
expect(body.targetId).toBe("tid-1");
expect(body.url).toBe("https://example.com/page");
});
it("prefers postRunUrl over tab.url", () => {
const body: Record<string, unknown> = { ok: true };
enrichTabResponseBody(body, TAB, "https://example.com/after-navigate");
expect(body.url).toBe("https://example.com/after-navigate");
});
it("falls back to tab.url when postRunUrl is undefined", () => {
const body: Record<string, unknown> = { ok: true };
enrichTabResponseBody(body, TAB, undefined);
expect(body.url).toBe("https://example.com/page");
});
it("does not overwrite existing targetId", () => {
const body: Record<string, unknown> = { ok: true, targetId: "existing-id" };
enrichTabResponseBody(body, TAB);
expect(body.targetId).toBe("existing-id");
});
it("does not overwrite existing url", () => {
const body: Record<string, unknown> = { ok: true, url: "https://existing.com" };
enrichTabResponseBody(body, TAB, "https://new.com");
expect(body.url).toBe("https://existing.com");
});
it("returns false for non-ok responses", () => {
const body = { ok: false, error: "not found" };
expect(enrichTabResponseBody(body, TAB)).toBe(false);
expect((body as Record<string, unknown>).targetId).toBeUndefined();
});
it("returns false for null body", () => {
expect(enrichTabResponseBody(null, TAB)).toBe(false);
});
it("returns false for array body", () => {
expect(enrichTabResponseBody([{ ok: true }], TAB)).toBe(false);
});
it("returns false for primitive body", () => {
expect(enrichTabResponseBody("ok", TAB)).toBe(false);
});
it("handles tab with no url and no postRunUrl", () => {
const body: Record<string, unknown> = { ok: true };
enrichTabResponseBody(body, { targetId: "tid-2" });
expect(body.targetId).toBe("tid-2");
expect(body.url).toBeUndefined();
});
});

View File

@ -5,6 +5,38 @@ import type { BrowserRouteContext, ProfileContext } from "../server-context.js";
import type { BrowserRequest, BrowserResponse } from "./types.js";
import { getProfileContext, jsonError } from "./utils.js";
/**
* Enrich a tab-targeting response body with targetId and URL.
* Pure function no side effects, no I/O. Mutates `body` in place.
*
* @returns true if enrichment was applied, false if body was not eligible.
*/
export function enrichTabResponseBody(
body: unknown,
tab: { targetId: string; url?: string },
postRunUrl?: string,
): boolean {
if (
!body ||
typeof body !== "object" ||
Array.isArray(body) ||
(body as Record<string, unknown>).ok !== true
) {
return false;
}
const record = body as Record<string, unknown>;
if (record.targetId === undefined) {
record.targetId = tab.targetId;
}
if (record.url === undefined) {
const resolvedUrl = postRunUrl || tab.url;
if (resolvedUrl) {
record.url = resolvedUrl;
}
}
return true;
}
export const SELECTOR_UNSUPPORTED_MESSAGE = [
"Error: 'selector' is not supported. Use 'ref' from snapshot instead.",
"",
@ -148,41 +180,25 @@ export async function withRouteTabContext<T>(
// Now enrich and flush the intercepted response body.
if (jsonCalled) {
if (
interceptedBody &&
typeof interceptedBody === "object" &&
!Array.isArray(interceptedBody) &&
(interceptedBody as Record<string, unknown>).ok === true
) {
const record = interceptedBody as Record<string, unknown>;
if (record.targetId === undefined) {
record.targetId = tab.targetId;
}
if (record.url === undefined) {
// Resolve live URL *after* the handler ran, so navigating actions
// report the post-action URL. Try Playwright first (actual page
// state), fall back to tab metadata URL.
let postRunUrl: string | undefined;
try {
const pwMod = await getPwAiModuleBase({ mode: "soft" });
if (pwMod?.getPageForTargetId) {
const page = await pwMod.getPageForTargetId({
cdpUrl: profileCtx.profile.cdpUrl,
targetId: tab.targetId,
});
if (page) {
postRunUrl = page.url();
}
}
} catch {
// Playwright unavailable — fall back to tab.url
}
const resolvedUrl = postRunUrl || tab.url;
if (resolvedUrl) {
record.url = resolvedUrl;
// Resolve live URL *after* the handler ran, so navigating actions
// report the post-action URL. Try Playwright first (actual page
// state), fall back to tab metadata URL.
let postRunUrl: string | undefined;
try {
const pwMod = await getPwAiModuleBase({ mode: "soft" });
if (pwMod?.getPageForTargetId) {
const page = await pwMod.getPageForTargetId({
cdpUrl: profileCtx.profile.cdpUrl,
targetId: tab.targetId,
});
if (page) {
postRunUrl = page.url();
}
}
} catch {
// Playwright unavailable — fall back to tab.url
}
enrichTabResponseBody(interceptedBody, tab, postRunUrl);
// Restore res.json before flushing so that if originalJson throws
// (e.g. BigInt serialization), the outer catch can still send errors.
params.res.json = originalJson;