@@ -8,16 +8,31 @@ jest.mock('@aws-sdk/client-s3', () => ({
88 HeadObjectCommand : jest . fn ( ) . mockImplementation ( ( params ) => params )
99} ) ) ;
1010
11- const mockPgQuery = jest . fn ( ) ;
12- const mockPgConnect = jest . fn ( ) ;
13- const mockPgEnd = jest . fn ( ) ;
14- jest . mock ( 'pg' , ( ) => ( {
15- Client : jest . fn ( ) . mockImplementation ( ( ) => ( {
16- connect : mockPgConnect ,
17- query : mockPgQuery ,
18- end : mockPgEnd
19- } ) )
20- } ) ) ;
11+ const mockStorageModuleRow = {
12+ id : 'sm-123' ,
13+ buckets_schema : 'test-db-storage-public' ,
14+ buckets_table : 'app_buckets' ,
15+ files_schema : 'test-db-storage-public' ,
16+ files_table : 'app_files' ,
17+ endpoint : null ,
18+ provider : 'minio' ,
19+ } ;
20+
21+ const createMockPool = ( ) => {
22+ const mockQuery = jest . fn ( ) ;
23+ const mockRelease = jest . fn ( ) ;
24+ const mockClient = {
25+ query : mockQuery ,
26+ release : mockRelease
27+ } ;
28+ const pool = {
29+ connect : jest . fn ( ) . mockResolvedValue ( mockClient ) ,
30+ query : mockQuery ,
31+ _mockClient : mockClient ,
32+ _mockQuery : mockQuery
33+ } ;
34+ return pool ;
35+ } ;
2136
2237const loadHandler = ( ) => {
2338 const mod = require ( '../handler' ) ;
@@ -44,58 +59,96 @@ describe('storage-confirm-upload handler', () => {
4459 describe ( 'validation' , ( ) => {
4560 it ( 'throws on missing file_id' , async ( ) => {
4661 const handler = loadHandler ( ) ;
62+ const pool = createMockPool ( ) ;
4763 await expect (
4864 handler (
4965 { key : 'test.txt' , bucket_id : 'bucket-123' } ,
50- createMockContext ( )
66+ createMockContext ( { pool , databaseId : 'db-123' } )
5167 )
5268 ) . rejects . toThrow ( 'Missing required fields' ) ;
5369 } ) ;
5470
5571 it ( 'throws on missing key' , async ( ) => {
5672 const handler = loadHandler ( ) ;
73+ const pool = createMockPool ( ) ;
5774 await expect (
5875 handler (
5976 { file_id : '123' , bucket_id : 'bucket-123' } ,
60- createMockContext ( )
77+ createMockContext ( { pool , databaseId : 'db-123' } )
6178 )
6279 ) . rejects . toThrow ( 'Missing required fields' ) ;
6380 } ) ;
6481
6582 it ( 'throws on missing bucket_id' , async ( ) => {
6683 const handler = loadHandler ( ) ;
84+ const pool = createMockPool ( ) ;
6785 await expect (
6886 handler (
6987 { file_id : '123' , key : 'test.txt' } ,
70- createMockContext ( )
88+ createMockContext ( { pool , databaseId : 'db-123' } )
7189 )
7290 ) . rejects . toThrow ( 'Missing required fields' ) ;
7391 } ) ;
92+
93+ it ( 'throws on missing databaseId' , async ( ) => {
94+ const handler = loadHandler ( ) ;
95+ const pool = createMockPool ( ) ;
96+ await expect (
97+ handler (
98+ { file_id : '123' , key : 'test.txt' , bucket_id : 'bucket-123' } ,
99+ createMockContext ( { pool, databaseId : undefined } )
100+ )
101+ ) . rejects . toThrow ( 'Missing databaseId' ) ;
102+ } ) ;
103+ } ) ;
104+
105+ describe ( 'storage module lookup' , ( ) => {
106+ it ( 'throws when storage module not found' , async ( ) => {
107+ const handler = loadHandler ( ) ;
108+ const pool = createMockPool ( ) ;
109+ pool . _mockQuery . mockResolvedValueOnce ( { rows : [ ] } ) ; // No storage module
110+
111+ await expect (
112+ handler (
113+ { file_id : '123' , key : 'test.txt' , bucket_id : 'bucket-123' } ,
114+ createMockContext ( { pool, databaseId : 'db-123' } )
115+ )
116+ ) . rejects . toThrow ( 'STORAGE_MODULE_NOT_FOUND' ) ;
117+ } ) ;
74118 } ) ;
75119
76120 describe ( 'S3 file check' , ( ) => {
77121 it ( 'throws when file not found in S3' , async ( ) => {
78122 const handler = loadHandler ( ) ;
123+ const pool = createMockPool ( ) ;
124+ // Storage module query
125+ pool . _mockQuery . mockResolvedValueOnce ( { rows : [ mockStorageModuleRow ] } ) ;
126+ // Bucket type query
127+ pool . _mockQuery . mockResolvedValueOnce ( { rows : [ { type : 'public' } ] } ) ;
128+ // S3 HeadObject fails
79129 const notFoundError = new Error ( 'Not Found' ) ;
80130 ( notFoundError as any ) . name = 'NotFound' ;
81131 mockSend . mockRejectedValueOnce ( notFoundError ) ;
82132
83133 await expect (
84134 handler (
85135 { file_id : '123' , key : 'test.txt' , bucket_id : 'bucket-123' } ,
86- createMockContext ( )
136+ createMockContext ( { pool , databaseId : 'db-123' } )
87137 )
88138 ) . rejects . toThrow ( 'File not found in S3' ) ;
89139 } ) ;
90140
91141 it ( 'rethrows unexpected S3 errors' , async ( ) => {
92142 const handler = loadHandler ( ) ;
143+ const pool = createMockPool ( ) ;
144+ pool . _mockQuery . mockResolvedValueOnce ( { rows : [ mockStorageModuleRow ] } ) ;
145+ pool . _mockQuery . mockResolvedValueOnce ( { rows : [ { type : 'public' } ] } ) ;
93146 mockSend . mockRejectedValueOnce ( new Error ( 'Network error' ) ) ;
94147
95148 await expect (
96149 handler (
97150 { file_id : '123' , key : 'test.txt' , bucket_id : 'bucket-123' } ,
98- createMockContext ( )
151+ createMockContext ( { pool , databaseId : 'db-123' } )
99152 )
100153 ) . rejects . toThrow ( 'Network error' ) ;
101154 } ) ;
@@ -104,95 +157,100 @@ describe('storage-confirm-upload handler', () => {
104157 describe ( 'confirm upload' , ( ) => {
105158 it ( 'calls confirm function when file exists in S3' , async ( ) => {
106159 const handler = loadHandler ( ) ;
107- mockSend . mockResolvedValueOnce ( { } ) ; // HeadObject success
108- mockPgQuery . mockResolvedValueOnce ( { rows : [ ] } ) ; // confirm_uploaded call
160+ const pool = createMockPool ( ) ;
161+ // Storage module query
162+ pool . _mockQuery . mockResolvedValueOnce ( { rows : [ mockStorageModuleRow ] } ) ;
163+ // Bucket type query
164+ pool . _mockQuery . mockResolvedValueOnce ( { rows : [ { type : 'public' } ] } ) ;
165+ // S3 HeadObject success
166+ mockSend . mockResolvedValueOnce ( { } ) ;
167+ // Confirm uploaded call
168+ pool . _mockQuery . mockResolvedValueOnce ( { rows : [ ] } ) ;
109169
110170 const result = await handler (
111- {
112- file_id : '123' ,
113- key : 'test.txt' ,
114- bucket_id : 'bucket-123' ,
115- schema : 'test-db-app-public' ,
116- table : 'app_files'
117- } ,
118- createMockContext ( )
171+ { file_id : '123' , key : 'test.txt' , bucket_id : 'bucket-123' } ,
172+ createMockContext ( { pool, databaseId : 'db-123' } )
119173 ) ;
120174
121175 expect ( result . success ) . toBe ( true ) ;
122176 expect ( result . file_id ) . toBe ( '123' ) ;
123- expect ( mockPgQuery ) . toHaveBeenCalled ( ) ;
177+ // Verify confirm function was called with correct schema
178+ expect ( pool . _mockQuery ) . toHaveBeenCalledWith (
179+ expect . stringContaining ( 'test-db-storage-private' ) ,
180+ [ '123' ]
181+ ) ;
182+ expect ( pool . _mockQuery ) . toHaveBeenCalledWith (
183+ expect . stringContaining ( 'app_files_confirm_uploaded' ) ,
184+ [ '123' ]
185+ ) ;
124186 } ) ;
125187
126- it ( 'uses default schema and table when not provided ' , async ( ) => {
188+ it ( 'uses schema names from storage_module metadata ' , async ( ) => {
127189 const handler = loadHandler ( ) ;
128- mockSend . mockResolvedValueOnce ( { } ) ; // HeadObject success
129- mockPgQuery . mockResolvedValueOnce ( { rows : [ ] } ) ; // confirm_uploaded call
190+ const pool = createMockPool ( ) ;
191+ const customRow = {
192+ ...mockStorageModuleRow ,
193+ buckets_schema : 'custom-db-storage-public' ,
194+ files_schema : 'custom-db-storage-public' ,
195+ files_table : 'custom_files' ,
196+ } ;
197+ pool . _mockQuery . mockResolvedValueOnce ( { rows : [ customRow ] } ) ;
198+ pool . _mockQuery . mockResolvedValueOnce ( { rows : [ { type : 'private' } ] } ) ;
199+ mockSend . mockResolvedValueOnce ( { } ) ;
200+ pool . _mockQuery . mockResolvedValueOnce ( { rows : [ ] } ) ;
130201
131202 const result = await handler (
132- {
133- file_id : '123' ,
134- key : 'test.txt' ,
135- bucket_id : 'bucket-123'
136- } ,
137- createMockContext ( )
203+ { file_id : '456' , key : 'doc.pdf' , bucket_id : 'bucket-456' } ,
204+ createMockContext ( { pool, databaseId : 'custom-db' } )
138205 ) ;
139206
140207 expect ( result . success ) . toBe ( true ) ;
141- // Should use default storage_public and app_files
142- expect ( mockPgQuery ) . toHaveBeenCalledWith (
143- expect . stringContaining ( 'app_files_confirm_uploaded' ) ,
144- [ '123' ]
208+ // Verify it used the custom schema/table from storage_module
209+ expect ( pool . _mockQuery ) . toHaveBeenCalledWith (
210+ expect . stringContaining ( 'custom-db-storage-private' ) ,
211+ [ '456' ]
212+ ) ;
213+ expect ( pool . _mockQuery ) . toHaveBeenCalledWith (
214+ expect . stringContaining ( 'custom_files_confirm_uploaded' ) ,
215+ [ '456' ]
145216 ) ;
146217 } ) ;
147218 } ) ;
148219
149220 describe ( 'bucket name resolution' , ( ) => {
150- it ( 'resolves bucket name from database when schema and databaseId provided ' , async ( ) => {
221+ it ( 'resolves bucket name from database using storage_module schema ' , async ( ) => {
151222 const handler = loadHandler ( ) ;
152- // First query: resolve bucket name
153- mockPgQuery . mockResolvedValueOnce ( { rows : [ { type : 'public' } ] } ) ;
154- // HeadObject success
223+ const pool = createMockPool ( ) ;
224+ pool . _mockQuery . mockResolvedValueOnce ( { rows : [ mockStorageModuleRow ] } ) ;
225+ pool . _mockQuery . mockResolvedValueOnce ( { rows : [ { type : 'public' } ] } ) ;
155226 mockSend . mockResolvedValueOnce ( { } ) ;
156- // Second query: confirm_uploaded call
157- mockPgQuery . mockResolvedValueOnce ( { rows : [ ] } ) ;
158-
159- const context = createMockContext ( ) ;
160- context . job . databaseId = 'db-123' ;
227+ pool . _mockQuery . mockResolvedValueOnce ( { rows : [ ] } ) ;
161228
162229 const result = await handler (
163- {
164- file_id : '123' ,
165- key : 'test.txt' ,
166- bucket_id : 'bucket-123' ,
167- schema : 'test-db-app-public'
168- } ,
169- context
230+ { file_id : '123' , key : 'test.txt' , bucket_id : 'bucket-123' } ,
231+ createMockContext ( { pool, databaseId : 'db-123' } )
170232 ) ;
171233
172234 expect ( result . success ) . toBe ( true ) ;
173235 expect ( result . bucket ) . toBe ( 'test-bucket-public-db-123' ) ;
236+ // Verify bucket query used correct schema from storage_module
237+ expect ( pool . _mockQuery ) . toHaveBeenCalledWith (
238+ expect . stringContaining ( 'test-db-storage-public' ) ,
239+ [ 'bucket-123' ]
240+ ) ;
174241 } ) ;
175242
176243 it ( 'falls back to bucket_id when resolution fails' , async ( ) => {
177244 const handler = loadHandler ( ) ;
178- // First query fails
179- mockPgQuery . mockRejectedValueOnce ( new Error ( 'DB error' ) ) ;
180- // HeadObject success
245+ const pool = createMockPool ( ) ;
246+ pool . _mockQuery . mockResolvedValueOnce ( { rows : [ mockStorageModuleRow ] } ) ;
247+ pool . _mockQuery . mockRejectedValueOnce ( new Error ( 'DB error' ) ) ;
181248 mockSend . mockResolvedValueOnce ( { } ) ;
182- // Second query: confirm_uploaded call
183- mockPgQuery . mockResolvedValueOnce ( { rows : [ ] } ) ;
184-
185- const context = createMockContext ( ) ;
186- context . job . databaseId = 'db-123' ;
249+ pool . _mockQuery . mockResolvedValueOnce ( { rows : [ ] } ) ;
187250
188251 const result = await handler (
189- {
190- file_id : '123' ,
191- key : 'test.txt' ,
192- bucket_id : 'bucket-123' ,
193- schema : 'test-db-app-public'
194- } ,
195- context
252+ { file_id : '123' , key : 'test.txt' , bucket_id : 'bucket-123' } ,
253+ createMockContext ( { pool, databaseId : 'db-123' } )
196254 ) ;
197255
198256 expect ( result . success ) . toBe ( true ) ;
0 commit comments