1- import { Readable } from "node:stream" ;
2- import { listen } from "listhen" ;
3- import { getQuery , joinURL } from "ufo" ;
4- import {
5- createApp ,
6- createError ,
7- eventHandler ,
8- readBody ,
9- readRawBody ,
10- toNodeListener ,
11- } from "h3" ;
121import {
132 describe ,
143 beforeEach ,
@@ -18,81 +7,66 @@ import {
187 expect ,
198 vi ,
209} from "vitest" ;
10+ import { Readable } from "node:stream" ;
11+ import { H3 , HTTPError , readBody , serve } from "h3" ;
2112import { Headers , FormData , Blob } from "node-fetch-native" ;
2213import { nodeMajorVersion } from "std-env" ;
2314import { $fetch } from "../src/node" ;
2415
2516describe ( "ofetch" , ( ) => {
26- let listener ;
27- const getURL = ( url ) => joinURL ( listener . url , url ) ;
17+ let listener : ReturnType < typeof serve > ;
18+ const getURL = ( url : string = "/" ) =>
19+ listener . url ! + ( url . replace ( / ^ \/ / , "" ) || "" ) ;
2820
2921 const fetch = vi . spyOn ( globalThis , "fetch" ) ;
3022
3123 beforeAll ( async ( ) => {
32- const app = createApp ( )
33- . use (
34- "/ok" ,
35- eventHandler ( ( ) => "ok" )
36- )
37- . use (
38- "/params" ,
39- eventHandler ( ( event ) => getQuery ( event . node . req . url || "" ) )
40- )
41- . use (
42- "/url" ,
43- eventHandler ( ( event ) => event . node . req . url )
44- )
45- . use (
46- "/echo" ,
47- eventHandler ( async ( event ) => ( {
48- path : event . path ,
49- body :
50- event . node . req . method === "POST"
51- ? await readRawBody ( event )
52- : undefined ,
53- headers : event . node . req . headers ,
54- } ) )
55- )
56- . use (
57- "/post" ,
58- eventHandler ( async ( event ) => ( {
59- body : await readBody ( event ) ,
60- headers : event . node . req . headers ,
61- } ) )
62- )
63- . use (
64- "/binary" ,
65- eventHandler ( ( event ) => {
66- event . node . res . setHeader ( "Content-Type" , "application/octet-stream" ) ;
67- return new Blob ( [ "binary" ] ) ;
68- } )
69- )
70- . use (
24+ const app = new H3 ( { debug : true } )
25+ // .use(async (event) => {
26+ // console.log({
27+ // body: await event.req.clone().text(),
28+ // url: event.url.href,
29+ // headers: event.req.headers,
30+ // });
31+ // })
32+ . all ( "/ok" , ( ) => "ok" )
33+ . all ( "/params" , ( event ) => Object . fromEntries ( event . url . searchParams ) )
34+ . all ( "/url/**" , ( event ) => event . url . pathname + event . url . search )
35+ . all ( "/echo" , async ( event ) => ( {
36+ path : event . url . pathname + event . url . search ,
37+ body : event . req . method === "POST" ? await event . req . text ( ) : undefined ,
38+ headers : Object . fromEntries ( event . req . headers ) ,
39+ } ) )
40+ . all ( "/post" , async ( event ) => ( {
41+ body : event . req . headers
42+ . get ( "content-type" )
43+ ?. includes ( "multipart/form-data" )
44+ ? await event . req . text ( )
45+ : await readBody ( event ) ,
46+ headers : Object . fromEntries ( event . req . headers ) ,
47+ } ) )
48+ . all ( "/binary" , ( event ) => {
49+ event . res . headers . set ( "Content-Type" , "application/octet-stream" ) ;
50+ return new Blob ( [ "binary" ] ) ;
51+ } )
52+ . all (
7153 "/403" ,
72- eventHandler ( ( ) =>
73- createError ( { status : 403 , statusMessage : "Forbidden" } )
74- )
75- )
76- . use (
77- "/408" ,
78- eventHandler ( ( ) => createError ( { status : 408 } ) )
54+ ( ) => new HTTPError ( { status : 403 , statusMessage : "Forbidden" } )
7955 )
80- . use (
56+ . all ( "/408" , ( ) => new HTTPError ( { status : 408 } ) )
57+ . all (
8158 "/204" ,
82- eventHandler ( ( ) => null ) // eslint-disable-line unicorn/no-null
59+ ( ) => null // eslint-disable-line unicorn/no-null
8360 )
84- . use (
85- "/timeout" ,
86- eventHandler ( async ( ) => {
87- await new Promise ( ( resolve ) => {
88- setTimeout ( ( ) => {
89- resolve ( createError ( { status : 408 } ) ) ;
90- } , 1000 * 5 ) ;
91- } ) ;
92- } )
93- ) ;
61+ . all ( "/timeout" , async ( ) => {
62+ await new Promise ( ( resolve ) => {
63+ setTimeout ( ( ) => {
64+ resolve ( new HTTPError ( { status : 408 } ) ) ;
65+ } , 1000 * 5 ) ;
66+ } ) ;
67+ } ) ;
9468
95- listener = await listen ( toNodeListener ( app ) ) ;
69+ listener = await serve ( app , { port : 0 , hostname : "localhost" } ) . ready ( ) ;
9670 } ) ;
9771
9872 afterAll ( ( ) => {
@@ -109,7 +83,7 @@ describe("ofetch", () => {
10983
11084 it ( "custom parseResponse" , async ( ) => {
11185 let called = 0 ;
112- const parser = ( r ) => {
86+ const parser = ( r : any ) => {
11387 called ++ ;
11488 return "C" + r ;
11589 } ;
@@ -128,7 +102,7 @@ describe("ofetch", () => {
128102 ) . to . be . instanceOf ( Blob ) ;
129103 expect (
130104 await $fetch ( getURL ( "params?test=true" ) , { responseType : "text" } )
131- ) . to . equal ( '{"test":"true"}' ) ;
105+ ) . to . equal ( '{\n "test": "true"\n }' ) ;
132106 expect (
133107 await $fetch ( getURL ( "params?test=true" ) , { responseType : "arrayBuffer" } )
134108 ) . to . be . instanceOf ( ArrayBuffer ) ;
@@ -140,7 +114,7 @@ describe("ofetch", () => {
140114
141115 it ( "baseURL" , async ( ) => {
142116 expect ( await $fetch ( "/x?foo=123" , { baseURL : getURL ( "url" ) } ) ) . to . equal (
143- "/x?foo=123"
117+ "/url/ x?foo=123"
144118 ) ;
145119 } ) ;
146120
@@ -162,15 +136,12 @@ describe("ofetch", () => {
162136 let body3 ;
163137 await $fetch ( getURL ( "post" ) , {
164138 method : "POST" ,
165- headers : {
166- "Content-Type" : "application/x-www-form-urlencoded" ,
167- } ,
168139 body : { num : 42 } ,
169140 onResponse ( ctx ) {
170141 body3 = ctx . options . body ;
171142 } ,
172143 } ) ;
173- expect ( body3 ) . equals ( " num=42" ) ;
144+ expect ( JSON . parse ( body3 ! as string ) ) . toMatchObject ( { num : 42 } ) ;
174145
175146 const headerFetches = [
176147 [ [ "X-header" , "1" ] ] ,
@@ -267,25 +238,26 @@ describe("ofetch", () => {
267238
268239 it ( "404" , async ( ) => {
269240 const error = await $fetch ( getURL ( "404" ) ) . catch ( ( error_ ) => error_ ) ;
270- expect ( error . toString ( ) ) . to . contain ( "Cannot find any path matching /404." ) ;
271- expect ( error . data ) . to . deep . eq ( {
272- stack : [ ] ,
273- statusCode : 404 ,
274- statusMessage : "Cannot find any path matching /404." ,
241+ expect ( error . toString ( ) ) . toBe (
242+ `FetchError: [GET] "${ getURL ( "404" ) } ": 404 Not Found`
243+ ) ;
244+ expect ( error . data ) . toMatchObject ( {
245+ status : 404 ,
246+ message : expect . stringContaining ( "Cannot find any route matching" ) ,
275247 } ) ;
276248 expect ( error . response ?. _data ) . to . deep . eq ( error . data ) ;
277249 expect ( error . request ) . to . equal ( getURL ( "404" ) ) ;
278250 } ) ;
279251
280252 it ( "403 with ignoreResponseError" , async ( ) => {
281253 const res = await $fetch ( getURL ( "403" ) , { ignoreResponseError : true } ) ;
282- expect ( res ?. statusCode ) . to . eq ( 403 ) ;
283- expect ( res ?. statusMessage ) . to . eq ( "Forbidden" ) ;
254+ expect ( res ?. status ) . to . eq ( 403 ) ;
255+ expect ( res ?. message ) . to . eq ( "Forbidden" ) ;
284256 } ) ;
285257
286258 it ( "204 no content" , async ( ) => {
287259 const res = await $fetch ( getURL ( "204" ) ) ;
288- expect ( res ) . toBeUndefined ( ) ;
260+ expect ( res ) . toBe ( "" ) ;
289261 } ) ;
290262
291263 it ( "HEAD no content" , async ( ) => {
@@ -328,7 +300,7 @@ describe("ofetch", () => {
328300 expect ( race ) . to . equal ( "fast" ) ;
329301 } ) ;
330302
331- it ( "abort with retry" , ( ) => {
303+ it ( "abort with retry" , async ( ) => {
332304 const controller = new AbortController ( ) ;
333305 async function abortHandle ( ) {
334306 controller . abort ( ) ;
@@ -339,15 +311,15 @@ describe("ofetch", () => {
339311 } ) ;
340312 console . log ( "response" , response ) ;
341313 }
342- expect ( abortHandle ( ) ) . rejects . toThrow ( / a b o r t e d / ) ;
314+ await expect ( abortHandle ( ) ) . rejects . toThrow ( / a b o r t e d / ) ;
343315 } ) ;
344316
345317 it ( "passing request obj should return request obj in error" , async ( ) => {
346318 const error = await $fetch ( getURL ( "/403" ) , { method : "post" } ) . catch (
347319 ( error ) => error
348320 ) ;
349321 expect ( error . toString ( ) ) . toBe (
350- ' FetchError: [POST] "http://localhost:3000/ 403": 403 Forbidden'
322+ ` FetchError: [POST] "${ getURL ( " 403") } " : 403 Forbidden`
351323 ) ;
352324 expect ( error . request ) . to . equal ( getURL ( "403" ) ) ;
353325 expect ( error . options . method ) . to . equal ( "POST" ) ;
@@ -411,8 +383,8 @@ describe("ofetch", () => {
411383 } ) ;
412384
413385 const parseParams = ( str : string ) =>
414- Object . fromEntries ( new URLSearchParams ( str ) . entries ( ) ) ;
415- expect ( parseParams ( path ) ) . toMatchObject ( parseParams ( "?b=2&c=3&a=1" ) ) ;
386+ Object . fromEntries ( new URL ( str , "http://_" ) . searchParams . entries ( ) ) ;
387+ expect ( parseParams ( path ) ) . toMatchObject ( { a : "1" , b : "2" , c : "3" } ) ;
416388 } ) ;
417389
418390 it ( "uses request headers" , async ( ) => {
0 commit comments