@@ -107,6 +107,25 @@ describe('StorageService', () => {
107107 ` ) ;
108108 }
109109
110+ function createAppWithEnv ( appEnv : Env , uid ?: number ) {
111+ const serviceApp = new Hono < { Bindings : Env ; Variables : Variables } > ( ) ;
112+ serviceApp . use ( createMiddleware < { Bindings : Env ; Variables : Variables } > ( async ( c , next ) => {
113+ c . set ( 'db' , db ) ;
114+ c . set ( 'cache' , new TestCacheImpl ( ) ) ;
115+ c . set ( 'serverConfig' , new TestCacheImpl ( ) ) ;
116+ c . set ( 'clientConfig' , new TestCacheImpl ( ) ) ;
117+ c . set ( 'jwt' , {
118+ sign : async ( payload : any ) => `mock_token_${ payload . id } ` ,
119+ verify : async ( token : string ) => token . startsWith ( 'mock_token_' ) ? { id : 1 } : null ,
120+ } as JWTUtils ) ;
121+ c . set ( 'env' , appEnv ) ;
122+ c . set ( 'uid' , uid ) ;
123+ await next ( ) ;
124+ } ) ) ;
125+ serviceApp . route ( '/' , StorageService ( ) ) ;
126+ return serviceApp ;
127+ }
128+
110129 describe ( 'POST / - Upload file' , ( ) => {
111130 it ( 'should require authentication' , async ( ) => {
112131 const formData = new FormData ( ) ;
@@ -122,70 +141,91 @@ describe('StorageService', () => {
122141 expect ( res . status ) . toBeLessThanOrEqual ( 401 ) ;
123142 } ) ;
124143
125- it ( 'should return 500 when S3_ENDPOINT is not defined' , async ( ) => {
126- const envNoS3 = createMockEnv ( {
144+ it ( 'should upload through R2 binding when configured' , async ( ) => {
145+ const putCalls : Array < { key : string ; type : string | undefined } > = [ ] ;
146+ const r2Env = createMockEnv ( {
147+ R2_BUCKET : {
148+ put : async ( key : string , value : any , options ?: R2PutOptions ) => {
149+ putCalls . push ( {
150+ key,
151+ type : options ?. httpMetadata && 'contentType' in options . httpMetadata
152+ ? options . httpMetadata . contentType
153+ : undefined ,
154+ } ) ;
155+ return {
156+ key,
157+ version : '1' ,
158+ size : value . size || 0 ,
159+ etag : 'etag' ,
160+ httpEtag : 'etag' ,
161+ uploaded : new Date ( ) ,
162+ storageClass : 'Standard' ,
163+ checksums : { } as R2Checksums ,
164+ writeHttpMetadata : ( ) => { } ,
165+ } as unknown as R2Object ;
166+ } ,
167+ } as R2Bucket ,
168+ S3_ACCESS_HOST : 'https://images.example.com' as any ,
127169 S3_ENDPOINT : '' as any ,
170+ S3_BUCKET : '' as any ,
171+ S3_ACCESS_KEY_ID : '' ,
172+ S3_SECRET_ACCESS_KEY : '' ,
128173 } ) ;
129174
130- const appNoS3 = new Hono < { Bindings : Env ; Variables : Variables } > ( ) ;
131- appNoS3 . use ( createMiddleware < { Bindings : Env ; Variables : Variables } > ( async ( c , next ) => {
132- c . set ( 'db' , db ) ;
133- c . set ( 'cache' , new TestCacheImpl ( ) ) ;
134- c . set ( 'serverConfig' , new TestCacheImpl ( ) ) ;
135- c . set ( 'clientConfig' , new TestCacheImpl ( ) ) ;
136- c . set ( 'jwt' , {
137- sign : async ( payload : any ) => `mock_token_${ payload . id } ` ,
138- verify : async ( token : string ) => token . startsWith ( 'mock_token_' ) ? { id : 1 } : null ,
139- } as JWTUtils ) ;
140- c . set ( 'env' , envNoS3 ) ;
141- c . set ( 'uid' , undefined ) ;
142- await next ( ) ;
143- } ) ) ;
144- appNoS3 . route ( '/' , StorageService ( ) ) ;
175+ const r2App = createAppWithEnv ( r2Env , 1 ) ;
176+ const formData = new FormData ( ) ;
177+ formData . append ( 'key' , 'test.txt' ) ;
178+ formData . append ( 'file' , new File ( [ 'test content' ] , 'test.txt' , { type : 'text/plain' } ) ) ;
179+
180+ const res = await r2App . request ( '/' , {
181+ method : 'POST' ,
182+ body : formData ,
183+ } , r2Env ) ;
184+
185+ expect ( res . status ) . toBe ( 200 ) ;
186+ expect ( putCalls ) . toHaveLength ( 1 ) ;
187+ expect ( putCalls [ 0 ] ?. key ) . toMatch ( / ^ i m a g e s \/ [ a - f 0 - 9 ] + \. t x t $ / ) ;
188+ expect ( putCalls [ 0 ] ?. type ) . toBe ( 'text/plain;charset=utf-8' ) ;
189+ const payload = await res . json ( ) as { url : string } ;
190+ expect ( payload . url ) . toMatch ( / ^ h t t p s : \/ \/ i m a g e s \. e x a m p l e \. c o m \/ i m a g e s \/ [ a - f 0 - 9 ] + \. t x t $ / ) ;
191+ } ) ;
192+
193+ it ( 'should return 500 when S3_ENDPOINT is not defined without R2 binding' , async ( ) => {
194+ const envNoS3 = createMockEnv ( {
195+ S3_ENDPOINT : '' as any ,
196+ } ) ;
197+ const appNoS3 = createAppWithEnv ( envNoS3 , 1 ) ;
145198
146199 const formData = new FormData ( ) ;
200+ formData . append ( 'key' , 'test.txt' ) ;
147201 formData . append ( 'file' , new File ( [ 'test content' ] , 'test.txt' , { type : 'text/plain' } ) ) ;
148202
149203 const res = await appNoS3 . request ( '/' , {
150204 method : 'POST' ,
151- headers : { 'Authorization' : 'Bearer mock_token_1' } ,
152205 body : formData ,
153206 } , envNoS3 ) ;
154207
155- expect ( res . status ) . toBeGreaterThanOrEqual ( 400 ) ;
208+ expect ( res . status ) . toBe ( 500 ) ;
209+ expect ( await res . text ( ) ) . toBe ( 'S3_ENDPOINT is not defined' ) ;
156210 } ) ;
157211
158- it ( 'should return error when S3_ACCESS_KEY_ID is not defined' , async ( ) => {
212+ it ( 'should return error when S3_ACCESS_KEY_ID is not defined without R2 binding ' , async ( ) => {
159213 const envNoKey = createMockEnv ( {
160214 S3_ACCESS_KEY_ID : '' ,
161215 } ) ;
162-
163- const appNoKey = new Hono < { Bindings : Env ; Variables : Variables } > ( ) ;
164- appNoKey . use ( createMiddleware < { Bindings : Env ; Variables : Variables } > ( async ( c , next ) => {
165- c . set ( 'db' , db ) ;
166- c . set ( 'cache' , new TestCacheImpl ( ) ) ;
167- c . set ( 'serverConfig' , new TestCacheImpl ( ) ) ;
168- c . set ( 'clientConfig' , new TestCacheImpl ( ) ) ;
169- c . set ( 'jwt' , {
170- sign : async ( payload : any ) => `mock_token_${ payload . id } ` ,
171- verify : async ( token : string ) => token . startsWith ( 'mock_token_' ) ? { id : 1 } : null ,
172- } as JWTUtils ) ;
173- c . set ( 'env' , envNoKey ) ;
174- c . set ( 'uid' , undefined ) ;
175- await next ( ) ;
176- } ) ) ;
177- appNoKey . route ( '/' , StorageService ( ) ) ;
216+ const appNoKey = createAppWithEnv ( envNoKey , 1 ) ;
178217
179218 const formData = new FormData ( ) ;
219+ formData . append ( 'key' , 'test.txt' ) ;
180220 formData . append ( 'file' , new File ( [ 'test content' ] , 'test.txt' , { type : 'text/plain' } ) ) ;
181221
182222 const res = await appNoKey . request ( '/' , {
183223 method : 'POST' ,
184- headers : { 'Authorization' : 'Bearer mock_token_1' } ,
185224 body : formData ,
186225 } , envNoKey ) ;
187226
188- expect ( res . status ) . toBeGreaterThanOrEqual ( 400 ) ;
227+ expect ( res . status ) . toBe ( 500 ) ;
228+ expect ( await res . text ( ) ) . toBe ( 'S3_ACCESS_KEY_ID is not defined' ) ;
189229 } ) ;
190230 } ) ;
191231} ) ;
0 commit comments