mirror of
https://github.com/hectorqin/reader.git
synced 2026-11-11 21:41:02 +08:00
57 lines
3.4 KiB
TypeScript
57 lines
3.4 KiB
TypeScript
import {test} from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import Fastify from 'fastify';
|
|
import {setTimeout as delay} from 'node:timers/promises';
|
|
import {registerMediaProxy} from '../src/http/routes/media-proxy.ts';
|
|
import {registerErrorHandler} from '../src/http/errors.ts';
|
|
|
|
test('media bridge forwards direct playback redirects without consuming the source',async()=>{
|
|
const app=Fastify({logger:false});
|
|
registerMediaProxy(app,{close:async()=>{},request:async()=>new Response(null,{status:307,headers:{location:'https://cdn.example/movie?sign=temporary','cache-control':'private, no-store','referrer-policy':'no-referrer','server-timing':'resolve;dur=12'}})});
|
|
try{const response=await app.inject('/api/v1/media/streams/test');assert.equal(response.statusCode,307);assert.equal(response.headers.location,'https://cdn.example/movie?sign=temporary');assert.equal(response.headers['server-timing'],'resolve;dur=12');}finally{await app.close();}
|
|
});
|
|
|
|
test('media proxy bounds outstanding work and releases its capacity after replies',async()=>{
|
|
const app=Fastify({logger:false});registerErrorHandler(app);
|
|
const release:Array<()=>void>=[];
|
|
registerMediaProxy(app,{close:async()=>{},request:async()=>new Promise<Response>(resolve=>release.push(()=>resolve(new Response('{}',{headers:{'content-type':'application/json'}}))))});
|
|
try{
|
|
await app.ready();
|
|
const requests=Array.from({length:64},()=>app.inject('/api/v1/media/libraries'));
|
|
for(let i=0;i<100&&release.length!==64;i++)await delay(5);
|
|
assert.equal(release.length,64);
|
|
assert.equal((await app.inject('/api/v1/media/libraries')).statusCode,503);
|
|
release.splice(0).forEach(done=>done());
|
|
for(const response of await Promise.all(requests))assert.equal(response.statusCode,200);
|
|
const next=app.inject('/api/v1/media/libraries');
|
|
for(let i=0;i<100&&!release.length;i++)await delay(5);
|
|
assert.equal(release.length,1);release.shift()!();assert.equal((await next).statusCode,200);
|
|
}finally{release.forEach(done=>done());await app.close();}
|
|
});
|
|
|
|
for(const shutdown of [false,true])test(`open media stream aborts without buffering on ${shutdown?'server shutdown':'client disconnect'}`,async()=>{
|
|
const app=Fastify({logger:false});registerErrorHandler(app);
|
|
let aborted=false;
|
|
registerMediaProxy(app,{close:async()=>{},request:async(_path,options)=>{
|
|
const body=new ReadableStream<Uint8Array>({start(controller){
|
|
controller.enqueue(new TextEncoder().encode('first chunk'));
|
|
options!.signal!.addEventListener('abort',()=>{aborted=true;controller.error(new Error('client disconnected'));},{once:true});
|
|
}});
|
|
return new Response(body,{headers:{'content-type':'audio/mpeg'}});
|
|
}});
|
|
try{
|
|
const origin=await app.listen({host:'127.0.0.1',port:0});
|
|
const controller=new AbortController();
|
|
const response=await fetch(origin+'/api/v1/media/streams/test',{signal:controller.signal});
|
|
const reader=response.body!.getReader();
|
|
assert.equal(new TextDecoder().decode((await reader.read()).value),'first chunk');
|
|
if(shutdown){
|
|
const stopped=app.close();
|
|
await Promise.race([stopped,delay(2000,undefined,{ref:false}).then(()=>{throw Error('shutdown waited for the unfinished stream');})]);
|
|
}else controller.abort();
|
|
await reader.cancel().catch(()=>{});
|
|
for(let i=0;i<100&&!aborted;i++)await delay(5);
|
|
assert.equal(aborted,true);
|
|
}finally{await app.close();}
|
|
});
|