mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
Merge upstream/main into pr/panel-resize
Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
commit
11603729dc
234 files changed
+45360
-944
No files matched your search
@@ -1,5 +1,6 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { createServer } from 'node:http';
|
||||
import { Readable } from 'node:stream';
|
||||
import {
|
||||
mkdtempSync,
|
||||
@@ -279,7 +280,157 @@ test('Realtime service configuration selects compatible endpoint/model without f
|
||||
url: '/?tier=arbitrary-model&model=other',
|
||||
});
|
||||
assert.equal(response.status, 200);
|
||||
assert.deepEqual(response.json(), { value: 'short-lived-fixture' });
|
||||
assert.equal(response.headers['x-gev-voice-model'], 'configured-model');
|
||||
assert.equal(response.headers['cache-control'], 'no-store');
|
||||
assert.doesNotMatch(response.body, /server-fixture|voice\.example/);
|
||||
});
|
||||
|
||||
test('OpenAI routes answer generically when the upstream or the request fails', async (t) => {
|
||||
env(t, 'OPENAI_API_KEY', 'fixture-upstream-secret');
|
||||
env(t, 'GEV_RATELIMIT_OPENAI_PER_MIN', undefined);
|
||||
const leak =
|
||||
'fixture-upstream-secret req_fixture_1234 org-fixture quota exhausted';
|
||||
|
||||
// `data.error.message` is OpenAI's own wording — request ids, organization
|
||||
// hints, quota phrasing — and was relayed verbatim whenever upstream was not ok.
|
||||
t.mock.method(globalThis, 'fetch', async () =>
|
||||
Response.json({ error: { message: leak } }, { status: 429 }),
|
||||
);
|
||||
const summary = await request(
|
||||
install(openAiRealtimeProxy()).get('/api/openai/hud-summary'),
|
||||
{ method: 'POST', body: JSON.stringify({ context: {} }) },
|
||||
);
|
||||
assert.equal(summary.json().error, 'OpenAI HUD summary request failed');
|
||||
assert.equal(summary.body.includes('req_fixture_1234'), false);
|
||||
assert.equal(summary.body.includes('fixture-upstream-secret'), false);
|
||||
|
||||
// An HTTP error from the client-secret endpoint carries the same upstream
|
||||
// detail, while a successful response must still pass the ephemeral secret.
|
||||
t.mock.restoreAll();
|
||||
t.mock.method(globalThis, 'fetch', async () =>
|
||||
Response.json({ error: { message: leak } }, { status: 429 }),
|
||||
);
|
||||
const rejectedToken = await request(
|
||||
install(openAiRealtimeProxy()).get('/api/realtime/token'),
|
||||
);
|
||||
assert.equal(rejectedToken.status, 429);
|
||||
assert.equal(
|
||||
rejectedToken.headers['content-type'],
|
||||
'application/json; charset=utf-8',
|
||||
);
|
||||
assert.deepEqual(rejectedToken.json(), {
|
||||
error: 'Failed to create Realtime token',
|
||||
});
|
||||
assert.equal(rejectedToken.body.includes('req_fixture_1234'), false);
|
||||
assert.equal(rejectedToken.body.includes('fixture-upstream-secret'), false);
|
||||
|
||||
// A network fault surfaced a resolver message naming the upstream host.
|
||||
t.mock.restoreAll();
|
||||
t.mock.method(globalThis, 'fetch', async () => {
|
||||
throw Error(`getaddrinfo ENOTFOUND api.openai.com ${leak}`);
|
||||
});
|
||||
const token = await request(
|
||||
install(openAiRealtimeProxy()).get('/api/realtime/token'),
|
||||
);
|
||||
assert.equal(token.status, 502);
|
||||
assert.deepEqual(token.json(), { error: 'Failed to create Realtime token' });
|
||||
assert.equal(token.body.includes('api.openai.com'), false);
|
||||
assert.equal(token.body.includes('fixture-upstream-secret'), false);
|
||||
});
|
||||
|
||||
test('the debug-log sink stays bounded, rate limited, and quiet about failures', async (t) => {
|
||||
const sourceRoot = root(t);
|
||||
const handler = install(openAiRealtimeProxy({ sourceRoot })).get(
|
||||
'/api/realtime/debug-log',
|
||||
);
|
||||
const file = path.join(sourceRoot, '.gev-logs/realtime-conversations.jsonl');
|
||||
const write = (record) =>
|
||||
request(handler, { method: 'POST', body: JSON.stringify(record) });
|
||||
|
||||
// A malformed record is the caller's fault and a 400; neither answer carries
|
||||
// the error text, which for a write failure is an errno and an absolute path.
|
||||
const malformed = await request(handler, { method: 'POST', body: '{nope' });
|
||||
assert.equal(malformed.status, 400);
|
||||
assert.deepEqual(malformed.json(), {
|
||||
error: 'Failed to write Realtime debug log',
|
||||
});
|
||||
|
||||
// The limiter is always on, unlike the opt-in one the cost-bearing routes
|
||||
// share: 120/min per IP, far above what a voice session writes.
|
||||
let limited = null;
|
||||
let accepted = 0;
|
||||
for (let n = 0; n < 130 && !limited; n += 1) {
|
||||
const response = await write({ n });
|
||||
if (response.status === 429) limited = response;
|
||||
else if (response.status === 204) accepted += 1;
|
||||
}
|
||||
assert.ok(limited, 'the sink refuses a caller past its per-minute ceiling');
|
||||
assert.equal(limited.headers['retry-after'], '60');
|
||||
assert.deepEqual(limited.json(), { error: 'Rate limit exceeded' });
|
||||
// The limiter counts requests rather than successful writes, so the malformed
|
||||
// record above already spent one of the 120 slots.
|
||||
assert.equal(accepted, 119);
|
||||
|
||||
// Every accepted record is on disk and parses: the queue serializes appends,
|
||||
// so none was lost or truncated by the ones beside it.
|
||||
const lines = readFileSync(file, 'utf8').split('\n').filter(Boolean);
|
||||
assert.equal(lines.length, accepted);
|
||||
for (const line of lines) assert.doesNotThrow(() => JSON.parse(line));
|
||||
assert.ok(lines.every((line) => JSON.parse(line).loggedAt));
|
||||
});
|
||||
|
||||
test('an oversized debug-log request receives the fixed error response', async (t) => {
|
||||
const handler = install(openAiRealtimeProxy({ sourceRoot: root(t) })).get(
|
||||
'/api/realtime/debug-log',
|
||||
);
|
||||
const server = createServer(handler);
|
||||
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
|
||||
t.after(() => new Promise((resolve) => server.close(resolve)));
|
||||
|
||||
const response = await fetch(
|
||||
`http://127.0.0.1:${server.address().port}/api/realtime/debug-log`,
|
||||
{ method: 'POST', body: 'x'.repeat(8 * 1024 * 1024 + 1) },
|
||||
);
|
||||
assert.equal(response.status, 400);
|
||||
assert.deepEqual(await response.json(), {
|
||||
error: 'Failed to write Realtime debug log',
|
||||
});
|
||||
});
|
||||
|
||||
test('the debug log rotates instead of growing without bound', async (t) => {
|
||||
const sourceRoot = root(t);
|
||||
const handler = install(openAiRealtimeProxy({ sourceRoot })).get(
|
||||
'/api/realtime/debug-log',
|
||||
);
|
||||
const file = path.join(sourceRoot, '.gev-logs/realtime-conversations.jsonl');
|
||||
|
||||
// 8 MB bounds one request body; nothing bounded the file until now, so a
|
||||
// single page could grow it for as long as the dev server ran. Each record
|
||||
// here is ~1 MB, well inside the body cap.
|
||||
const pad = 'x'.repeat(1024 * 1024);
|
||||
// Seventy-two records force two rotations. The second one replaces an
|
||||
// existing `.1`, which requires an explicit removal on Windows.
|
||||
for (let n = 0; n < 72; n += 1) {
|
||||
assert.equal(
|
||||
(
|
||||
await request(handler, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ n, pad }),
|
||||
})
|
||||
).status,
|
||||
204,
|
||||
);
|
||||
}
|
||||
|
||||
const live = statSync(file).size;
|
||||
const previous = statSync(`${file}.1`).size;
|
||||
assert.ok(live <= 32 * 1024 * 1024, `live log under the ceiling (${live})`);
|
||||
assert.ok(
|
||||
live + previous <= 64 * 1024 * 1024,
|
||||
'both generations together stay within twice the ceiling',
|
||||
);
|
||||
// Unrotated, these records would be ~75 MB in one file.
|
||||
assert.ok(live + previous < 64 * 1024 * 1024);
|
||||
assert.ok(!existsSync(`${file}.2`), 'exactly one generation is retained');
|
||||
});
|
||||
@@ -149,3 +149,45 @@ for (const hook of ['configureServer', 'configurePreviewServer']) {
|
||||
assert.equal(cancelled, true);
|
||||
});
|
||||
}
|
||||
|
||||
test('a failed CCTV media fetch reports a fixed health message, not the error text', async (t) => {
|
||||
isolate(t);
|
||||
const root = mkdtempSync(path.join(tmpdir(), 'gev-cctv-'));
|
||||
t.after(() => rmSync(root, { recursive: true, force: true }));
|
||||
mkdirSync(path.join(root, 'config'));
|
||||
writeFileSync(
|
||||
path.join(root, 'config/cctv_sources.austin.json'),
|
||||
JSON.stringify([
|
||||
{
|
||||
id: 'leaky',
|
||||
name: 'Leaky camera',
|
||||
lat: 30.27,
|
||||
lon: -97.74,
|
||||
feedType: 'video',
|
||||
url: 'https://cams.fixture.invalid/leaky.m3u8',
|
||||
},
|
||||
]),
|
||||
);
|
||||
const leak =
|
||||
'connect ETIMEDOUT 203.0.113.9:443 C:\fixture\secret-path\cams.json';
|
||||
t.mock.restoreAll();
|
||||
t.mock.method(globalThis, 'fetch', () => {
|
||||
throw Error(leak);
|
||||
});
|
||||
|
||||
const call = install(cctvProxy({ sourceRoot: root }));
|
||||
const media = await call('/media/leaky');
|
||||
assert.equal(media.status, 502);
|
||||
assert.equal(media.body.includes('203.0.113.9'), false);
|
||||
|
||||
// GET /health is the second door: src/layers/cctv/frames.js renders each
|
||||
// entry's `message` as the camera's status label, so a raw errno stored here
|
||||
// reaches the screen even though the response above is sanitized.
|
||||
const camera = JSON.parse((await call('/health')).body).cameras.find(
|
||||
(entry) => entry.id === 'leaky',
|
||||
);
|
||||
assert.equal(camera.status, 'degraded');
|
||||
assert.equal(camera.message, 'Media fetch failed');
|
||||
assert.equal(camera.message.includes('ETIMEDOUT'), false);
|
||||
assert.equal(camera.message.includes('secret-path'), false);
|
||||
});
|
||||
@@ -675,7 +675,7 @@ test('public transit documentation uses courtesy attribution and the approved gr
|
||||
);
|
||||
assert.ok(
|
||||
readme.includes(
|
||||
'Fifteen layers and map sources. **Thirteen have a keyless path.**',
|
||||
'Nineteen layers and map sources. **Seventeen have a keyless path.**',
|
||||
),
|
||||
);
|
||||
assert.ok(
|
||||
|
||||
Reference in new issue
Block a user