fix: accept compressed Robot revision ETags
This commit is contained in:
@@ -98,10 +98,56 @@ describe('AI hardware Host API route', () => {
|
||||
expect(result.payload).toEqual({ success: true, data: config, revision: 0 });
|
||||
});
|
||||
|
||||
it('rejects weak upstream ETags without exposing upstream data', async () => {
|
||||
it('accepts canonical weak numeric ETags for versioned responses and keeps outbound If-Match strong', async () => {
|
||||
const config = {
|
||||
id: 'a-1', name: 'Desk', config_revision: 0,
|
||||
system_prompt: null, lang_code: null, language: null, asr_model_id: null,
|
||||
vad_model_id: null, llm_model_id: null, slm_model_id: null, vllm_model_id: null,
|
||||
tts_model_id: null, tts_voice_id: null, tts_language: null, tts_volume: null,
|
||||
tts_rate: null, tts_pitch: null, mem_model_id: null, intent_model_id: null,
|
||||
chat_history_conf: null,
|
||||
};
|
||||
const updatedConfig = { ...config, config_revision: 1, system_prompt: 'hello' };
|
||||
const assignment = { id: 'd-1', agent_id: 'a-1', assignment_revision: 0 };
|
||||
const updatedAssignment = { ...assignment, assignment_revision: 1 };
|
||||
const fetchImpl = vi.fn<typeof fetch>()
|
||||
.mockResolvedValueOnce(jsonResponse(config, { headers: { etag: 'W/"0"' } }))
|
||||
.mockResolvedValueOnce(jsonResponse(updatedConfig, { headers: { etag: 'W/"1"' } }))
|
||||
.mockResolvedValueOnce(jsonResponse(assignment, { headers: { etag: 'W/"0"' } }))
|
||||
.mockResolvedValueOnce(jsonResponse(updatedAssignment, { headers: { etag: 'W/"1"' } }));
|
||||
const { handler } = setup(fetchImpl);
|
||||
|
||||
const getConfig = await invoke(handler, 'GET', '/api/works/ai-hardware/agents/a-1');
|
||||
expect(getConfig.payload).toEqual({ success: true, data: config, revision: 0 });
|
||||
|
||||
const patchConfig = await invoke(handler, 'PATCH', '/api/works/ai-hardware/agents/a-1', {
|
||||
revision: 0, system_prompt: 'hello',
|
||||
});
|
||||
expect(patchConfig.payload).toEqual({ success: true, data: updatedConfig, revision: 1 });
|
||||
expect((fetchImpl.mock.calls[1][1] as RequestInit).headers).toMatchObject({ 'If-Match': '"0"' });
|
||||
|
||||
const getAssignment = await invoke(handler, 'GET', '/api/works/ai-hardware/devices/d-1/agent-assignment');
|
||||
expect(getAssignment.payload).toEqual({ success: true, data: assignment, revision: 0 });
|
||||
|
||||
const putAssignment = await invoke(handler, 'PUT', '/api/works/ai-hardware/devices/d-1/agent-assignment', {
|
||||
revision: 0, agent_id: 'a-1',
|
||||
});
|
||||
expect(putAssignment.payload).toEqual({ success: true, data: updatedAssignment, revision: 1 });
|
||||
expect((fetchImpl.mock.calls[3][1] as RequestInit).headers).toMatchObject({ 'If-Match': '"0"' });
|
||||
});
|
||||
|
||||
it.each([
|
||||
'w/"0"',
|
||||
'W/ "0"',
|
||||
'W/"00"',
|
||||
'W/"-1"',
|
||||
'W/"1.0"',
|
||||
'W/"revision"',
|
||||
'W/"9007199254740992"',
|
||||
])('rejects non-canonical weak ETag %s without exposing upstream data', async (etag) => {
|
||||
const fetchImpl = vi.fn<typeof fetch>().mockResolvedValue(jsonResponse({
|
||||
id: 'd-1', agent_id: 'a-1', assignment_revision: 0, token: 'secret',
|
||||
}, { headers: { etag: 'W/"0"' } }));
|
||||
}, { headers: { etag } }));
|
||||
const { handler } = setup(fetchImpl);
|
||||
const result = await invoke(handler, 'GET', '/api/works/ai-hardware/devices/d-1/agent-assignment');
|
||||
expect(result.status).toBe(200);
|
||||
@@ -109,7 +155,7 @@ describe('AI hardware Host API route', () => {
|
||||
expect(JSON.stringify(result.payload)).not.toContain('secret');
|
||||
});
|
||||
|
||||
it('rejects config and assignment DTO revisions that disagree with the strong ETag', async () => {
|
||||
it('rejects config and assignment DTO revisions that disagree with the canonical ETag', async () => {
|
||||
const config = {
|
||||
id: 'a-1', name: 'Desk', config_revision: 2,
|
||||
system_prompt: null, lang_code: null, language: null, asr_model_id: null,
|
||||
@@ -118,13 +164,13 @@ describe('AI hardware Host API route', () => {
|
||||
tts_rate: null, tts_pitch: null, mem_model_id: null, intent_model_id: null,
|
||||
chat_history_conf: null,
|
||||
};
|
||||
const configHandler = setup(vi.fn<typeof fetch>().mockResolvedValue(jsonResponse(config, { headers: { etag: '"1"' } })));
|
||||
const configHandler = setup(vi.fn<typeof fetch>().mockResolvedValue(jsonResponse(config, { headers: { etag: 'W/"1"' } })));
|
||||
const configResult = await invoke(configHandler.handler, 'GET', '/api/works/ai-hardware/agents/a-1');
|
||||
expect(configResult.payload).toMatchObject({ success: false, status: 502, code: 'AI_HARDWARE_INVALID_RESPONSE' });
|
||||
|
||||
const deviceHandler = setup(vi.fn<typeof fetch>().mockResolvedValue(jsonResponse(
|
||||
{ id: 'd-1', agent_id: 'a-1', assignment_revision: 3 },
|
||||
{ headers: { etag: '"2"' } },
|
||||
{ headers: { etag: 'W/"2"' } },
|
||||
)));
|
||||
const deviceResult = await invoke(deviceHandler.handler, 'GET', '/api/works/ai-hardware/devices/d-1/agent-assignment');
|
||||
expect(deviceResult.payload).toMatchObject({ success: false, status: 502, code: 'AI_HARDWARE_INVALID_RESPONSE' });
|
||||
|
||||
Reference in New Issue
Block a user