@@ -894,6 +894,10 @@ describe('S3Adapter tests', () => {
894894 { mount : 'http://example.com' , applicationId : 'test123' }
895895 ) ;
896896
897+ expect ( s3 . getFileLocation ) . toHaveBeenCalledWith (
898+ jasmine . objectContaining ( { mount : 'http://example.com' , applicationId : 'test123' } ) ,
899+ 'file.txt'
900+ ) ;
897901 expect ( result ) . toEqual ( {
898902 location : jasmine . any ( String ) ,
899903 name : 'file.txt' ,
@@ -929,13 +933,14 @@ describe('S3Adapter tests', () => {
929933 s3ClientMock . send . and . returnValue ( Promise . resolve ( { } ) ) ;
930934 s3 . _s3Client = s3ClientMock ;
931935
932- await s3 . createFile ( 'file.txt' , 'hello world' , 'text/utf8' , { } ) ;
936+ const out = await s3 . createFile ( 'file.txt' , 'hello world' , 'text/utf8' , { } ) ;
933937
934938 expect ( s3ClientMock . send ) . toHaveBeenCalledTimes ( 2 ) ;
935939 const commands = s3ClientMock . send . calls . all ( ) ;
936940 const commandArg = commands [ 1 ] . args [ 0 ] ;
937941 expect ( commandArg ) . toBeInstanceOf ( PutObjectCommand ) ;
938942 expect ( commandArg . input . Key ) . toBe ( 'async-file.txt' ) ;
943+ expect ( out . name ) . toBe ( 'async-file.txt' ) ;
939944 } ) ;
940945
941946 it ( 'should handle generateKey that returns a Promise' , async ( ) => {
@@ -949,13 +954,14 @@ describe('S3Adapter tests', () => {
949954 s3ClientMock . send . and . returnValue ( Promise . resolve ( { } ) ) ;
950955 s3 . _s3Client = s3ClientMock ;
951956
952- await s3 . createFile ( 'file.txt' , 'hello world' , 'text/utf8' , { } ) ;
957+ const out = await s3 . createFile ( 'file.txt' , 'hello world' , 'text/utf8' , { } ) ;
953958
954959 expect ( s3ClientMock . send ) . toHaveBeenCalledTimes ( 2 ) ;
955960 const commands = s3ClientMock . send . calls . all ( ) ;
956961 const commandArg = commands [ 1 ] . args [ 0 ] ;
957962 expect ( commandArg ) . toBeInstanceOf ( PutObjectCommand ) ;
958963 expect ( commandArg . input . Key ) . toBe ( 'promise-file.txt' ) ;
964+ expect ( out . name ) . toBe ( 'promise-file.txt' ) ;
959965 } ) ;
960966
961967 it ( 'should validate generateKey returns a non-empty string' , async ( ) => {
@@ -984,6 +990,19 @@ describe('S3Adapter tests', () => {
984990 ) . toBeRejectedWithError ( 'generateKey must return a non-empty string' ) ;
985991 } ) ;
986992
993+ it ( 'should reject when generateKey returns only whitespace' , async ( ) => {
994+ const options = {
995+ bucket : 'bucket-1' ,
996+ generateKey : ( ) => ' '
997+ } ;
998+ const s3 = new S3Adapter ( options ) ;
999+ s3 . _s3Client = s3ClientMock ;
1000+
1001+ await expectAsync (
1002+ s3 . createFile ( 'file.txt' , 'hello world' , 'text/utf8' , { } )
1003+ ) . toBeRejectedWithError ( 'generateKey must return a non-empty string' ) ;
1004+ } ) ;
1005+
9871006 it ( 'should validate async generateKey returns a string' , async ( ) => {
9881007 const options = {
9891008 bucket : 'bucket-1' ,
@@ -998,6 +1017,88 @@ describe('S3Adapter tests', () => {
9981017 } ) ;
9991018 } ) ;
10001019
1020+ describe ( 'URL construction with custom endpoints' , ( ) => {
1021+ let s3ClientMock ;
1022+
1023+ beforeEach ( ( ) => {
1024+ s3ClientMock = jasmine . createSpyObj ( 'S3Client' , [ 'send' ] ) ;
1025+ s3ClientMock . send . and . returnValue ( Promise . resolve ( { } ) ) ;
1026+ } ) ;
1027+
1028+ it ( 'should handle endpoint with path and query correctly' , async ( ) => {
1029+ const s3 = new S3Adapter ( {
1030+ bucket : 'test-bucket' ,
1031+ s3overrides : {
1032+ endpoint : 'https://example.com:8080/path?foo=bar'
1033+ }
1034+ } ) ;
1035+ s3 . _s3Client = s3ClientMock ;
1036+
1037+ const result = await s3 . createFile ( 'test.txt' , 'hello world' , 'text/utf8' ) ;
1038+
1039+ // Should construct proper URL without breaking query parameters
1040+ expect ( result . location ) . toBe ( 'https://example.com:8080/path/test-bucket/test.txt' ) ;
1041+ } ) ;
1042+
1043+ it ( 'should handle path-style endpoint without bucket in hostname' , async ( ) => {
1044+ const s3 = new S3Adapter ( {
1045+ bucket : 'test-bucket' ,
1046+ s3overrides : {
1047+ endpoint : 'https://minio.example.com'
1048+ }
1049+ } ) ;
1050+ s3 . _s3Client = s3ClientMock ;
1051+
1052+ const result = await s3 . createFile ( 'test.txt' , 'hello world' , 'text/utf8' ) ;
1053+
1054+ // Should add bucket to path for path-style
1055+ expect ( result . location ) . toBe ( 'https://minio.example.com/test-bucket/test.txt' ) ;
1056+ } ) ;
1057+
1058+ it ( 'should handle virtual-hosted-style endpoint with bucket in hostname' , async ( ) => {
1059+ const s3 = new S3Adapter ( {
1060+ bucket : 'test-bucket' ,
1061+ s3overrides : {
1062+ endpoint : 'https://test-bucket.s3.example.com'
1063+ }
1064+ } ) ;
1065+ s3 . _s3Client = s3ClientMock ;
1066+
1067+ const result = await s3 . createFile ( 'test.txt' , 'hello world' , 'text/utf8' ) ;
1068+
1069+ // Should not duplicate bucket when it's already in hostname
1070+ expect ( result . location ) . toBe ( 'https://test-bucket.s3.example.com/test.txt' ) ;
1071+ } ) ;
1072+
1073+ it ( 'should fallback for malformed endpoint' , async ( ) => {
1074+ const s3 = new S3Adapter ( {
1075+ bucket : 'test-bucket' ,
1076+ s3overrides : {
1077+ endpoint : 'not-a-valid-url'
1078+ }
1079+ } ) ;
1080+ s3 . _s3Client = s3ClientMock ;
1081+
1082+ const result = await s3 . createFile ( 'test.txt' , 'hello world' , 'text/utf8' ) ;
1083+
1084+ // Should fallback to safe construction
1085+ expect ( result . location ) . toBe ( 'https://not-a-valid-url/test-bucket/test.txt' ) ;
1086+ } ) ;
1087+
1088+ it ( 'should use default AWS endpoint when no custom endpoint' , async ( ) => {
1089+ const s3 = new S3Adapter ( {
1090+ bucket : 'test-bucket' ,
1091+ region : 'us-west-2'
1092+ } ) ;
1093+ s3 . _s3Client = s3ClientMock ;
1094+
1095+ const result = await s3 . createFile ( 'test.txt' , 'hello world' , 'text/utf8' ) ;
1096+
1097+ // Should use standard AWS S3 URL
1098+ expect ( result . location ) . toBe ( 'https://test-bucket.s3.us-west-2.amazonaws.com/test.txt' ) ;
1099+ } ) ;
1100+ } ) ;
1101+
10011102 describe ( 'handleFileStream' , ( ) => {
10021103 const filename = 'file.txt' ;
10031104 let s3 ;
0 commit comments