fix: recover Robot configuration loading
This commit is contained in:
@@ -366,6 +366,32 @@ describe('AI hardware page', () => {
|
||||
expect(screen.getByText('正在读取配置')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('stops loading and lets the user retry after configuration loading fails', async () => {
|
||||
api.getAiHardwareOverview.mockResolvedValueOnce({
|
||||
status: 'active',
|
||||
agents: [agentOne],
|
||||
devices: [device],
|
||||
});
|
||||
api.getAiHardwareAgentConfiguration
|
||||
.mockRejectedValueOnce(new AiHardwareApiError({
|
||||
status: 502,
|
||||
code: 'xiaozhi_hardware_unavailable',
|
||||
message: 'private provider details',
|
||||
retryable: true,
|
||||
}))
|
||||
.mockResolvedValueOnce({ data: configuration, revision: 4 });
|
||||
|
||||
render(<AiHardware />);
|
||||
|
||||
expect(await screen.findByRole('alert')).toHaveTextContent('无法读取智能体配置');
|
||||
expect(screen.queryByText('正在读取配置')).not.toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '重试读取配置' }));
|
||||
await waitFor(() => expect(api.getAiHardwareAgentConfiguration).toHaveBeenCalledTimes(2));
|
||||
expect(await screen.findByText('保持简洁')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '编辑配置' })).toBeEnabled();
|
||||
});
|
||||
|
||||
it('loads the current assignment revision before reassigning a device', async () => {
|
||||
render(<AiHardware />);
|
||||
fireEvent.click(await screen.findByRole('button', { name: '重新指派' }));
|
||||
|
||||
@@ -326,6 +326,41 @@ describe('AI hardware Host API route', () => {
|
||||
expect(hugeResult.payload).toMatchObject({ success: false, status: 502, code: 'AI_HARDWARE_RESPONSE_TOO_LARGE' });
|
||||
});
|
||||
|
||||
it('keeps the upstream deadline active while reading a stalled configuration body', async () => {
|
||||
let bodyController: ReadableStreamDefaultController<Uint8Array> | undefined;
|
||||
const fetchImpl = vi.fn<typeof fetch>((_input, init) => {
|
||||
const body = new ReadableStream<Uint8Array>({
|
||||
start(controller) {
|
||||
bodyController = controller;
|
||||
controller.enqueue(new TextEncoder().encode('{"id":"a-1"'));
|
||||
init?.signal?.addEventListener('abort', () => {
|
||||
controller.error(new DOMException('secret stalled body', 'AbortError'));
|
||||
}, { once: true });
|
||||
},
|
||||
});
|
||||
return Promise.resolve(new Response(body, {
|
||||
headers: { 'content-type': 'application/json', etag: '"0"' },
|
||||
}));
|
||||
});
|
||||
const { handler } = setup(fetchImpl);
|
||||
const pending = invoke(handler, 'GET', '/api/works/ai-hardware/agents/a-1');
|
||||
const result = await Promise.race([
|
||||
pending,
|
||||
new Promise<null>((resolve) => setTimeout(() => resolve(null), 100)),
|
||||
]);
|
||||
|
||||
if (result === null) {
|
||||
bodyController?.close();
|
||||
await pending;
|
||||
}
|
||||
expect(result?.payload).toMatchObject({
|
||||
success: false,
|
||||
status: 504,
|
||||
code: 'AI_HARDWARE_TIMEOUT',
|
||||
retryable: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('bounds request bodies and distinguishes unrelated and unknown hardware routes', async () => {
|
||||
const { handler, fetchImpl } = setup();
|
||||
const unrelated = response();
|
||||
|
||||
Reference in New Issue
Block a user