@@ -673,293 +673,4 @@ describe('API Routes', () => {
673673 } ) ;
674674 } ) ;
675675 } ) ;
676-
677- describe ( 'GET /v1/static/*' , ( ) => {
678- beforeEach ( ( ) => {
679- // Reset all mocks before each test
680- mockFileExists . mockReset ( ) ;
681- mockGetMetadata . mockReset ( ) ;
682- mockCreateReadStream . mockReset ( ) ;
683- mockFile . mockClear ( ) ;
684- mockBucket . mockClear ( ) ;
685- } ) ;
686-
687- describe ( 'Valid file requests' , ( ) => {
688- it ( 'should return file content for valid path' , async ( ) => {
689- const fileContent = JSON . stringify ( { data : 'test' } ) ;
690- const readable = Readable . from ( [ fileContent ] ) ;
691-
692- mockFileExists . mockResolvedValue ( [ true ] ) ;
693- mockGetMetadata . mockResolvedValue ( [ {
694- contentType : 'application/json' ,
695- etag : '"abc123"' ,
696- size : fileContent . length
697- } ] ) ;
698- mockCreateReadStream . mockReturnValue ( readable ) ;
699-
700- const res = await request ( app )
701- . get ( '/v1/static/reports/2024/data.json' )
702- . expect ( 200 ) ;
703-
704- expect ( res . headers [ 'content-type' ] ) . toContain ( 'application/json' ) ;
705- expect ( res . headers [ 'cache-control' ] ) . toContain ( 'public' ) ;
706- expect ( res . headers [ 'access-control-allow-origin' ] ) . toEqual ( '*' ) ;
707- } ) ;
708-
709- it ( 'should infer MIME type from file extension when not in metadata' , async ( ) => {
710- const fileContent = '{"test": true}' ;
711- const readable = Readable . from ( [ fileContent ] ) ;
712-
713- mockFileExists . mockResolvedValue ( [ true ] ) ;
714- mockGetMetadata . mockResolvedValue ( [ {
715- etag : '"abc123"' ,
716- size : fileContent . length
717- } ] ) ;
718- mockCreateReadStream . mockReturnValue ( readable ) ;
719-
720- const res = await request ( app )
721- . get ( '/v1/static/reports/data.json' )
722- . expect ( 200 ) ;
723-
724- expect ( res . headers [ 'content-type' ] ) . toContain ( 'application/json' ) ;
725- } ) ;
726-
727- it ( 'should handle CORS preflight requests' , async ( ) => {
728- const res = await request ( app )
729- . options ( '/v1/static/reports/data.json' )
730- . set ( 'Origin' , 'http://example.com' )
731- . set ( 'Access-Control-Request-Method' , 'GET' )
732- . set ( 'Access-Control-Request-Headers' , 'Content-Type' ) ;
733-
734- expect ( res . statusCode ) . toEqual ( 204 ) ;
735- expect ( res . headers [ 'access-control-allow-origin' ] ) . toEqual ( '*' ) ;
736- } ) ;
737- } ) ;
738-
739- describe ( 'Invalid file paths (directory traversal attempts)' , ( ) => {
740- it ( 'should reject paths containing double dot sequences' , async ( ) => {
741- // Test with '..' embedded in the path that won't be normalized away
742- const res = await request ( app )
743- . get ( '/v1/static/reports/..hidden/passwd' )
744-
745- expect ( res . body ) . toHaveProperty ( 'error' , 'Invalid file path' ) ;
746- } ) ;
747-
748- it ( 'should reject paths with double slashes' , async ( ) => {
749- const res = await request ( app )
750- . get ( '/v1/static/reports//data.json' )
751- . expect ( 400 ) ;
752-
753- expect ( res . body ) . toHaveProperty ( 'error' , 'Invalid file path' ) ;
754- } ) ;
755-
756- it ( 'should reject paths with encoded double dots' , async ( ) => {
757- // URL-encoded '..' = %2e%2e
758- mockFileExists . mockResolvedValue ( [ false ] ) ; // Will be checked after validation
759-
760- const res = await request ( app )
761- . get ( '/v1/static/reports/%2e%2e/secret/passwd' ) ;
762-
763- // Should either be rejected as invalid or not found
764- expect ( [ 400 , 404 ] ) . toContain ( res . statusCode ) ;
765- } ) ;
766- } ) ;
767-
768- describe ( 'Non-existent files (404 handling)' , ( ) => {
769- it ( 'should return 404 for non-existent files' , async ( ) => {
770- mockFileExists . mockResolvedValue ( [ false ] ) ;
771-
772- const res = await request ( app )
773- . get ( '/v1/static/reports/nonexistent.json' )
774- . expect ( 404 ) ;
775-
776- expect ( res . body ) . toHaveProperty ( 'error' , 'File not found' ) ;
777- } ) ;
778-
779- it ( 'should return 400 for empty file path' , async ( ) => {
780- const res = await request ( app )
781- . get ( '/v1/static/' )
782- . expect ( 400 ) ;
783-
784- expect ( res . body ) . toHaveProperty ( 'error' , 'File path required' ) ;
785- } ) ;
786- } ) ;
787-
788- describe ( 'Conditional requests (ETag/If-None-Match)' , ( ) => {
789- it ( 'should return 304 when ETag matches If-None-Match header' , async ( ) => {
790- const etag = '"abc123"' ;
791-
792- mockFileExists . mockResolvedValue ( [ true ] ) ;
793- mockGetMetadata . mockResolvedValue ( [ {
794- contentType : 'application/json' ,
795- etag : etag ,
796- size : 100
797- } ] ) ;
798-
799- const res = await request ( app )
800- . get ( '/v1/static/reports/data.json' )
801- . set ( 'If-None-Match' , etag )
802- . expect ( 304 ) ;
803-
804- // 304 responses have no body
805- expect ( res . text ) . toEqual ( '' ) ;
806- } ) ;
807-
808- it ( 'should return 200 with content when ETag does not match' , async ( ) => {
809- const fileContent = JSON . stringify ( { data : 'test' } ) ;
810- const readable = Readable . from ( [ fileContent ] ) ;
811-
812- mockFileExists . mockResolvedValue ( [ true ] ) ;
813- mockGetMetadata . mockResolvedValue ( [ {
814- contentType : 'application/json' ,
815- etag : '"abc123"' ,
816- size : fileContent . length
817- } ] ) ;
818- mockCreateReadStream . mockReturnValue ( readable ) ;
819-
820- const res = await request ( app )
821- . get ( '/v1/static/reports/data.json' )
822- . set ( 'If-None-Match' , '"different-etag"' )
823- . expect ( 200 ) ;
824-
825- expect ( res . headers [ 'etag' ] ) . toEqual ( '"abc123"' ) ;
826- } ) ;
827-
828- it ( 'should include ETag in response headers' , async ( ) => {
829- const fileContent = JSON . stringify ( { data : 'test' } ) ;
830- const readable = Readable . from ( [ fileContent ] ) ;
831-
832- mockFileExists . mockResolvedValue ( [ true ] ) ;
833- mockGetMetadata . mockResolvedValue ( [ {
834- contentType : 'application/json' ,
835- etag : '"abc123"' ,
836- size : fileContent . length
837- } ] ) ;
838- mockCreateReadStream . mockReturnValue ( readable ) ;
839-
840- const res = await request ( app )
841- . get ( '/v1/static/reports/data.json' )
842- . expect ( 200 ) ;
843-
844- expect ( res . headers ) . toHaveProperty ( 'etag' , '"abc123"' ) ;
845- } ) ;
846- } ) ;
847-
848- describe ( 'Error scenarios (GCS failures)' , ( ) => {
849- it ( 'should handle GCS exists() failure' , async ( ) => {
850- mockFileExists . mockRejectedValue ( new Error ( 'GCS connection failed' ) ) ;
851-
852- const res = await request ( app )
853- . get ( '/v1/static/reports/data.json' )
854- . expect ( 500 ) ;
855-
856- expect ( res . body ) . toHaveProperty ( 'error' , 'Server failed to respond' ) ;
857- expect ( res . body ) . toHaveProperty ( 'details' ) ;
858- } ) ;
859-
860- it ( 'should handle GCS getMetadata() failure' , async ( ) => {
861- mockFileExists . mockResolvedValue ( [ true ] ) ;
862- mockGetMetadata . mockRejectedValue ( new Error ( 'Metadata retrieval failed' ) ) ;
863-
864- const res = await request ( app )
865- . get ( '/v1/static/reports/data.json' )
866- . expect ( 500 ) ;
867-
868- expect ( res . body ) . toHaveProperty ( 'error' , 'Server failed to respond' ) ;
869- expect ( res . body ) . toHaveProperty ( 'details' ) ;
870- } ) ;
871-
872- it ( 'should handle stream errors during file read' , async ( ) => {
873- mockFileExists . mockResolvedValue ( [ true ] ) ;
874- mockGetMetadata . mockResolvedValue ( [ {
875- contentType : 'application/json' ,
876- etag : '"abc123"' ,
877- size : 100
878- } ] ) ;
879-
880- // Create a stream that emits an error after a delay
881- const errorStream = new Readable ( {
882- read ( ) {
883- // Emit error asynchronously
884- process . nextTick ( ( ) => {
885- this . destroy ( new Error ( 'Stream read error' ) ) ;
886- } ) ;
887- }
888- } ) ;
889- mockCreateReadStream . mockReturnValue ( errorStream ) ;
890-
891- // Use try-catch since stream errors may cause connection issues
892- try {
893- const res = await request ( app )
894- . get ( '/v1/static/reports/data.json' )
895- . timeout ( 1000 ) ;
896-
897- // If we get a response, verify error handling
898- expect ( [ 200 , 500 ] ) . toContain ( res . statusCode ) ;
899- } catch ( err ) {
900- // Connection aborted due to stream error is expected behavior
901- expect ( err . message ) . toMatch ( / a b o r t e d | E C O N N R E S E T | s o c k e t h a n g u p / i) ;
902- }
903- } ) ;
904- } ) ;
905-
906- describe ( 'MIME type detection' , ( ) => {
907- it ( 'should detect application/json for .json files' , async ( ) => {
908- const content = '{"test":true}' ;
909- const readable = Readable . from ( [ content ] ) ;
910-
911- mockFileExists . mockResolvedValue ( [ true ] ) ;
912- mockGetMetadata . mockResolvedValue ( [ { size : content . length } ] ) ;
913- mockCreateReadStream . mockReturnValue ( readable ) ;
914-
915- const res = await request ( app )
916- . get ( '/v1/static/reports/data.json' )
917- . expect ( 200 ) ;
918-
919- expect ( res . headers [ 'content-type' ] ) . toContain ( 'application/json' ) ;
920- } ) ;
921-
922- it ( 'should detect image/png for .png files' , async ( ) => {
923- const content = Buffer . from ( [ 0x89 , 0x50 , 0x4E , 0x47 ] ) ; // PNG magic bytes
924- const readable = Readable . from ( [ content ] ) ;
925-
926- mockFileExists . mockResolvedValue ( [ true ] ) ;
927- mockGetMetadata . mockResolvedValue ( [ { size : content . length } ] ) ;
928- mockCreateReadStream . mockReturnValue ( readable ) ;
929-
930- const res = await request ( app )
931- . get ( '/v1/static/reports/chart.png' )
932- . buffer ( true )
933- . parse ( ( res , callback ) => {
934- const chunks = [ ] ;
935- res . on ( 'data' , chunk => chunks . push ( chunk ) ) ;
936- res . on ( 'end' , ( ) => callback ( null , Buffer . concat ( chunks ) ) ) ;
937- } ) ;
938-
939- expect ( res . statusCode ) . toEqual ( 200 ) ;
940- expect ( res . headers [ 'content-type' ] ) . toContain ( 'image/png' ) ;
941- } ) ;
942-
943- it ( 'should use application/octet-stream for unknown extensions' , async ( ) => {
944- const content = Buffer . from ( [ 0x00 , 0x01 , 0x02 ] ) ;
945- const readable = Readable . from ( [ content ] ) ;
946-
947- mockFileExists . mockResolvedValue ( [ true ] ) ;
948- mockGetMetadata . mockResolvedValue ( [ { size : content . length } ] ) ;
949- mockCreateReadStream . mockReturnValue ( readable ) ;
950-
951- const res = await request ( app )
952- . get ( '/v1/static/reports/file.xyz' )
953- . buffer ( true )
954- . parse ( ( res , callback ) => {
955- const chunks = [ ] ;
956- res . on ( 'data' , chunk => chunks . push ( chunk ) ) ;
957- res . on ( 'end' , ( ) => callback ( null , Buffer . concat ( chunks ) ) ) ;
958- } ) ;
959-
960- expect ( res . statusCode ) . toEqual ( 200 ) ;
961- expect ( res . headers [ 'content-type' ] ) . toContain ( 'application/octet-stream' ) ;
962- } ) ;
963- } ) ;
964- } ) ;
965676} ) ;
0 commit comments