Files
openchamber/packages/ui/src/lib/walkthrough/api.test.ts
T
Bohdan Triapitsyn 8c37061886 fix(walkthrough): name an outdated server instead of failing to parse its HTML
A server without these routes does not answer 404 with JSON. The unmatched
/api path reaches the OpenCode proxy, and OpenCode serves its embedded web UI
for anything it does not recognise — HTML, status 200 — so a client newer than
its server parsed a web page as JSON and put "Unexpected token '<', "<!doctype"
in the panel, naming neither the cause nor the remedy.

The client now checks the content type before parsing. A non-JSON answer on 2xx
or 404 blocks with "this server is older than the app, update it and refresh".
A non-JSON 5xx keeps its own failure: a server that answered badly is not a
server missing the feature, and sending that user to upgrade chases the wrong
thing.
2026-08-04 19:06:19 +03:00

89 lines
3.4 KiB
TypeScript

import { beforeEach, describe, expect, mock, test } from 'bun:test';
// A server older than this client does not answer 404-with-JSON: unmatched
// `/api/*` reaches the OpenCode proxy, and OpenCode serves its embedded web UI
// for any unknown path — HTML, status 200. These tests pin that the panel gets
// an actionable code instead of a JSON parser error.
let nextResponse: Response = new Response('{}', { headers: { 'Content-Type': 'application/json' } });
mock.module('@/lib/runtime-fetch', () => ({
runtimeFetch: mock(async () => nextResponse),
}));
const { fetchWalkthrough, generateWalkthrough } = await import('./api');
const { WalkthroughError } = await import('./types');
import type { WalkthroughSource } from './types';
const SOURCE: WalkthroughSource = { kind: 'working-tree', scope: 'all' };
const html = (status: number) =>
new Response('<!doctype html><html><body>OpenCode</body></html>', {
status,
headers: { 'Content-Type': 'text/html; charset=utf-8' },
});
describe('walkthrough api', () => {
beforeEach(() => {
nextResponse = new Response('{}', { headers: { 'Content-Type': 'application/json' } });
});
test('reads a JSON answer', async () => {
nextResponse = new Response(JSON.stringify({ hunkCount: 3 }), {
headers: { 'Content-Type': 'application/json' },
});
const result = await fetchWalkthrough('/repo', SOURCE);
expect(result.hunkCount).toBe(3);
});
test('reports HTML served with 200 as a server without the routes', async () => {
nextResponse = html(200);
const error = await fetchWalkthrough('/repo', SOURCE).catch((caught: unknown) => caught);
expect(error).toBeInstanceOf(WalkthroughError);
expect((error as InstanceType<typeof WalkthroughError>).code).toBe('server-unsupported');
expect((error as Error).message).not.toContain('JSON');
});
test('reports a non-JSON 404 the same way', async () => {
nextResponse = html(404);
const error = await generateWalkthrough('/repo', SOURCE).catch((caught: unknown) => caught);
expect((error as InstanceType<typeof WalkthroughError>).code).toBe('server-unsupported');
});
test('keeps a server-side failure rather than blaming the server version', async () => {
nextResponse = new Response(JSON.stringify({ error: 'model exploded', code: 'output-exhausted' }), {
status: 500,
headers: { 'Content-Type': 'application/json' },
});
const error = await generateWalkthrough('/repo', SOURCE).catch((caught: unknown) => caught);
expect((error as InstanceType<typeof WalkthroughError>).code).toBe('output-exhausted');
expect((error as Error).message).toBe('model exploded');
});
test('a 5xx that is not JSON is a broken server, not a missing route', async () => {
nextResponse = html(502);
const error = await fetchWalkthrough('/repo', SOURCE).catch((caught: unknown) => caught);
expect((error as InstanceType<typeof WalkthroughError>).code).toBe(undefined);
expect((error as Error).message).toBe('Failed to load walkthrough');
});
test('JSON that does not parse is reported without the parser wording', async () => {
nextResponse = new Response('{"walkthrough":', { headers: { 'Content-Type': 'application/json' } });
const error = await fetchWalkthrough('/repo', SOURCE).catch((caught: unknown) => caught);
expect(error).toBeInstanceOf(WalkthroughError);
expect((error as Error).message).toBe('The server returned a malformed walkthrough response');
});
});