Skip to content

Commit a6b43f4

Browse files
committed
fix: fixing tests for validating images and router
Signed-off-by: Lucas <lyoon@redhat.com>
1 parent 2d859f5 commit a6b43f4

2 files changed

Lines changed: 128 additions & 125 deletions

File tree

workspaces/lightspeed/plugins/lightspeed-backend/src/service/router.test.ts

Lines changed: 24 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1149,16 +1149,8 @@ describe('lightspeed router tests', () => {
11491149

11501150
it('returns true when model supports vision', async () => {
11511151
rcs.use(
1152-
http.get(`${LOCAL_LCS_ADDR}/v1/models`, () => {
1153-
return HttpResponse.json({
1154-
models: [
1155-
{
1156-
identifier: 'gpt-4o',
1157-
provider_resource_id: 'gpt-4o',
1158-
supports_vision: true,
1159-
},
1160-
],
1161-
});
1152+
http.post(`${LOCAL_LCS_ADDR}/v1/responses`, () => {
1153+
return HttpResponse.json({ output: 'hi' });
11621154
}),
11631155
);
11641156

@@ -1168,21 +1160,19 @@ describe('lightspeed router tests', () => {
11681160
.send({ model: 'gpt-4o', provider: 'test-server' });
11691161

11701162
expect(response.statusCode).toEqual(200);
1171-
expect(response.body).toEqual({ supports_vision: true });
1163+
expect(response.body).toEqual({
1164+
model: 'gpt-4o',
1165+
supportsVision: true,
1166+
});
11721167
});
11731168

11741169
it('returns false when model lacks vision', async () => {
11751170
rcs.use(
1176-
http.get(`${LOCAL_LCS_ADDR}/v1/models`, () => {
1177-
return HttpResponse.json({
1178-
models: [
1179-
{
1180-
identifier: 'gpt-3.5-turbo',
1181-
provider_resource_id: 'gpt-3.5-turbo',
1182-
supports_vision: false,
1183-
},
1184-
],
1185-
});
1171+
http.post(`${LOCAL_LCS_ADDR}/v1/responses`, () => {
1172+
return HttpResponse.json(
1173+
{ error: 'model does not support images' },
1174+
{ status: 400 },
1175+
);
11861176
}),
11871177
);
11881178

@@ -1192,24 +1182,24 @@ describe('lightspeed router tests', () => {
11921182
.send({ model: 'gpt-3.5-turbo', provider: 'test-server' });
11931183

11941184
expect(response.statusCode).toEqual(200);
1195-
expect(response.body).toEqual({ supports_vision: false });
1185+
expect(response.body).toEqual({
1186+
model: 'gpt-3.5-turbo',
1187+
supportsVision: false,
1188+
});
11961189
});
11971190

1198-
it('returns 400 when model is not found', async () => {
1199-
rcs.use(
1200-
http.get(`${LOCAL_LCS_ADDR}/v1/models`, () => {
1201-
return HttpResponse.json({ models: [] });
1202-
}),
1203-
);
1191+
it('returns cached result on subsequent calls', async () => {
1192+
ModelCapabilitiesCache.set('gpt-4o', true);
12041193

12051194
const backendServer = await startBackendServer();
12061195
const response = await request(backendServer)
12071196
.post('/api/lightspeed/v1/validate-model-vision')
1208-
.send({ model: 'unknown-model', provider: 'test-server' });
1197+
.send({ model: 'gpt-4o', provider: 'test-server' });
12091198

1210-
expect(response.statusCode).toEqual(400);
1199+
expect(response.statusCode).toEqual(200);
12111200
expect(response.body).toEqual({
1212-
error: 'Model unknown-model not found',
1201+
model: 'gpt-4o',
1202+
supportsVision: true,
12131203
});
12141204
});
12151205
});
@@ -1220,19 +1210,7 @@ describe('lightspeed router tests', () => {
12201210
});
12211211

12221212
it('rejects attachments when model lacks vision', async () => {
1223-
rcs.use(
1224-
http.get(`${LOCAL_LCS_ADDR}/v1/models`, () => {
1225-
return HttpResponse.json({
1226-
models: [
1227-
{
1228-
identifier: 'gpt-3.5-turbo',
1229-
provider_resource_id: 'gpt-3.5-turbo',
1230-
supports_vision: false,
1231-
},
1232-
],
1233-
});
1234-
}),
1235-
);
1213+
ModelCapabilitiesCache.set('gpt-3.5-turbo', false);
12361214

12371215
const backendServer = await startBackendServer();
12381216
const response = await request(backendServer)
@@ -1252,24 +1230,12 @@ describe('lightspeed router tests', () => {
12521230

12531231
expect(response.statusCode).toEqual(400);
12541232
expect(response.body.error).toContain(
1255-
'Model gpt-3.5-turbo does not support image attachments',
1233+
'This model does not support JPEG images',
12561234
);
12571235
});
12581236

12591237
it('accepts attachments when model supports vision', async () => {
1260-
rcs.use(
1261-
http.get(`${LOCAL_LCS_ADDR}/v1/models`, () => {
1262-
return HttpResponse.json({
1263-
models: [
1264-
{
1265-
identifier: 'gpt-4o',
1266-
provider_resource_id: 'gpt-4o',
1267-
supports_vision: true,
1268-
},
1269-
],
1270-
});
1271-
}),
1272-
);
1238+
ModelCapabilitiesCache.set('gpt-4o', true);
12731239

12741240
const backendServer = await startBackendServer();
12751241
const response = await request(backendServer)

workspaces/lightspeed/plugins/lightspeed-backend/src/service/validation.test.ts

Lines changed: 104 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -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

2629
describe('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

Comments
 (0)