Files
fluxer-desktop-patched/fluxer_desktop/native/linux-audio-capture/index.test.mjs
T
brenden 682afacd30 Add native self-hosted instance connection to fluxer_desktop
Trimmed monorepo checkout (fluxer_desktop + packages/voice_engine_v2 +
tools/ci) with a "Connect to a Different Server" menu item and popout
that lets the desktop app switch to any self-hosted Fluxer instance,
plus fixes for well-known discovery on single-domain self-hosted
deployments and a false-positive ERR_ABORTED on same-origin client
redirects during the switch. Defaults to chat.fluxr.chat and uses an
isolated userData directory from the official build.
2026-07-01 18:22:43 -04:00

168 lines
4.6 KiB
JavaScript

// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import {createRequire} from 'node:module';
import {test} from 'node:test';
const requireSrc = createRequire(import.meta.url);
const modulePath = requireSrc.resolve('./index.js');
function freshModule() {
delete requireSrc.cache[modulePath];
return requireSrc('./index.js');
}
function makeFakeBinding() {
const captures = [];
let startResult = true;
class FakeDirectAudioCapture {
constructor() {
this.started = false;
this.stopCount = 0;
this.startRules = [];
this.lifecycleCallback = undefined;
captures.push(this);
}
setLifecycleCallback(callback) {
this.lifecycleCallback = callback;
}
start(rule) {
if (!startResult) return false;
this.started = true;
this.startRules.push(rule);
return true;
}
setRule(rule) {
this.startRules.push(rule);
return true;
}
read() {
return null;
}
stop() {
this.started = false;
this.stopCount += 1;
}
}
function FakeAudioMixRuntimeHandle() {}
FakeAudioMixRuntimeHandle.boundToDirectCapture = () => {
throw new Error('ProcessLoopback must not tick a discard-only mix runtime');
};
return {
binding: {
AudioBridge: class {},
AudioMixRuntimeHandle: FakeAudioMixRuntimeHandle,
DirectAudioCapture: FakeDirectAudioCapture,
pipeWireAvailable: () => true,
audioBackend: () => 'pipewire',
},
captures,
setStartResult(value) {
startResult = value;
},
};
}
let loadError = null;
try {
freshModule();
} catch (error) {
loadError = error;
}
test('ProcessLoopback reuses idle direct captures after stop', {skip: loadError?.message}, async () => {
const mod = freshModule();
const {binding, captures} = makeFakeBinding();
mod.__setBindingForTests(binding);
const rule = {linuxRule: {include: [{'application.name': 'Firefox'}]}};
const first = new mod.ProcessLoopback(rule);
first.start();
await first.stop();
assert.equal(captures.length, 1);
assert.equal(captures[0].stopCount, 1);
assert.equal(mod.__getIdleDirectCaptureCountForTests(), 1);
const second = new mod.ProcessLoopback(rule);
second.start();
await second.stop();
assert.equal(captures.length, 1);
assert.equal(captures[0].stopCount, 2);
assert.equal(captures[0].startRules.length, 2);
assert.equal(mod.__getIdleDirectCaptureCountForTests(), 1);
});
test('ProcessLoopback does not pool failed direct captures', {skip: loadError?.message}, async () => {
const mod = freshModule();
const {binding, captures, setStartResult} = makeFakeBinding();
mod.__setBindingForTests(binding);
const rule = {linuxRule: {include: [{'application.name': 'Firefox'}]}};
setStartResult(false);
const first = new mod.ProcessLoopback(rule);
assert.throws(() => first.start(), /failed to start Linux direct audio capture/);
await first.stop();
assert.equal(captures.length, 1);
assert.equal(mod.__getIdleDirectCaptureCountForTests(), 0);
setStartResult(true);
const second = new mod.ProcessLoopback(rule);
second.start();
await second.stop();
assert.equal(captures.length, 2);
assert.equal(mod.__getIdleDirectCaptureCountForTests(), 1);
});
test('ProcessLoopback installs native lifecycle callback on direct capture', {skip: loadError?.message}, () => {
const mod = freshModule();
const {binding, captures} = makeFakeBinding();
mod.__setBindingForTests(binding);
const loopback = new mod.ProcessLoopback({linuxRule: {include: [{'application.name': 'Firefox'}]}});
assert.equal(captures.length, 1);
assert.equal(typeof captures[0].lifecycleCallback, 'function');
assert.equal(loopback.listenerCount('closed'), 0);
});
test('ProcessLoopback does not create a discard-only audio mix runtime', {skip: loadError?.message}, async () => {
const mod = freshModule();
const {binding} = makeFakeBinding();
mod.__setBindingForTests(binding);
const loopback = new mod.ProcessLoopback({linuxRule: {include: [{'application.name': 'Firefox'}]}});
loopback.start();
await new Promise((resolve) => setImmediate(resolve));
await loopback.stop();
});
test('ProcessLoopback closes once when native lifecycle closes while idle', {skip: loadError?.message}, async () => {
const mod = freshModule();
const {binding, captures} = makeFakeBinding();
mod.__setBindingForTests(binding);
const loopback = new mod.ProcessLoopback({linuxRule: {include: [{'application.name': 'Firefox'}]}});
let closed = 0;
loopback.on('closed', () => {
closed += 1;
});
captures[0].lifecycleCallback('closed-clean', 'daemon disconnected');
await new Promise((resolve) => setImmediate(resolve));
await loopback.stop();
assert.equal(closed, 1);
assert.equal(captures[0].stopCount, 1);
});