mirror of
https://github.com/jambonz/jambonz-feature-server.git
synced 2026-10-04 02:04:12 +00:00
_onTtsStreamingResume asked for "streaming-event" while stream_open,
stream_paused, stream_closed and user_interruption all ask for
"/streaming-event".
That one missing slash decides whether the event reaches an HTTP application at
all. HttpRequestor joins a hook path to the app's baseUrl only when it is
relative, and relative means starting with a slash:
_isRelativeUrl(u) { return typeof u === 'string' && u.startsWith('/'); }
const absUrl = this._isRelativeUrl(url) ? `${this.baseUrl}${url}` : url;
"streaming-event" is neither relative nor absolute, so it is passed through
unjoined and never lands on the application. The call site catches and logs the
failure rather than raising it, so an application simply sees stream_paused with
no matching stream_resumed and nothing anywhere says why.
Present since the verb was introduced in #994. Found by reading the file, so
there is no issue to close.
Tests: the two new cases fail against the unpatched line and pass with it.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
65 lines
2.3 KiB
JavaScript
65 lines
2.3 KiB
JavaScript
const test = require('node:test');
|
|
const assert = require('node:assert');
|
|
|
|
/* call-session decrypts credentials at require time, so it needs a secret present */
|
|
process.env.ENCRYPTION_SECRET = process.env.ENCRYPTION_SECRET || 'foobar';
|
|
process.env.JAMBONES_LOGLEVEL = process.env.JAMBONES_LOGLEVEL || 'error';
|
|
|
|
const CallSession = require('../../lib/session/call-session');
|
|
|
|
/* Only the requestor and logger are touched by these handlers, so the rest of a
|
|
CallSession is deliberately left unbuilt. */
|
|
const makeSession = () => {
|
|
const sent = [];
|
|
const session = Object.create(CallSession.prototype);
|
|
|
|
Object.assign(session, {
|
|
application: {
|
|
requestor: {
|
|
request: async (type, hook, payload) => {
|
|
sent.push({type, hook, payload});
|
|
}
|
|
}
|
|
},
|
|
logger: {info: () => {}, debug: () => {}, error: () => {}}
|
|
});
|
|
|
|
return {session, sent};
|
|
};
|
|
|
|
test('the stream_resumed event is sent to the same hook path as every other one', async () => {
|
|
/* A hook path only gets joined to the application's baseUrl when it starts with
|
|
a slash (HttpRequestor: `_isRelativeUrl(url) ? baseUrl + url : url`). Sending
|
|
"streaming-event" instead of "/streaming-event" is neither relative nor
|
|
absolute, so it never reaches an HTTP application at all — and the failure is
|
|
swallowed by the .catch on the call site, so nothing surfaces. */
|
|
const {session, sent} = makeSession();
|
|
|
|
session._onTtsStreamingResume();
|
|
await new Promise((resolve) => setImmediate(resolve));
|
|
|
|
assert.strictEqual(sent.length, 1);
|
|
assert.strictEqual(sent[0].hook, '/streaming-event',
|
|
'stream_resumed must use an absolute-from-base hook path');
|
|
assert.strictEqual(sent[0].payload.event_type, 'stream_resumed');
|
|
});
|
|
|
|
test('pause and resume agree on the hook path', async () => {
|
|
/* These two are a pair; if they ever diverge again, an application would get
|
|
one half of the pause/resume cycle and silently lose the other. */
|
|
const {session, sent} = makeSession();
|
|
|
|
session._onTtsStreamingPause();
|
|
session._onTtsStreamingResume();
|
|
await new Promise((resolve) => setImmediate(resolve));
|
|
|
|
assert.deepStrictEqual(
|
|
sent.map(({hook}) => hook),
|
|
['/streaming-event', '/streaming-event']
|
|
);
|
|
assert.deepStrictEqual(
|
|
sent.map(({payload}) => payload.event_type),
|
|
['stream_paused', 'stream_resumed']
|
|
);
|
|
});
|