@@ -1090,6 +1090,110 @@ Deno.test("can use per-domain rate limiting with auto-update from headers", asyn
10901090 assertEquals ( slowApiOptions . maxRequests , 5 ) ; // Updated from headers
10911091} ) ;
10921092
1093+ Deno . test ( "can post FormData multipart" , async ( ) => {
1094+ const controller = new AbortController ( ) ;
1095+ const port = 48081 ;
1096+
1097+ const server = Deno . serve (
1098+ { port, signal : controller . signal } ,
1099+ async ( req ) => {
1100+ if ( req . method === "POST" && new URL ( req . url ) . pathname === "/upload" ) {
1101+ try {
1102+ const contentType = req . headers . get ( "content-type" ) ?? "" ;
1103+ const isMultipart = contentType . startsWith ( "multipart/form-data;" ) ;
1104+ const form = await req . formData ( ) ;
1105+ const responseJson : Record < string , unknown > = { isMultipart } ;
1106+ for ( const key of form . keys ( ) ) {
1107+ const value = form . get ( key ) ;
1108+ if ( value instanceof File ) {
1109+ const arrayBuf = await value . arrayBuffer ( ) ;
1110+ const bytes = new Uint8Array ( arrayBuf ) ;
1111+ const base64 = btoa ( String . fromCharCode ( ...bytes ) ) ;
1112+ responseJson [ key ] = {
1113+ name : value . name ,
1114+ size : value . size ,
1115+ type : value . type ,
1116+ base64,
1117+ } ;
1118+ } else {
1119+ responseJson [ key ] = value ;
1120+ }
1121+ }
1122+
1123+ return new Response ( JSON . stringify ( responseJson ) , {
1124+ status : 200 ,
1125+ headers : { "Content-Type" : "application/json" } ,
1126+ } ) ;
1127+ } catch ( err ) {
1128+ return new Response ( JSON . stringify ( { error : String ( err ) } ) , {
1129+ status : 500 ,
1130+ headers : { "Content-Type" : "application/json" } ,
1131+ } ) ;
1132+ }
1133+ }
1134+ return new Response ( null , { status : 404 } ) ;
1135+ } ,
1136+ ) ;
1137+
1138+ const client = new FetchClient ( ) ;
1139+ const fd = new FormData ( ) ;
1140+ fd . append ( "field1" , "value1" ) ;
1141+ fd . append ( "count" , "42" ) ;
1142+ // Binary content (PNG header bytes) to ensure we don't corrupt binary uploads
1143+ const binaryBytes = new Uint8Array ( [ 0x89 , 0x50 , 0x4E , 0x47 ] ) ;
1144+ fd . append (
1145+ "file" ,
1146+ new File ( [ "Hello Multipart" ] , "greeting.txt" , { type : "text/plain" } ) ,
1147+ ) ;
1148+ fd . append (
1149+ "binary" ,
1150+ new File ( [ binaryBytes ] , "image.png" , { type : "application/octet-stream" } ) ,
1151+ ) ;
1152+
1153+ const res = await client . postJSON < Record < string , unknown > > (
1154+ `http://localhost:${ port } /upload` ,
1155+ fd ,
1156+ {
1157+ expectedStatusCodes : [ 200 ] ,
1158+ } ,
1159+ ) ;
1160+
1161+ controller . abort ( ) ;
1162+ await server . finished ;
1163+
1164+ assertEquals ( res . status , 200 ) ;
1165+ assert ( res . ok ) ;
1166+ assert ( res . data ) ;
1167+ assertEquals ( res . data . field1 , "value1" ) ;
1168+ assertEquals ( res . data . count , "42" ) ;
1169+ assert ( res . data . isMultipart ) ;
1170+ const fileInfo = res . data . file as {
1171+ name : string ;
1172+ size : number ;
1173+ type : string ;
1174+ base64 : string ;
1175+ } ;
1176+ assertEquals ( fileInfo . name , "greeting.txt" ) ;
1177+ assertEquals ( fileInfo . type , "text/plain" ) ;
1178+ // "Hello Multipart" length check
1179+ assertEquals ( fileInfo . size , "Hello Multipart" . length ) ;
1180+ const binaryInfo = res . data . binary as {
1181+ name : string ;
1182+ size : number ;
1183+ type : string ;
1184+ base64 : string ;
1185+ } ;
1186+ assertEquals ( binaryInfo . name , "image.png" ) ;
1187+ assertEquals ( binaryInfo . type , "application/octet-stream" ) ;
1188+ assertEquals ( binaryInfo . size , 4 ) ;
1189+ // 0x89 50 4E 47 -> base64 iVBORw== (first 4 bytes of PNG yield iVBORw0KGgo but with only 4 bytes shorter)
1190+ // Let's compute expected base64 for [0x89,0x50,0x4E,0x47]
1191+ const expectedBinaryBase64 = btoa (
1192+ String . fromCharCode ( 0x89 , 0x50 , 0x4E , 0x47 ) ,
1193+ ) ;
1194+ assertEquals ( binaryInfo . base64 , expectedBinaryBase64 ) ;
1195+ } ) ;
1196+
10931197function delay ( time : number ) : Promise < void > {
10941198 return new Promise ( ( resolve ) => setTimeout ( resolve , time ) ) ;
10951199}
0 commit comments