11import { describe , expect , it , vi , beforeEach } from "vitest" ;
22import { parseCookies } from "h3" ;
33import type { FetchEvent } from "../server/types.ts" ;
4+ import { getFetchEvent } from "../server/fetchEvent.ts" ;
5+ import { getServerFunction } from "./registration.ts" ;
46
57vi . mock ( "h3" , ( ) => ( {
68 parseCookies : vi . fn ( ( ) => ( { } ) ) ,
@@ -16,6 +18,16 @@ vi.mock("solid-js/web/storage", () => ({
1618
1719vi . mock ( "solid-start:server-fn-manifest" , ( ) => ( { } ) ) ;
1820
21+ const configuredErrorHandler = vi . hoisted ( ( ) => ( {
22+ current : undefined as ( ( thrown : unknown ) => unknown ) | undefined ,
23+ } ) ) ;
24+
25+ vi . mock ( "solid-start:server-fn-error-handler" , ( ) => ( {
26+ get default ( ) {
27+ return configuredErrorHandler . current ;
28+ } ,
29+ } ) ) ;
30+
1931vi . mock ( "../server/handler.ts" , ( ) => ( {
2032 createPageEvent : vi . fn ( ) ,
2133} ) ) ;
@@ -25,6 +37,16 @@ vi.mock("../server/fetchEvent.ts", () => ({
2537 mergeResponseHeaders : vi . fn ( ) ,
2638} ) ) ;
2739
40+ vi . mock ( "./registration.ts" , ( ) => ( {
41+ getServerFunction : vi . fn ( ) ,
42+ hasServerFunction : vi . fn ( ( ) => true ) ,
43+ } ) ) ;
44+
45+ vi . mock ( "./serialization.ts" , ( ) => ( {
46+ serializeToJSONStream : vi . fn ( ( ) => "serialized" ) ,
47+ serializeToJSStream : vi . fn ( ( ) => "serialized" ) ,
48+ } ) ) ;
49+
2850function createMockFetchEvent (
2951 headers : Record < string , string > = { } ,
3052 setCookies : string [ ] = [ ] ,
@@ -158,3 +180,70 @@ describe("createSingleFlightHeaders", () => {
158180 expect ( ( ) => createSingleFlightHeaders ( sourceEvent , { some : "value" } ) ) . not . toThrow ( ) ;
159181 } ) ;
160182} ) ;
183+
184+ describe ( "the configured server function error handler" , ( ) => {
185+ const callThrowing = async ( thrown : unknown ) => {
186+ const request = new Request ( "http://localhost/_server" , {
187+ method : "POST" ,
188+ headers : { "X-Server-Id" : "fn" , "X-Server-Instance" : "server-fn:1" } ,
189+ } ) ;
190+ const h3Event = { res : { headers : new Headers ( ) , status : 200 } } ;
191+ vi . mocked ( getFetchEvent ) . mockReturnValue ( {
192+ request,
193+ response : { headers : { getSetCookie : ( ) => [ ] } } ,
194+ nativeEvent : h3Event ,
195+ locals : { } ,
196+ } as unknown as FetchEvent ) ;
197+ vi . mocked ( getServerFunction ) . mockReturnValue ( ( ) => {
198+ throw thrown ;
199+ } ) ;
200+ const { handleServerFunction } = await import ( "./handler.ts" ) ;
201+ await handleServerFunction ( h3Event as never ) ;
202+ return h3Event ;
203+ } ;
204+
205+ beforeEach ( ( ) => {
206+ vi . clearAllMocks ( ) ;
207+ configuredErrorHandler . current = undefined ;
208+ } ) ;
209+
210+ it ( "passes the thrown value to the handler" , async ( ) => {
211+ const thrown = new Error ( "boom" ) ;
212+ configuredErrorHandler . current = vi . fn ( ( ) => undefined ) ;
213+
214+ await callThrowing ( thrown ) ;
215+
216+ expect ( configuredErrorHandler . current ) . toHaveBeenCalledWith ( thrown ) ;
217+ } ) ;
218+
219+ it ( "serializes the replacement the handler returns" , async ( ) => {
220+ configuredErrorHandler . current = ( ) => new Error ( "replaced" ) ;
221+
222+ const h3Event = await callThrowing ( new Error ( "boom" ) ) ;
223+
224+ expect ( h3Event . res . headers . get ( "X-Error" ) ) . toBe ( "replaced" ) ;
225+ } ) ;
226+
227+ it ( "treats a Response the handler returns as control flow" , async ( ) => {
228+ configuredErrorHandler . current = ( ) => new Response ( null , { status : 403 } ) ;
229+
230+ const h3Event = await callThrowing ( new Error ( "boom" ) ) ;
231+
232+ expect ( h3Event . res . status ) . toBe ( 403 ) ;
233+ expect ( h3Event . res . headers . get ( "X-Error" ) ) . toBe ( "true" ) ;
234+ } ) ;
235+
236+ it ( "keeps the original error when the handler returns nothing" , async ( ) => {
237+ configuredErrorHandler . current = ( ) => undefined ;
238+
239+ const h3Event = await callThrowing ( new Error ( "boom" ) ) ;
240+
241+ expect ( h3Event . res . headers . get ( "X-Error" ) ) . toBe ( "boom" ) ;
242+ } ) ;
243+
244+ it ( "leaves the response untouched when no handler is configured" , async ( ) => {
245+ const h3Event = await callThrowing ( new Error ( "boom" ) ) ;
246+
247+ expect ( h3Event . res . headers . get ( "X-Error" ) ) . toBe ( "boom" ) ;
248+ } ) ;
249+ } ) ;
0 commit comments