@@ -21,7 +21,10 @@ import {
2121 MAX_QUERY_LENGTH ,
2222 MAX_TOTAL_ATTACHMENTS_SIZE_BYTES ,
2323} from './constant' ;
24- import { validateCompletionsRequest } from './validation' ;
24+ import {
25+ validateAttachmentsForModel ,
26+ validateCompletionsRequest ,
27+ } from './validation' ;
2528
2629describe ( 'validateCompletionsRequest' , ( ) => {
2730 let mockReq : Partial < Request > ;
@@ -142,7 +145,7 @@ describe('validateCompletionsRequest', () => {
142145 } ) ;
143146 } ) ;
144147
145- describe ( 'attachment validation ' , ( ) => {
148+ describe ( 'attachment passthrough ' , ( ) => {
146149 it ( 'should pass with no attachments' , ( ) => {
147150 mockReq . body = {
148151 model : 'gpt-4' ,
@@ -156,14 +159,17 @@ describe('validateCompletionsRequest', () => {
156159 expect ( statusMock ) . not . toHaveBeenCalled ( ) ;
157160 } ) ;
158161
159- it ( 'should pass with valid small attachments ' , ( ) => {
162+ it ( 'should pass with attachments (size validation is in validateAttachmentsForModel) ' , ( ) => {
160163 mockReq . body = {
161164 model : 'gpt-4' ,
162165 provider : 'openai' ,
163166 query : 'test query' ,
164167 attachments : [
165- { name : 'file1.txt' , content : 'small content' } ,
166- { name : 'file2.txt' , content : 'another small content' } ,
168+ {
169+ attachment_type : 'dom' ,
170+ content_type : 'text/html' ,
171+ content : 'small content' ,
172+ } ,
167173 ] ,
168174 } ;
169175
@@ -172,82 +178,113 @@ describe('validateCompletionsRequest', () => {
172178 expect ( mockNext ) . toHaveBeenCalled ( ) ;
173179 expect ( statusMock ) . not . toHaveBeenCalled ( ) ;
174180 } ) ;
181+ } ) ;
182+ } ) ;
175183
176- it ( 'should reject attachment exceeding MAX_ATTACHMENT_SIZE_BYTES' , ( ) => {
177- // Create content that exceeds the limit (20MB)
178- const largeContent = 'a' . repeat ( MAX_ATTACHMENT_SIZE_BYTES + 1 ) ;
179- mockReq . body = {
180- model : 'gpt-4' ,
181- provider : 'openai' ,
182- query : 'test query' ,
183- attachments : [ { name : 'large-file.txt' , content : largeContent } ] ,
184- } ;
184+ describe ( 'validateAttachmentsForModel' , ( ) => {
185+ let mockReq : Partial < Request > ;
186+ let mockRes : Partial < Response > ;
187+ let mockNext : NextFunction ;
188+ let statusMock : jest . Mock ;
189+ let jsonMock : jest . Mock ;
185190
186- callValidate ( ) ;
191+ beforeEach ( ( ) => {
192+ jsonMock = jest . fn ( ) ;
193+ statusMock = jest . fn ( ) . mockReturnValue ( { json : jsonMock } ) ;
194+ mockReq = { body : { } } ;
195+ mockRes = { status : statusMock } as unknown as Partial < Response > ;
196+ mockNext = jest . fn ( ) ;
197+ } ) ;
187198
188- expect ( statusMock ) . toHaveBeenCalledWith ( 400 ) ;
189- expect ( jsonMock ) . toHaveBeenCalledWith ( {
190- error : `Attachment "large-file.txt" exceeds maximum size of ${ MAX_ATTACHMENT_SIZE_BYTES / ( 1024 * 1024 ) } MB` ,
191- } ) ;
192- expect ( mockNext ) . not . toHaveBeenCalled ( ) ;
193- } ) ;
199+ function callValidate ( ) {
200+ validateAttachmentsForModel (
201+ mockReq as Request ,
202+ mockRes as Response ,
203+ mockNext ,
204+ ) ;
205+ }
194206
195- it ( 'should reject total attachments exceeding MAX_TOTAL_ATTACHMENTS_SIZE_BYTES' , ( ) => {
196- // Create multiple attachments that individually pass but together exceed total limit
197- const attachmentSize = 18 * 1024 * 1024 ; // 18MB each
198- const content1 = 'a' . repeat ( attachmentSize ) ;
199- const content2 = 'b' . repeat ( attachmentSize ) ;
200- const content3 = 'c' . repeat ( attachmentSize ) ;
207+ it ( 'should pass with no attachments' , ( ) => {
208+ mockReq . body = { model : 'gpt-4' } ;
209+ callValidate ( ) ;
210+ expect ( mockNext ) . toHaveBeenCalled ( ) ;
211+ } ) ;
201212
202- mockReq . body = {
203- model : 'gpt-4' ,
204- provider : 'openai' ,
205- query : 'test query' ,
206- attachments : [
207- { name : 'file1.txt' , content : content1 } ,
208- { name : 'file2.txt' , content : content2 } ,
209- { name : 'file3.txt' , content : content3 } ,
210- ] ,
211- } ;
213+ it ( 'should pass with empty attachments' , ( ) => {
214+ mockReq . body = { model : 'gpt-4' , attachments : [ ] } ;
215+ callValidate ( ) ;
216+ expect ( mockNext ) . toHaveBeenCalled ( ) ;
217+ } ) ;
212218
213- callValidate ( ) ;
219+ it ( 'should reject attachment exceeding MAX_ATTACHMENT_SIZE_BYTES' , ( ) => {
220+ const largeContent = 'a' . repeat ( MAX_ATTACHMENT_SIZE_BYTES + 1 ) ;
221+ mockReq . body = {
222+ model : 'gpt-4' ,
223+ attachments : [
224+ {
225+ attachment_type : 'dom' ,
226+ content_type : 'text/html' ,
227+ content : largeContent ,
228+ } ,
229+ ] ,
230+ } ;
214231
215- expect ( statusMock ) . toHaveBeenCalledWith ( 400 ) ;
216- expect ( jsonMock ) . toHaveBeenCalledWith ( {
217- error : `Total attachments size exceeds maximum of ${ MAX_TOTAL_ATTACHMENTS_SIZE_BYTES / ( 1024 * 1024 ) } MB` ,
218- } ) ;
219- expect ( mockNext ) . not . toHaveBeenCalled ( ) ;
232+ callValidate ( ) ;
233+
234+ expect ( statusMock ) . toHaveBeenCalledWith ( 400 ) ;
235+ expect ( jsonMock ) . toHaveBeenCalledWith ( {
236+ error : `Attachment with type "dom" exceeds maximum size of ${ MAX_ATTACHMENT_SIZE_BYTES / ( 1024 * 1024 ) } MB` ,
220237 } ) ;
238+ expect ( mockNext ) . not . toHaveBeenCalled ( ) ;
239+ } ) ;
221240
222- it ( 'should handle attachments with empty content' , ( ) => {
223- mockReq . body = {
224- model : 'gpt-4' ,
225- provider : 'openai' ,
226- query : 'test query' ,
227- attachments : [
228- { name : 'empty.txt' , content : '' } ,
229- { name : 'file.txt' , content : 'some content' } ,
230- ] ,
231- } ;
241+ it ( 'should reject total attachments exceeding MAX_TOTAL_ATTACHMENTS_SIZE_BYTES' , ( ) => {
242+ const attachmentSize = 18 * 1024 * 1024 ;
243+ const content1 = 'a' . repeat ( attachmentSize ) ;
244+ const content2 = 'b' . repeat ( attachmentSize ) ;
245+ const content3 = 'c' . repeat ( attachmentSize ) ;
246+
247+ mockReq . body = {
248+ model : 'gpt-4' ,
249+ attachments : [
250+ {
251+ attachment_type : 'dom' ,
252+ content_type : 'text/html' ,
253+ content : content1 ,
254+ } ,
255+ {
256+ attachment_type : 'dom' ,
257+ content_type : 'text/html' ,
258+ content : content2 ,
259+ } ,
260+ {
261+ attachment_type : 'dom' ,
262+ content_type : 'text/html' ,
263+ content : content3 ,
264+ } ,
265+ ] ,
266+ } ;
232267
233- callValidate ( ) ;
268+ callValidate ( ) ;
234269
235- expect ( mockNext ) . toHaveBeenCalled ( ) ;
236- expect ( statusMock ) . not . toHaveBeenCalled ( ) ;
270+ expect ( statusMock ) . toHaveBeenCalledWith ( 400 ) ;
271+ expect ( jsonMock ) . toHaveBeenCalledWith ( {
272+ error : `Total attachments size exceeds maximum of ${ MAX_TOTAL_ATTACHMENTS_SIZE_BYTES / ( 1024 * 1024 ) } MB` ,
237273 } ) ;
274+ expect ( mockNext ) . not . toHaveBeenCalled ( ) ;
275+ } ) ;
238276
239- it ( 'should handle attachments without content field ' , ( ) => {
240- mockReq . body = {
241- model : 'gpt-4' ,
242- provider : 'openai' ,
243- query : 'test query' ,
244- attachments : [ { name : 'file.txt' } ] ,
245- } ;
277+ it ( 'should handle attachments with empty content ' , ( ) => {
278+ mockReq . body = {
279+ model : 'gpt-4' ,
280+ attachments : [
281+ { attachment_type : 'dom' , content_type : 'text/html' , content : '' } ,
282+ ] ,
283+ } ;
246284
247- callValidate ( ) ;
285+ callValidate ( ) ;
248286
249- expect ( mockNext ) . toHaveBeenCalled ( ) ;
250- expect ( statusMock ) . not . toHaveBeenCalled ( ) ;
251- } ) ;
287+ expect ( mockNext ) . toHaveBeenCalled ( ) ;
288+ expect ( statusMock ) . not . toHaveBeenCalled ( ) ;
252289 } ) ;
253290} ) ;
0 commit comments