Files
gods-eye-view/src/voice/realtimeRadio.test.mjs
T

121 lines
4.1 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { GevRealtimeController } from './realtimeController.js';
const tick = () => new Promise((resolve) => setImmediate(resolve));
const playback = (id) => ({
ok: true,
action: 'control_radio',
radioAction: 'play',
radioPlaybackRequested: true,
id,
});
function fixture(t) {
const attempts = [],
stopped = [],
corrections = [],
states = [];
const controller = new GevRealtimeController({
ui: {
root: {
dataset: {},
classList: { remove() {} },
querySelectorAll: () => [],
remove() {},
},
status: {},
detail: {},
errorDetail: {},
},
runner: async () => ({ ok: false }),
debugSink: null,
onSessionEvent: (event) => states.push(event),
radioLayer: {
setVoiceDucked() {},
playForVoice: ({ attemptId }) =>
new Promise((resolve, reject) =>
attempts.push({ attemptId, resolve, reject }),
),
stopPlayback: (options) => stopped.push(options),
},
});
controller.queueResponseCreate = (message) => corrections.push(message);
controller.dc = { readyState: 'open', send() {}, close() {} };
t.after(() => controller.stop({ removeUi: true }));
return { controller, attempts, stopped, corrections, states };
}
test('default OpenAI reservation silences rejected frozen preflight and resumes retained result after failure', async (t) => {
const h = fixture(t),
c = h.controller;
const a = playback('A');
c.pendingRadioPlaybackResult = a;
const pending = c.startPendingRadioHandoff();
const reservation = c.reserveRadioToolHandoff();
assert.equal(c.pendingRadioPlaybackResult, a);
assert.equal(c.radioHandoffInFlight, false);
h.attempts[0].reject(new Error('obsolete frozen preflight'));
await pending;
assert.deepEqual(h.corrections, []);
assert.equal(
h.states.some((event) => event.detail === 'Radio did not start'),
false,
);
c.settleRadioToolHandoffReservation(reservation, { commit: false });
await tick();
assert.equal(h.attempts.length, 2);
assert.equal(c.radioHandoffInFlightResult, a);
assert.notEqual(h.attempts[0].attemptId, h.attempts[1].attemptId);
h.attempts[1].reject(new Error('current preflight failed'));
await tick();
assert.equal(h.corrections.length, 1);
assert.equal(c.status, 'listening');
});
test('default OpenAI group cancellation silences rejected preflight without blocking the next failure correction', async (t) => {
const h = fixture(t),
c = h.controller;
c.pendingRadioPlaybackResult = playback('A');
const old = c.startPendingRadioHandoff();
c.cancelRadioHandoff();
h.attempts[0].reject(new Error('cancelled preflight'));
await old;
assert.deepEqual(h.corrections, []);
c.pendingRadioPlaybackResult = playback('B');
const current = c.startPendingRadioHandoff();
h.attempts[1].resolve(false);
await current;
assert.equal(h.corrections.length, 1);
});
test('per-result cancellation retains sibling result and epoch and ignores late failure or duplicate cancellation', async (t) => {
const h = fixture(t),
c = h.controller;
const a = playback('A'),
b = playback('B');
c.pendingRadioPlaybackResult = a;
const old = c.startPendingRadioHandoff();
const siblingEpoch = c.radioHandoffEpoch;
c.pendingRadioPlaybackResult = b;
c._radio.cancelPlaybackResult(a);
c._radio.cancelPlaybackResult(a);
c._radio.cancelPlaybackResult(playback('unknown'));
assert.equal(c.radioHandoffEpoch, siblingEpoch);
assert.equal(c.pendingRadioPlaybackResult, b);
assert.equal(h.stopped.length, 1);
h.attempts[0].reject(new Error('late cancelled preflight'));
await old;
assert.deepEqual(h.corrections, []);
const current = c.startPendingRadioHandoff();
const reservation = c.reserveRadioToolHandoff();
c._radio.cancelPlaybackResult(b);
assert.equal(c._radio.radioToolHandoffReservations.has(reservation), true);
h.attempts[1].resolve(false);
await current;
assert.deepEqual(h.corrections, []);
c.settleRadioToolHandoffReservation(reservation, { commit: false });
await tick();
assert.equal(h.attempts.length, 2, 'a cancelled result cannot be resumed');
});