From 3db0dc12b02474da043bd08a5654550f3d0b9637 Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Thu, 6 Aug 2026 23:28:52 +0200 Subject: [PATCH 1/3] test(routing): pin the prepared route entry points production actually calls executePreparedAppRoute, executePreparedPagesRoute, and resolvePreparedRouteMethods (called from handler.ts) had zero direct test coverage; the existing suite only exercised the older executeAppRoute / executePagesRoute options-bag entry points. Add characterization tests for happy path, handler-throw failure, and method resolution for each. --- src/routing/api/route-executor.test.ts | 163 ++++++++++++++++++++++++- 1 file changed, 162 insertions(+), 1 deletion(-) diff --git a/src/routing/api/route-executor.test.ts b/src/routing/api/route-executor.test.ts index 5c85dc92c8..4d34b24cc9 100644 --- a/src/routing/api/route-executor.test.ts +++ b/src/routing/api/route-executor.test.ts @@ -1,11 +1,15 @@ import "#veryfront/schemas/_test-setup.ts"; -import { assertEquals } from "#veryfront/testing/assert.ts"; +import { assertEquals, assertRejects, assertStringIncludes } from "#veryfront/testing/assert.ts"; import { afterEach, describe, it } from "#veryfront/testing/bdd.ts"; import { __serializeRequestForTests, executeAppRoute as executeAppRouteRaw, executePagesRoute as executePagesRouteRaw, + executePreparedAppRoute, + executePreparedPagesRoute, type ExecuteRouteOptions, + type PreparedRouteExecutionOptions, + resolvePreparedRouteMethods, } from "./route-executor.ts"; import type { RouteMatch } from "./api-route-matcher.ts"; import type { RuntimeAdapter } from "#veryfront/platform/adapters/base.ts"; @@ -136,6 +140,19 @@ async function isolatedRouteOptions( }; } +async function preparedRouteOptions( + source: string, + executionScopeId: string, +): Promise { + return { + executionScopeId, + module: await prepareModuleSource(source), + modulePath: "/tmp/test/handler.ts", + projectDir: "/tmp/test", + isLocalProject: false, + }; +} + describe("routing/api/route-executor", () => { describe("application request boundary", () => { it("withholds infrastructure credentials from remote project code", async () => { @@ -946,4 +963,148 @@ describe("routing/api/route-executor", () => { assertEquals(response.body, null); }); }); + + describe("executePreparedAppRoute() / executePreparedPagesRoute() / resolvePreparedRouteMethods()", () => { + afterEach(async () => { + Deno.env.delete("WORKER_ISOLATION_ENABLED"); + Deno.env.delete("WORKER_ISOLATION_API"); + await __resetPoolForTests(); + }); + + it("executes a prepared app route module in the worker and returns its response", async () => { + Deno.env.set("WORKER_ISOLATION_ENABLED", "1"); + Deno.env.set("WORKER_ISOLATION_API", "1"); + await __resetPoolForTests(); + + const options = await preparedRouteOptions( + "export function GET(_req, ctx) { return Response.json({ id: ctx.params.id }); }", + "prepared-app-happy", + ); + + const response = await runWithExactSourceIntegrationPolicy( + normalizeSourceIntegrationPolicy({ allow: {} }), + () => + executePreparedAppRoute( + new Request("http://localhost/api/users/42", { method: "GET" }), + makeMatch("/api/users/[id]", "/tmp/test/handler.ts", { id: "42" }), + "/api/users/42", + options, + ), + ); + + assertEquals(response.status, 200); + assertStringIncludes(response.headers.get("content-type") ?? "", "application/json"); + assertEquals(await response.json(), { id: "42" }); + }); + + it("returns a 500 error response when the prepared app route handler throws", async () => { + Deno.env.set("WORKER_ISOLATION_ENABLED", "1"); + Deno.env.set("WORKER_ISOLATION_API", "1"); + await __resetPoolForTests(); + + const options = await preparedRouteOptions( + "export function GET() { throw new Error('prepared app boom'); }", + "prepared-app-error", + ); + + const response = await runWithExactSourceIntegrationPolicy( + normalizeSourceIntegrationPolicy({ allow: {} }), + () => + executePreparedAppRoute( + new Request("http://localhost/api/test", { method: "GET" }), + makeMatch(), + "/api/test", + options, + ), + ); + + assertEquals(response.status, 500); + }); + + it("executes a prepared pages route module in the worker and returns its response", async () => { + Deno.env.set("WORKER_ISOLATION_ENABLED", "1"); + Deno.env.set("WORKER_ISOLATION_API", "1"); + await __resetPoolForTests(); + + const options = await preparedRouteOptions( + "export function GET(ctx) { return ctx.text('prepared pages ok'); }", + "prepared-pages-happy", + ); + + const response = await runWithExactSourceIntegrationPolicy( + normalizeSourceIntegrationPolicy({ allow: {} }), + () => + executePreparedPagesRoute( + new Request("http://localhost/api/test", { method: "GET" }), + makeMatch(), + "/api/test", + options, + ), + ); + + assertEquals(response.status, 200); + assertEquals(await response.text(), "prepared pages ok"); + }); + + it("returns a 500 error response when the prepared pages route handler throws", async () => { + Deno.env.set("WORKER_ISOLATION_ENABLED", "1"); + Deno.env.set("WORKER_ISOLATION_API", "1"); + await __resetPoolForTests(); + + const options = await preparedRouteOptions( + "export function GET() { throw new Error('prepared pages boom'); }", + "prepared-pages-error", + ); + + const response = await runWithExactSourceIntegrationPolicy( + normalizeSourceIntegrationPolicy({ allow: {} }), + () => + executePreparedPagesRoute( + new Request("http://localhost/api/test", { method: "GET" }), + makeMatch(), + "/api/test", + options, + ), + ); + + assertEquals(response.status, 500); + }); + + it("resolves the exported HTTP methods for a prepared route", async () => { + Deno.env.set("WORKER_ISOLATION_ENABLED", "1"); + Deno.env.set("WORKER_ISOLATION_API", "1"); + await __resetPoolForTests(); + + const options = await preparedRouteOptions( + "export function GET() {} export function POST() {}", + "prepared-methods-happy", + ); + + const methods = await runWithExactSourceIntegrationPolicy( + normalizeSourceIntegrationPolicy({ allow: {} }), + () => resolvePreparedRouteMethods(undefined, options), + ); + + assertEquals(methods, ["GET", "HEAD", "POST", "OPTIONS"]); + }); + + it("rejects when the prepared module has no callable route export", async () => { + Deno.env.set("WORKER_ISOLATION_ENABLED", "1"); + Deno.env.set("WORKER_ISOLATION_API", "1"); + await __resetPoolForTests(); + + const options = await preparedRouteOptions( + "export const notARouteHandler = 1;", + "prepared-methods-error", + ); + + await assertRejects( + () => + runWithExactSourceIntegrationPolicy( + normalizeSourceIntegrationPolicy({ allow: {} }), + () => resolvePreparedRouteMethods(undefined, options), + ), + ); + }); + }); }); From 522a1801dcbbbc72e2aaede1fa5a2a0578502a4f Mon Sep 17 00:00:00 2001 From: Koji Wakayama Date: Thu, 6 Aug 2026 23:34:10 +0200 Subject: [PATCH 2/3] test: tighten rejection assertion for prepared route without export The test for resolvePreparedRouteMethods failure-path now pins both the error type (Error) and the exact message ("Prepared API route module has no callable route export") to prevent accidental pass-on-wrong-failure. --- src/routing/api/route-executor.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/routing/api/route-executor.test.ts b/src/routing/api/route-executor.test.ts index 4d34b24cc9..6d9f517f7c 100644 --- a/src/routing/api/route-executor.test.ts +++ b/src/routing/api/route-executor.test.ts @@ -1104,6 +1104,8 @@ describe("routing/api/route-executor", () => { normalizeSourceIntegrationPolicy({ allow: {} }), () => resolvePreparedRouteMethods(undefined, options), ), + Error, + "Prepared API route module has no callable route export", ); }); }); From 753ab344b14489773a62392c1f3402841e5e0962 Mon Sep 17 00:00:00 2001 From: Kentaro Wakayama Date: Fri, 7 Aug 2026 04:55:03 +0200 Subject: [PATCH 3/3] test(routing): use synthetic fixture paths instead of /tmp Route-executor tests never touch these paths on disk; projectDir only feeds createProjectScopedFs against a mocked FileSystemAdapter. Use the synthetic /test/project path that src/routing/api/handler.test.ts already uses, so no test source carries a local absolute path. --- src/routing/api/route-executor.test.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/routing/api/route-executor.test.ts b/src/routing/api/route-executor.test.ts index 6d9f517f7c..7a86019818 100644 --- a/src/routing/api/route-executor.test.ts +++ b/src/routing/api/route-executor.test.ts @@ -132,8 +132,8 @@ async function isolatedRouteOptions( executionScopeId: string, ): Promise { return { - modulePath: "/tmp/test/handler.ts", - projectDir: "/tmp/test", + modulePath: "/test/project/handler.ts", + projectDir: "/test/project", isLocalProject: false, preparedModule: await prepareModuleSource(source), executionScopeId, @@ -147,8 +147,8 @@ async function preparedRouteOptions( return { executionScopeId, module: await prepareModuleSource(source), - modulePath: "/tmp/test/handler.ts", - projectDir: "/tmp/test", + modulePath: "/test/project/handler.ts", + projectDir: "/test/project", isLocalProject: false, }; } @@ -986,7 +986,7 @@ describe("routing/api/route-executor", () => { () => executePreparedAppRoute( new Request("http://localhost/api/users/42", { method: "GET" }), - makeMatch("/api/users/[id]", "/tmp/test/handler.ts", { id: "42" }), + makeMatch("/api/users/[id]", "/test/project/handler.ts", { id: "42" }), "/api/users/42", options, ),