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

288 lines
9.3 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import {
nativeIntegrationFixture,
assertSuccessfulZoom,
turnOfEventLoop,
} from '../testSupport/geminiIntegrationFixture.mjs';
const options = { concurrency: false, timeout: 5000 };
const resultFor = (h, id) =>
h.events.filter(
(event) => event.type === 'action-result' && event.callId === id,
);
const callFor = (h, id) =>
h.events.filter(
(event) => event.type === 'action-call' && event.callId === id,
);
test(
'native tool reaches actual common session and camera runner without a caption',
options,
async (t) => {
const h = nativeIntegrationFixture(t);
const ws = await h.start();
h.activity(ws); // Native activity is input framing, not transcript admission.
await h.tool('ordinary', ws);
assertSuccessfulZoom(h, ws, 'ordinary', 1000);
assert.equal(h.runnerCalls.length, 1);
assert.equal(h.runnerCalls[0].options.callId, 'ordinary');
assert.equal(h.runnerCalls[0].options.signal.aborted, false);
assert.equal(h.runnerCalls[0].options.isCurrent(), true);
h.complete(ws);
assert.equal(h.controller.narration.state().tools, 0);
assert.equal(h.controller.narration.state().pendingTimers, 0);
},
);
for (const cancellation of ['stop', 'native interruption']) {
test(
`${cancellation} discards a stale completed action result; successor changes camera once`,
options,
async (t) => {
const h = nativeIntegrationFixture(t);
const oldSocket = await h.start();
h.activity(oldSocket);
const gate = h.holdResult('old');
const oldDispatch = h.tool('old', oldSocket);
const committedResult = await gate.ready;
assert.equal(committedResult.ok, true);
assert.equal(
h.state.height,
750,
'the legitimate first action committed before cancellation',
);
assert.equal(callFor(h, 'old').length, 1);
assert.deepEqual(resultFor(h, 'old'), []);
assert.deepEqual(h.outputs(oldSocket), []);
if (cancellation === 'stop') h.session.stop();
else
oldSocket.message({
serverContent: { interrupted: true, turnComplete: true },
});
assert.equal(h.runnerCalls[0].options.signal.aborted, true);
assert.equal(h.runnerCalls[0].options.isCurrent(), false);
await oldDispatch; // Cancellation must settle before the held result returns.
gate.release();
await turnOfEventLoop();
assert.equal(
h.state.height,
750,
'settling a cancelled result adds no camera mutation',
);
assert.equal(h.state.zooms.length, 1);
assert.deepEqual(resultFor(h, 'old'), []);
assert.deepEqual(h.outputs(oldSocket), []);
if (cancellation === 'native interruption')
assert.ok(h.events.some((event) => event.type === 'interruption'));
const successorSocket =
cancellation === 'stop' ? await h.start() : oldSocket;
h.activity(successorSocket);
await h.tool('successor', successorSocket);
assertSuccessfulZoom(h, successorSocket, 'successor', 750);
assert.equal(h.state.zooms.length, 2);
assert.equal(h.runnerCalls.length, 2);
assert.equal(
h.outputs(successorSocket).some((output) => output.id === 'old'),
false,
);
h.complete(successorSocket);
},
);
}
for (const phase of ['common connecting', 'adapter connecting']) {
test(
`Stop from ${phase} observer prevents acquisition and permits fresh Start`,
options,
async (t) => {
const h = nativeIntegrationFixture(t);
let stopped = false;
const remove = h.session.subscribe((event) => {
const matchingDetail =
phase === 'common connecting'
? event.detail === undefined
: typeof event.detail === 'string' && event.detail.length > 0;
if (
!stopped &&
event.type === 'state' &&
event.state === 'connecting' &&
matchingDetail
) {
stopped = true;
h.session.stop();
}
});
await h.session.start();
remove();
assert.equal(
stopped,
true,
'the chosen production observer boundary was exercised',
);
assert.deepEqual(h.acquisitions, {
microphone: 0,
audio: 0,
token: 0,
socket: 0,
});
assert.equal(h.session.state, 'idle');
assert.equal(h.controller.narration.state().tools, 0);
assert.equal(h.controller.narration.state().pendingTimers, 0);
assert.equal(h.clock.timers.size, 0);
const ws = await h.start();
h.activity(ws);
await h.tool('fresh-after-start-cancel', ws);
assertSuccessfulZoom(h, ws, 'fresh-after-start-cancel', 1000);
h.complete(ws);
},
);
}
test(
'Stop from common action-call observer prevents runner and leaves no unhandled rejection',
options,
async (t) => {
const h = nativeIntegrationFixture(t);
const ws = await h.start();
const remove = h.session.subscribe((event) => {
if (event.type === 'action-call' && event.callId === 'cancel-at-call')
h.session.stop();
});
h.activity(ws);
await h.tool('cancel-at-call', ws);
// Do not install a swallowing process rejection listener. node:test records
// an unhandled rejection as a failure; these checkpoints let it surface here.
await turnOfEventLoop();
await turnOfEventLoop();
remove();
assert.equal(callFor(h, 'cancel-at-call').length, 1);
assert.deepEqual(resultFor(h, 'cancel-at-call'), []);
assert.equal(h.runnerCalls.length, 0);
assert.equal(h.state.height, 1000);
assert.deepEqual(h.outputs(ws), []);
assert.equal(h.controller.narration.state().tools, 0);
assert.equal(h.controller.narration.state().pendingTimers, 0);
assert.equal(h.clock.timers.size, 0);
const successor = await h.start();
h.activity(successor);
await h.tool('after-call-cancel', successor);
assertSuccessfulZoom(h, successor, 'after-call-cancel', 1000);
h.complete(successor);
},
);
test(
'Stop from executing observer cannot recreate narration or dispatch the runner',
options,
async (t) => {
const h = nativeIntegrationFixture(t);
const ws = await h.start();
let stopped = false;
const remove = h.session.subscribe((event) => {
if (!stopped && event.type === 'state' && event.state === 'executing') {
stopped = true;
h.session.stop();
}
});
h.activity(ws);
await h.tool('cancel-at-executing', ws);
remove();
assert.equal(stopped, true);
assert.equal(h.runnerCalls.length, 0);
assert.deepEqual(callFor(h, 'cancel-at-executing'), []);
assert.deepEqual(resultFor(h, 'cancel-at-executing'), []);
assert.equal(h.state.height, 1000);
assert.deepEqual(h.outputs(ws), []);
assert.equal(h.controller.narration.state().tools, 0);
assert.equal(h.controller.narration.state().pendingTimers, 0);
assert.equal(h.clock.timers.size, 0);
assert.equal(ws.closed, true);
assert.ok(h.audios.every((audio) => audio.closed));
assert.ok(h.tracks.every((track) => track.readyState === 'ended'));
const before = {
events: h.events.length,
wire: ws.sent.length,
earcon: h.state.earconStarts,
};
h.clock.advance(60_000);
assert.deepEqual(
{
events: h.events.length,
wire: ws.sent.length,
earcon: h.state.earconStarts,
},
before,
);
const successor = await h.start();
h.activity(successor);
await h.tool('after-executing-cancel', successor);
assertSuccessfulZoom(h, successor, 'after-executing-cancel', 1000);
h.complete(successor);
},
);
test(
'native idle observer restart remains connecting and public Stop cancels its pending permission',
options,
async (t) => {
const h = nativeIntegrationFixture(t);
await h.start();
let grant;
const permission = new Promise((resolve) => {
grant = resolve;
});
h.controller.getUserMedia = () => permission;
const states = [];
let restarted = false;
let startup;
const remove = h.session.subscribe((event) => {
if (event.type === 'state') states.push(event.state);
if (!restarted && event.type === 'state' && event.state === 'idle') {
restarted = true;
startup = h.session.start();
}
});
h.session.stop();
remove();
assert.equal(restarted, true);
assert.equal(states.filter((state) => state === 'idle').length, 1);
assert.equal(h.session.state, 'connecting');
assert.equal(h.controller.status, 'connecting');
assert.equal(
h.session.isActive(),
true,
'the common button/facade must offer Stop',
);
h.session.stop();
assert.equal(h.session.state, 'idle');
assert.equal(h.controller.status, 'idle');
const lateTrack = {
readyState: 'live',
stopCalls: 0,
stop() {
this.stopCalls++;
this.readyState = 'ended';
},
};
h.tracks.push(lateTrack);
grant({ getTracks: () => [lateTrack], getAudioTracks: () => [lateTrack] });
await startup;
await turnOfEventLoop();
assert.equal(lateTrack.stopCalls, 1);
assert.equal(
h.requests.length,
1,
'cancelled restart must not mint another token',
);
assert.equal(
h.sockets.length,
1,
'cancelled restart must not open another socket',
);
assert.equal(h.session.state, 'idle');
},
);