2026-10-30 17:40:13 +08:00
// node --test scripts/fir-regression.test.mjs — no hardware requests.
import { test , afterEach } from 'node:test' ;
import assert from 'node:assert/strict' ;
import { build } from 'esbuild' ;
import { fileURLToPath } from 'node:url' ;
async function load ( file , dev = false ) {
const result = await build ({ entryPoints : [ fileURLToPath ( new URL ( '../client/src/lib/' + file , import . meta . url ))], bundle : true , write : false , format : 'esm' , platform : 'node' , define : { 'import.meta.env.DEV' : String ( dev )}, alias : { '@' : fileURLToPath ( new URL ( '../client/src' , import . meta . url ))}});
return import ( 'data:text/javascript;base64,' + Buffer . from ( result . outputFiles [ 0 ]. text ). toString ( 'base64' ));
}
// Bundle together like the app, sharing the sample-rate metadata attached to buffers.
const shared = await build ({ stdin : { contents : 'export * from "./fir"; export { LuxsinAPI } from "./luxsinApi";' , resolveDir : fileURLToPath ( new URL ( '../client/src/lib' , import . meta . url )), loader : 'ts' }, bundle : true , write : false , format : 'esm' , platform : 'node' , define : { 'import.meta.env.DEV' : 'false' }, alias : { '@' : fileURLToPath ( new URL ( '../client/src' , import . meta . url ))}});
const { encodeFir , validateFirBinary , applyFir , readFirJson , readFirBinary , firRoomName , encodeFirWav , decodeFirWav , encodeDeviceFirWav , LuxsinAPI } = await import ( 'data:text/javascript;base64,' + Buffer . from ( shared . outputFiles [ 0 ]. text ). toString ( 'base64' ));
const originalFetch = globalThis . fetch ;
afterEach (()=>{ globalThis . fetch = originalFetch ;});
const coefficients = ()=>[ 0.25 , - 0.5 ,... Array ( 8189 ). fill ( 0 )];
test ( 'float32 LE conversion keeps 8191 taps and exact byte order' ,()=>{
const b = encodeFir ( coefficients ()); assert . equal ( b . byteLength , 32764 );
assert . deepEqual ([... new Uint8Array ( b ). slice ( 0 , 8 )],[ 0 , 0 , 128 , 62 , 0 , 0 , 0 , 191 ]);
assert . equal ( validateFirBinary ( b ), 8191 );
assert . equal ( encodeFir ( Array ( 8192 ). fill ( 0 )). byteLength , 32768 );
});
test ( 'reject bad lengths, NaN, infinity, float32 overflow and non-numbers' ,()=>{
for ( const v of [ NaN , Infinity , 1e100 , '1' ]) { const a = coefficients (); a [ 0 ] = v ; assert . throws (()=> encodeFir ( a ));}
assert . throws (()=> encodeFir ([ 1 ])); assert . throws (()=> validateFirBinary ( new ArrayBuffer ( 8 )));
const bad = encodeFir ( coefficients ()); new DataView ( bad ). setFloat32 ( 0 , NaN , true ); assert . throws (()=> validateFirBinary ( bad ));
});
test ( 'actual API sends only raw left/right binary, never FIR settings' , async ()=>{
const requests = []; globalThis . fetch = async ( url , init )=>{ requests . push ({ url , init }); return new Response ( 'OK' );};
const l = encodeFir ( coefficients ()), r = encodeFir ( Array ( 8191 ). fill ( - 0.125 ));
await applyFir ( new LuxsinAPI ( '192.168.9.127' ), l , r );
assert . deepEqual ( requests . map ( r => r . url ),[ 'http://192.168.9.127/dev/info.cgi?action=fir&channel=left' , 'http://192.168.9.127/dev/info.cgi?action=fir&channel=right' ]);
2026-11-02 15:48:32 +08:00
for ( const [ index , b ] of [[ 0 , l ],[ 1 , r ]]) { assert . equal ( requests [ index ]. init . method , 'POST' ); assert . equal ( new Headers ( requests [ index ]. init . headers ). has ( 'Content-Type' ), false ); assert . deepEqual ( new Uint8Array ( decodeFirWav ( requests [ index ]. init . body )), new Uint8Array ( decodeFirWav ( encodeDeviceFirWav ( b ))));}
2026-10-30 17:40:13 +08:00
});
test ( 'manual 8192-tap bytes preserved, enable/disable setting contract' , async ()=>{
const requests = []; globalThis . fetch = async ( url , init )=>{ requests . push ({ url , init }); return new Response ( 'OK' );};
const api = new LuxsinAPI ( '10.0.0.8' ); const b = encodeFir ( Array ( 8192 ). fill ( 0.2 ));
await applyFir ( api , b , b ); await api . setFirEnable ( true );
assert . deepEqual ( decodeFirWav ( requests [ 0 ]. init . body ), b ); assert . deepEqual ( decodeFirWav ( requests [ 1 ]. init . body ), b );
assert . equal ( requests [ 2 ]. url , 'http://10.0.0.8/dev/info.cgi?action=setting&fir_enable=1' );
});
test ( 'both channels validated before device side effects' , async ()=>{
let calls = 0 ; const device = { setFirEnable : async ()=>{ calls ++ ;}, uploadFirChannel : async ()=>{ calls ++ ;}};
await assert . rejects ( applyFir ( device , encodeFir ( coefficients ()), new ArrayBuffer ( 8 )));
await assert . rejects ( applyFir ( device , encodeFir ( coefficients ()), encodeFir ( Array ( 8192 ). fill ( 0 )))); assert . equal ( calls , 0 );
});
test ( 'missing firmware endpoint stops at left, no right upload or auto retry' , async ()=>{
const urls = []; globalThis . fetch = async url =>{ urls . push ( url ); return new Response ( '' ,{ status : 404 });};
await assert . rejects ( applyFir ( new LuxsinAPI ( '192.168.9.127' ), encodeFir ( coefficients ()), encodeFir ( coefficients ())), /incomplete/ );
assert . equal ( urls . length , 1 );
});
test ( 'right failure reported, subsequent explicit retry sends both sides again' , async ()=>{
const urls = []; globalThis . fetch = async url =>{ urls . push ( url ); return new Response ( '' ,{ status : urls . length === 2 ? 500 : 200 });};
const api = new LuxsinAPI ( '192.168.9.127' ), b = encodeFir ( coefficients ());
await assert . rejects ( applyFir ( api , b , b ), /incomplete/ ); await applyFir ( api , b , b );
assert . equal ( urls . length , 4 ); assert . equal ( urls [ 0 ], urls [ 2 ]); assert . equal ( urls [ 1 ], urls [ 3 ]);
});
test ( 'network rejection surfaces without retry' , async ()=>{
let calls = 0 ; globalThis . fetch = async ()=>{ calls ++ ; throw new TypeError ( 'Failed to fetch' );};
await assert . rejects ( new LuxsinAPI ( '192.168.9.127' ). uploadFirChannel ( 'left' , encodeFir ( coefficients ())), /Failed to fetch/ ); assert . equal ( calls , 1 );
});
test ( 'library API errors do not masquerade as rooms' , async ()=>{
globalThis . fetch = async ()=> new Response ( '' ,{ status : 503 }); await assert . rejects ( readFirJson ( '/fir-library/rooms/defaults' ), /libraryError/ );
globalThis . fetch = async ()=> Response . json ([{ room_id : 'R35' }]); assert . deepEqual ( await readFirJson ( '/fir-library/rooms/defaults' ),[{ room_id : 'R35' }]);
});
test ( 'device request timeout aborts and never retries FIR binary' , async ()=>{
const realSetTimeout = globalThis . setTimeout , realClearTimeout = globalThis . clearTimeout ;
let calls = 0 ;
globalThis . setTimeout = ( fn )=>{ queueMicrotask ( fn ); return 123 ;}; globalThis . clearTimeout = ()=>{};
globalThis . fetch = async ( _url , init )=>{ calls ++ ; return new Promise (( _resolve , reject )=>{ init . signal . addEventListener ( 'abort' ,()=> reject ( new DOMException ( 'Aborted' , 'AbortError' )),{ once : true });});};
try { await assert . rejects ( new LuxsinAPI ( '192.168.9.127' ). uploadFirChannel ( 'left' , encodeFir ( coefficients ())), /timed out/ ); assert . equal ( calls , 1 );}
finally { globalThis . setTimeout = realSetTimeout ; globalThis . clearTimeout = realClearTimeout ;}
});
test ( 'room display hides IDs but preserves the selection identifier' ,()=>{
const room = { room_id : 'R35' , name : 'Studio C' , label : 'R35 · Studio C' , metadata : {}};
assert . equal ( firRoomName ( room ), 'Studio C' ); assert . equal ( room . room_id , 'R35' );
assert . equal ( firRoomName ({... room , name : undefined }), 'Studio C' );
assert . equal ( firRoomName ({... room , name : '' , metadata : { 'Room Name' : 'Room Calypso' }}), 'Room Calypso' );
});
test ( 'development browser API uses same-origin relay and correct right channel' , async ()=>{
const { LuxsinAPI : DevAPI } = await load ( 'luxsinApi.ts' , true ); const urls = [];
globalThis . fetch = async ( url )=>{ urls . push ( url ); return new Response ( 'OK' );};
await new DevAPI ( '192.168.9.133' ). uploadFirChannel ( 'right' , encodeFir ( coefficients ()));
assert . deepEqual ( urls ,[ '/fir-device/192.168.9.133/right' ]);
});
test ( 'successful upload never invokes FIR switch' , async ()=>{
const calls = []; const device = { setFirEnable : async ()=> assert . fail ( 'upload must not change switch' ), uploadFirChannel : async side => calls . push ( side )}; const b = encodeFir ( coefficients ());
await applyFir ( device , b , b ); assert . deepEqual ( calls ,[ 'left' , 'right' ]);
});
test ( 'partial failure never invokes FIR switch' , async ()=>{
const calls = []; const device = { setFirEnable : async ()=> assert . fail ( 'failure must not change switch' ), uploadFirChannel : async side =>{ calls . push ( side ); if ( side === 'right' ) throw new Error ( 'failed' );}}; const b = encodeFir ( coefficients ());
await assert . rejects ( applyFir ( device , b , b ), /incomplete/ ); assert . deepEqual ( calls ,[ 'left' , 'right' ]);
});
test ( 'device binary library export forwards exactly 32768 bytes without conversion' , async ()=>{
const original = encodeFir ( Array ( 8192 ). fill ( 0.25 ));
globalThis . fetch = async ()=> new Response ( encodeFirWav ( original ),{ headers : { 'Content-Type' : 'audio/wav' }});
assert . deepEqual ( new Uint8Array ( await readFirBinary ( '/fir-library/rooms/R35/device/left' )), new Uint8Array ( original ));
globalThis . fetch = async ()=> new Response ( encodeFirWav ( encodeFir ( coefficients ())). slice ( 0 , - 1 ));
await assert . rejects ( readFirBinary ( '/bad-length' ), /libraryFormat/ );
globalThis . fetch = async ()=> new Response ( '' ,{ status : 404 }); await assert . rejects ( readFirBinary ( '/missing' ), /libraryError/ );
});
test ( 'device FIR GET uses exact left/right endpoints without cache or setting writes' , async ()=>{
const bytes = encodeFir ( Array ( 8192 ). fill (. 125 )), requests = [];
globalThis . fetch = async ( url , init )=>{ requests . push ({ url , init }); return new Response ( bytes );};
const api = new LuxsinAPI ( '192.168.9.133' );
for ( const side of [ 'left' , 'right' ]) assert . deepEqual ( new Uint8Array ( await api . getFirChannel ( side )), new Uint8Array ( bytes ));
assert . deepEqual ( requests . map ( r => r . url ),[ 'http://192.168.9.133/dev/info.cgi?action=fir&channel=left' , 'http://192.168.9.133/dev/info.cgi?action=fir&channel=right' ]);
assert . ok ( requests . every ( r => r . init . cache === 'no-store' &&! r . init . body &&! r . init . method ));
globalThis . fetch = async ()=> new Response ( 'device error' ,{ status : 502 }); await assert . rejects ( api . getFirChannel ( 'right' ), /right: HTTP 502/ );
globalThis . fetch = async ()=> new Response ( '{}' ); await assert . rejects ( api . getFirChannel ( 'left' ), /binarySize/ );
});
test ( 'development FIR GET uses same-origin device relay' , async ()=>{
const { LuxsinAPI : DevAPI } = await load ( 'luxsinApi.ts' , true ), urls = [];
globalThis . fetch = async url =>{ urls . push ( url ); return new Response ( encodeFir ( Array ( 8192 ). fill ( 0 )));};
await new DevAPI ( '192.168.9.133' ). getFirChannel ( 'right' ); assert . deepEqual ( urls ,[ '/fir-device/192.168.9.133/right' ]);
});
test ( 'WAV round trip preserves samples and sample rate' , () => {
const raw = encodeFir ( coefficients ()); const wav = encodeFirWav ( raw , 44100 );
assert . equal ( wav . byteLength , raw . byteLength + 58 );
assert . equal ( new DataView ( wav ). getUint16 ( 20 , true ), 3 );
const decoded = decodeFirWav ( wav ); assert . deepEqual ( decoded , raw );
assert . deepEqual ( encodeFirWav ( decoded ), wav );
assert . throws (() => decodeFirWav ( wav . slice ( 0 , - 1 )));
});
test ( 'device GET accepts WAV for AI analysis' , async () => {
const raw = encodeFir ( Array ( 8192 ). fill ( 0.125 ));
globalThis . fetch = async () => new Response ( encodeFirWav ( raw ));
assert . deepEqual ( await new LuxsinAPI ( '192.168.9.133' ). getFirChannel ( 'left' ), raw );
});
test ( 'manual WAV sample rates must match before writes' , async () => {
const raw = encodeFir ( Array ( 8192 ). fill ( 0 )); let calls = 0 ;
await assert . rejects ( applyFir ({ uploadFirChannel : async () => { calls ++ ; }}, decodeFirWav ( encodeFirWav ( raw , 44100 )), decodeFirWav ( encodeFirWav ( raw , 48000 ))), /mismatch/ );
assert . equal ( calls , 0 );
});
test ( 'legacy 44.1 kHz room becomes device-compatible 48 kHz WAV' , async () => {
const raw = encodeFir ( Array ( 8192 ). fill ( 0.25 )); const requests = [];
globalThis . fetch = async ( url , init ) => {
if ( url . startsWith ( '/library/' )) return new Response ( raw , { headers : { 'Content-Type' : 'application/octet-stream' , 'X-FIR-Sample-Rate' : '44100' }});
requests . push ( init ); return new Response ( 'OK' );
};
const left = await readFirBinary ( '/library/left' ); const right = await readFirBinary ( '/library/right' );
await applyFir ( new LuxsinAPI ( '192.168.9.133' ), left , right );
assert . equal ( requests . length , 2 );
for ( const req of requests ) {
2026-11-02 15:48:32 +08:00
assert . equal ( new Headers ( req . headers ). has ( 'Content-Type' ), false );
2026-10-30 17:40:13 +08:00
assert . equal ( new DataView ( req . body ). getUint32 ( 24 , true ), 48000 );
assert . equal ( req . body . byteLength , 32826 );
const samples = new DataView ( decodeFirWav ( req . body ));
assert . ok ( Math . abs ( samples . getFloat32 ( 4000 , true ) - 0.25 * 44100 / 48000 ) < 1e-5 );
}
});
test ( 'legacy room uses room metadata if cross-origin headers are hidden' , async () => {
const raw = encodeFir ( Array ( 8192 ). fill ( 0 ));
globalThis . fetch = async () => new Response ( raw , { headers : { 'Content-Type' : 'application/octet-stream' }});
const data = await readFirBinary ( '/library/left' , 44100 );
assert . equal ( new DataView ( encodeFirWav ( data )). getUint32 ( 24 , true ), 44100 );
await assert . rejects ( readFirBinary ( '/library/left' ), /rateError/ );
});
test ( 'room library rejects HTML, JSON and invalid raw coefficients' , async () => {
for ( const response of [ new Response ( '<html>error</html>' ), new Response ( '{}' ,{ headers : { 'Content-Type' : 'application/json' }}), new Response ( new ArrayBuffer ( 32768 ),{ headers : { 'Content-Type' : 'audio/wav' }})]) {
globalThis . fetch = async () => response ;
await assert . rejects ( readFirBinary ( '/library/left' , 44100 ), /libraryFormat/ );
}
const bad = encodeFir ( Array ( 8192 ). fill ( 0 )); new DataView ( bad ). setFloat32 ( 0 , NaN , true );
globalThis . fetch = async () => new Response ( bad ,{ headers : { 'Content-Type' : 'application/octet-stream' }});
await assert . rejects ( readFirBinary ( '/library/left' , 44100 ), /libraryFormat/ );
});
test ( 'library WAV accepts 8191 taps and retains its own sample rate' , async () => {
const raw = encodeFir ( coefficients ());
globalThis . fetch = async () => new Response ( encodeFirWav ( raw , 44100 ),{ headers : { 'Content-Type' : 'audio/wav' }});
const data = await readFirBinary ( '/library/left' , 48000 );
assert . deepEqual ( data , raw ); assert . equal ( new DataView ( encodeFirWav ( data )). getUint32 ( 24 , true ), 44100 );
});
test ( 'resampling preserves impulse time, interaural delay and convolution gain' , () => {
const channel = ( index ) => { const values = Array ( 8192 ). fill ( 0 ); values [ index ] = 1 ; return decodeFirWav ( encodeFirWav ( encodeFir ( values ), 44100 )); };
const peak = values => values . reduce (( best , x , i ) => Math . abs ( x ) > Math . abs ( values [ best ]) ? i : best , 0 );
const arrays = [ 441 , 882 ]. map ( index => { const v = new DataView ( decodeFirWav ( encodeDeviceFirWav ( channel ( index )))); return Array . from ({ length : 8192 },( _ , i )=> v . getFloat32 ( i * 4 , true )); });
assert . equal ( peak ( arrays [ 0 ]), 480 ); assert . equal ( peak ( arrays [ 1 ]), 960 );
for ( const values of arrays ) assert . ok ( Math . abs ( values . reduce (( sum , x )=> sum + x , 0 ) - 1 ) < 0.001 );
});
test ( '48 kHz 8191 taps are padded; 8192 taps keep exact bytes' , () => {
for ( const taps of [ 8191 , 8192 ]) {
const raw = encodeFir ( Array ( taps ). fill ( - 0.125 )); const wav = encodeDeviceFirWav ( raw );
assert . equal ( wav . byteLength , 32826 ); assert . equal ( new DataView ( wav ). getUint32 ( 24 , true ), 48000 );
const decoded = decodeFirWav ( wav ); assert . deepEqual ( decoded . slice ( 0 , raw . byteLength ), raw );
if ( taps === 8191 ) assert . equal ( new DataView ( decoded ). getFloat32 ( 8191 * 4 , true ), 0 );
}
});
test ( 'device error message is included in upload failure' , async () => {
globalThis . fetch = async ()=> new Response ( JSON . stringify ({ ok : false , message : 'WAV must be mono, 48 kHz, 8192 samples' }),{ status : 400 });
await assert . rejects ( new LuxsinAPI ( '192.168.9.133' ). uploadFirChannel ( 'left' , encodeFir ( Array ( 8192 ). fill ( 0 ))), error => error . params . reason . includes ( '48 kHz' ));
});