@@ -2311,6 +2311,208 @@ describe('Parse.File testing', () => {
23112311 expect ( b . url ) . toBeDefined ( ) ;
23122312 } ) ;
23132313
2314+ it ( 'saves file with directory via streaming upload (header)' , async ( ) => {
2315+ const headers = {
2316+ 'Content-Type' : 'text/plain' ,
2317+ 'X-Parse-Application-Id' : 'test' ,
2318+ 'X-Parse-Master-Key' : 'test' ,
2319+ 'X-Parse-Upload-Mode' : 'stream' ,
2320+ 'X-Parse-File-Directory' : 'stream-dir-test' ,
2321+ } ;
2322+ const response = await request ( {
2323+ method : 'POST' ,
2324+ headers,
2325+ url : 'http://localhost:8378/1/files/stream-header.txt' ,
2326+ body : 'stream directory header content' ,
2327+ } ) ;
2328+ const b = response . data ;
2329+ expect ( b . name ) . toMatch ( / ^ s t r e a m - d i r - t e s t \/ .* _ s t r e a m - h e a d e r .t x t $ / ) ;
2330+ expect ( b . url ) . toBeDefined ( ) ;
2331+ } ) ;
2332+
2333+ it ( 'rejects directory header without master key for streaming upload' , async ( ) => {
2334+ const headers = {
2335+ 'Content-Type' : 'text/plain' ,
2336+ 'X-Parse-Application-Id' : 'test' ,
2337+ 'X-Parse-REST-API-Key' : 'rest' ,
2338+ 'X-Parse-Upload-Mode' : 'stream' ,
2339+ 'X-Parse-File-Directory' : 'no-master' ,
2340+ } ;
2341+ try {
2342+ await request ( {
2343+ method : 'POST' ,
2344+ headers,
2345+ url : 'http://localhost:8378/1/files/stream-header.txt' ,
2346+ body : 'should fail' ,
2347+ } ) ;
2348+ fail ( 'should have thrown' ) ;
2349+ } catch ( error ) {
2350+ expect ( error . data . code ) . toEqual ( Parse . Error . OPERATION_FORBIDDEN ) ;
2351+ }
2352+ } ) ;
2353+
2354+ it ( 'validates directory header for streaming upload' , async ( ) => {
2355+ const headers = {
2356+ 'Content-Type' : 'text/plain' ,
2357+ 'X-Parse-Application-Id' : 'test' ,
2358+ 'X-Parse-Master-Key' : 'test' ,
2359+ 'X-Parse-Upload-Mode' : 'stream' ,
2360+ 'X-Parse-File-Directory' : '../etc' ,
2361+ } ;
2362+ try {
2363+ await request ( {
2364+ method : 'POST' ,
2365+ headers,
2366+ url : 'http://localhost:8378/1/files/stream-header.txt' ,
2367+ body : 'should fail' ,
2368+ } ) ;
2369+ fail ( 'should have thrown' ) ;
2370+ } catch ( error ) {
2371+ expect ( error . data . code ) . toEqual ( Parse . Error . INVALID_FILE_NAME ) ;
2372+ }
2373+ } ) ;
2374+
2375+ it ( 'saves file with metadata and tags via streaming upload headers' , async ( ) => {
2376+ spyOn ( FilesController . prototype , 'createFile' ) . and . callThrough ( ) ;
2377+ const headers = {
2378+ 'Content-Type' : 'text/plain' ,
2379+ 'X-Parse-Application-Id' : 'test' ,
2380+ 'X-Parse-Master-Key' : 'test' ,
2381+ 'X-Parse-Upload-Mode' : 'stream' ,
2382+ 'X-Parse-File-Metadata' : JSON . stringify ( { key1 : 'value1' } ) ,
2383+ 'X-Parse-File-Tags' : JSON . stringify ( { tag1 : 'tagValue1' } ) ,
2384+ } ;
2385+ const response = await request ( {
2386+ method : 'POST' ,
2387+ headers,
2388+ url : 'http://localhost:8378/1/files/stream-meta.txt' ,
2389+ body : 'stream with metadata content' ,
2390+ } ) ;
2391+ const b = response . data ;
2392+ expect ( b . name ) . toMatch ( / _ s t r e a m - m e t a .t x t $ / ) ;
2393+ expect ( b . url ) . toBeDefined ( ) ;
2394+ const options = FilesController . prototype . createFile . calls . argsFor ( 0 ) [ 4 ] ;
2395+ expect ( options . metadata ) . toEqual ( { key1 : 'value1' } ) ;
2396+ expect ( options . tags ) . toEqual ( { tag1 : 'tagValue1' } ) ;
2397+ } ) ;
2398+
2399+ it ( 'saves file with directory, metadata, and tags via streaming upload headers' , async ( ) => {
2400+ spyOn ( FilesController . prototype , 'createFile' ) . and . callThrough ( ) ;
2401+ const headers = {
2402+ 'Content-Type' : 'text/plain' ,
2403+ 'X-Parse-Application-Id' : 'test' ,
2404+ 'X-Parse-Master-Key' : 'test' ,
2405+ 'X-Parse-Upload-Mode' : 'stream' ,
2406+ 'X-Parse-File-Directory' : 'uploads' ,
2407+ 'X-Parse-File-Metadata' : JSON . stringify ( { author : 'test' } ) ,
2408+ 'X-Parse-File-Tags' : JSON . stringify ( { env : 'test' } ) ,
2409+ } ;
2410+ const response = await request ( {
2411+ method : 'POST' ,
2412+ headers,
2413+ url : 'http://localhost:8378/1/files/stream-all.txt' ,
2414+ body : 'stream with all file data' ,
2415+ } ) ;
2416+ const b = response . data ;
2417+ expect ( b . name ) . toMatch ( / ^ u p l o a d s \/ .* _ s t r e a m - a l l .t x t $ / ) ;
2418+ expect ( b . url ) . toBeDefined ( ) ;
2419+ const options = FilesController . prototype . createFile . calls . argsFor ( 0 ) [ 4 ] ;
2420+ expect ( options . metadata ) . toEqual ( { author : 'test' } ) ;
2421+ expect ( options . tags ) . toEqual ( { env : 'test' } ) ;
2422+ } ) ;
2423+
2424+ it ( 'rejects invalid JSON in metadata header' , async ( ) => {
2425+ const headers = {
2426+ 'Content-Type' : 'text/plain' ,
2427+ 'X-Parse-Application-Id' : 'test' ,
2428+ 'X-Parse-Master-Key' : 'test' ,
2429+ 'X-Parse-Upload-Mode' : 'stream' ,
2430+ 'X-Parse-File-Metadata' : 'not-json' ,
2431+ } ;
2432+ try {
2433+ await request ( {
2434+ method : 'POST' ,
2435+ headers,
2436+ url : 'http://localhost:8378/1/files/stream-bad.txt' ,
2437+ body : 'should fail' ,
2438+ } ) ;
2439+ fail ( 'should have thrown' ) ;
2440+ } catch ( error ) {
2441+ expect ( error . data . code ) . toEqual ( Parse . Error . INVALID_JSON ) ;
2442+ }
2443+ } ) ;
2444+
2445+ it ( 'rejects invalid JSON in tags header' , async ( ) => {
2446+ const headers = {
2447+ 'Content-Type' : 'text/plain' ,
2448+ 'X-Parse-Application-Id' : 'test' ,
2449+ 'X-Parse-Master-Key' : 'test' ,
2450+ 'X-Parse-Upload-Mode' : 'stream' ,
2451+ 'X-Parse-File-Tags' : '{bad' ,
2452+ } ;
2453+ try {
2454+ await request ( {
2455+ method : 'POST' ,
2456+ headers,
2457+ url : 'http://localhost:8378/1/files/stream-bad.txt' ,
2458+ body : 'should fail' ,
2459+ } ) ;
2460+ fail ( 'should have thrown' ) ;
2461+ } catch ( error ) {
2462+ expect ( error . data . code ) . toEqual ( Parse . Error . INVALID_JSON ) ;
2463+ }
2464+ } ) ;
2465+
2466+ it ( 'rejects non-object metadata header' , async ( ) => {
2467+ const invalidValues = [ '"a string"' , '[1,2]' , 'null' , '42' , 'true' ] ;
2468+ for ( const value of invalidValues ) {
2469+ const headers = {
2470+ 'Content-Type' : 'text/plain' ,
2471+ 'X-Parse-Application-Id' : 'test' ,
2472+ 'X-Parse-Master-Key' : 'test' ,
2473+ 'X-Parse-Upload-Mode' : 'stream' ,
2474+ 'X-Parse-File-Metadata' : value ,
2475+ } ;
2476+ try {
2477+ await request ( {
2478+ method : 'POST' ,
2479+ headers,
2480+ url : 'http://localhost:8378/1/files/stream-bad.txt' ,
2481+ body : 'should fail' ,
2482+ } ) ;
2483+ fail ( `should have thrown for metadata: ${ value } ` ) ;
2484+ } catch ( error ) {
2485+ expect ( error . data . code ) . toEqual ( Parse . Error . INVALID_JSON ) ;
2486+ expect ( error . data . error ) . toBe ( 'Invalid JSON in X-Parse-File-Metadata header.' ) ;
2487+ }
2488+ }
2489+ } ) ;
2490+
2491+ it ( 'rejects non-object tags header' , async ( ) => {
2492+ const invalidValues = [ '"a string"' , '[1,2]' , 'null' , '42' , 'true' ] ;
2493+ for ( const value of invalidValues ) {
2494+ const headers = {
2495+ 'Content-Type' : 'text/plain' ,
2496+ 'X-Parse-Application-Id' : 'test' ,
2497+ 'X-Parse-Master-Key' : 'test' ,
2498+ 'X-Parse-Upload-Mode' : 'stream' ,
2499+ 'X-Parse-File-Tags' : value ,
2500+ } ;
2501+ try {
2502+ await request ( {
2503+ method : 'POST' ,
2504+ headers,
2505+ url : 'http://localhost:8378/1/files/stream-bad.txt' ,
2506+ body : 'should fail' ,
2507+ } ) ;
2508+ fail ( `should have thrown for tags: ${ value } ` ) ;
2509+ } catch ( error ) {
2510+ expect ( error . data . code ) . toEqual ( Parse . Error . INVALID_JSON ) ;
2511+ expect ( error . data . error ) . toBe ( 'Invalid JSON in X-Parse-File-Tags header.' ) ;
2512+ }
2513+ }
2514+ } ) ;
2515+
23142516 it ( 'validates directory - rejects trailing slash' , async ( ) => {
23152517 const file = new Parse . File ( 'hello.txt' , data , 'text/plain' ) ;
23162518 file . setDirectory ( 'trailing/' ) ;
0 commit comments