From da21ce6f853ee9811f01c995fa6ee981a1e3f854 Mon Sep 17 00:00:00 2001 From: Atila Fassina Date: Mon, 10 Feb 2025 13:40:26 +0100 Subject: [PATCH 1/2] tests: add e2e for a non-existing server function --- packages/start/src/runtime/server-handler.ts | 72 ++++++++++++------- .../tests/cypress/e2e/server-function.cy.ts | 26 ++++--- 2 files changed, 62 insertions(+), 36 deletions(-) diff --git a/packages/start/src/runtime/server-handler.ts b/packages/start/src/runtime/server-handler.ts index 2f33b0ace..46e1603c5 100644 --- a/packages/start/src/runtime/server-handler.ts +++ b/packages/start/src/runtime/server-handler.ts @@ -16,7 +16,13 @@ import { import { sharedConfig } from "solid-js"; import { renderToString } from "solid-js/web"; import { provideRequestEvent } from "solid-js/web/storage"; -import { eventHandler, parseCookies, setHeader, setResponseStatus, type HTTPEvent } from "vinxi/http"; +import { + eventHandler, + parseCookies, + setHeader, + setResponseStatus, + type HTTPEvent +} from "vinxi/http"; import invariant from "vinxi/lib/invariant"; import { cloneEvent, getFetchEvent, mergeResponseHeaders } from "../server/fetchEvent"; import { getExpectedRedirectStatus } from "../server/handler"; @@ -87,14 +93,23 @@ async function handleServerFunction(h3Event: HTTPEvent) { } else { functionId = url.searchParams.get("id"); name = url.searchParams.get("name"); - if (!functionId || !name) return new Response("Server function not found", { status: 404 }); + + if (!functionId || !name) { + return process.env.NODE_ENV === "development" + ? new Response("Server function not found", { status: 404 }) + : new Response(null, { status: 404 }); + } } const serverFnInfo = serverFnManifest[functionId]; let fnModule: undefined | { [key: string]: any }; - if (!serverFnInfo) return new Response("Server function not found", { status: 404 }); - + if (!serverFnInfo) { + return process.env.NODE_ENV === "development" + ? new Response("Server function not found", { status: 404 }) + : new Response(null, { status: 404 }); + } + if (process.env.NODE_ENV === "development") { // In dev, we use Vinxi to get the "server" server-side router // Then we use that router's devServer.ssrLoadModule to get the serverFn @@ -118,19 +133,19 @@ async function handleServerFunction(h3Event: HTTPEvent) { const json = JSON.parse(args); (json.t ? (fromJSON(json, { - plugins: [ - CustomEventPlugin, - DOMExceptionPlugin, - EventPlugin, - FormDataPlugin, - HeadersPlugin, - ReadableStreamPlugin, - RequestPlugin, - ResponsePlugin, - URLSearchParamsPlugin, - URLPlugin - ] - }) as any) + plugins: [ + CustomEventPlugin, + DOMExceptionPlugin, + EventPlugin, + FormDataPlugin, + HeadersPlugin, + ReadableStreamPlugin, + RequestPlugin, + ResponsePlugin, + URLSearchParamsPlugin, + URLPlugin + ] + }) as any) : json ).forEach((arg: any) => parsed.push(arg)); } @@ -148,7 +163,8 @@ async function handleServerFunction(h3Event: HTTPEvent) { const isReadableStream = h3Request instanceof ReadableStream; const hasReadableStream = (h3Request as EdgeIncomingMessage).body instanceof ReadableStream; const isH3EventBodyStreamLocked = - (isReadableStream && h3Request.locked) || (hasReadableStream && ((h3Request as EdgeIncomingMessage).body as ReadableStream).locked); + (isReadableStream && h3Request.locked) || + (hasReadableStream && ((h3Request as EdgeIncomingMessage).body as ReadableStream).locked); const requestBody = isReadableStream ? h3Request : h3Request.body; if ( @@ -272,13 +288,15 @@ function handleNoJS(result: any, request: Request, parsed: any[], thrown?: boole if (result) { headers.append( "Set-Cookie", - `flash=${encodeURIComponent(JSON.stringify({ - url: url.pathname + url.search, - result: isError ? result.message : result, - thrown: thrown, - error: isError, - input: [...parsed.slice(0, -1), [...parsed[parsed.length - 1].entries()]] - }))}; Secure; HttpOnly;` + `flash=${encodeURIComponent( + JSON.stringify({ + url: url.pathname + url.search, + result: isError ? result.message : result, + thrown: thrown, + error: isError, + input: [...parsed.slice(0, -1), [...parsed[parsed.length - 1].entries()]] + }) + )}; Secure; HttpOnly;` ); } return new Response(null, { @@ -302,7 +320,7 @@ function createSingleFlightHeaders(sourceEvent: FetchEvent) { useH3Internals = true; sourceEvent.nativeEvent.node.req.headers.cookie = ""; } - SetCookies.forEach((cookie) => { + SetCookies.forEach(cookie => { if (!cookie) return; const keyValue = cookie.split(";")[0]!; const [key, value] = keyValue.split("="); @@ -311,7 +329,7 @@ function createSingleFlightHeaders(sourceEvent: FetchEvent) { Object.entries(cookies).forEach(([key, value]) => { headers.append("cookie", `${key}=${value}`); useH3Internals && (sourceEvent.nativeEvent.node.req.headers.cookie += `${key}=${value};`); - }) + }); return headers; } diff --git a/packages/tests/cypress/e2e/server-function.cy.ts b/packages/tests/cypress/e2e/server-function.cy.ts index 24d166fbc..ac3830261 100644 --- a/packages/tests/cypress/e2e/server-function.cy.ts +++ b/packages/tests/cypress/e2e/server-function.cy.ts @@ -2,38 +2,46 @@ describe("server-function", () => { it("should have isServer false in the client", () => { cy.visit("/"); cy.get("#server-fn-test").contains('{"clientWithIsServer":false}'); - }) + }); it("should have isServer true in the server function - nested", () => { cy.visit("/is-server-nested"); cy.get("#server-fn-test").contains('{"serverFnWithIsServer":true}'); - }) + }); it("should have an id of type string in the server function meta - nested", () => { cy.visit("/server-function-meta-nested"); cy.get("#server-fn-test").contains('{"serverFnWithMeta":"string"}'); - }) + }); it("should externalize node builtin in server function - nested", () => { cy.visit("/node-builtin-nested"); cy.get("#server-fn-test").contains('{"serverFnWithNodeBuiltin":"can/externalize"}'); - }) + }); it("should externalize npm module in server function - nested", () => { cy.visit("npm-module-nested"); cy.get("#server-fn-test").contains('{"serverFnWithNpmModule":[2,4,6]}'); - }) + }); it("should have isServer true in the server function - toplevel", () => { cy.visit("/is-server-toplevel"); cy.get("#server-fn-test").contains('{"serverFnWithIsServer":true}'); - }) + }); it("should have an id of type string in the server function meta - toplevel", () => { cy.visit("/server-function-meta"); cy.get("#server-fn-test").contains('{"serverFnWithMeta":"string"}'); - }) + }); it("should externalize node builtin in server function - toplevel", () => { cy.visit("/node-builtin-toplevel"); cy.get("#server-fn-test").contains('{"serverFnWithNodeBuiltin":"can/externalize"}'); - }) + }); it("should externalize npm module in server function - toplevel", () => { cy.visit("npm-module-toplevel"); cy.get("#server-fn-test").contains('{"serverFnWithNpmModule":[2,4,6]}'); - }) + }); + it("should throw a 404 if function is not found", () => { + cy.request({ + url: "/_server/?name='not-found-function'", + failOnStatusCode: false + }) + .its("status") + .should("eq", 404); + }); }); From fc697d79022afb3fe622c87baa845dcc381a7183 Mon Sep 17 00:00:00 2001 From: Atila Fassina Date: Mon, 10 Feb 2025 13:43:14 +0100 Subject: [PATCH 2/2] chore: bump patch --- .changeset/heavy-shrimps-study.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/heavy-shrimps-study.md diff --git a/.changeset/heavy-shrimps-study.md b/.changeset/heavy-shrimps-study.md new file mode 100644 index 000000000..c1b4c35ec --- /dev/null +++ b/.changeset/heavy-shrimps-study.md @@ -0,0 +1,5 @@ +--- +"@solidjs/start": patch +--- + +Return `404` when server-function is not found