40 lines
3.4 KiB
JavaScript
40 lines
3.4 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { fetchCanvasResponse } from '../src/lib/canvas-response.ts';
|
|
import { api, ApiError } from '../src/lib/api.ts';
|
|
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
|
|
test('snapshot body outlives the header deadline and reports transfer timing and bytes',async()=>{
|
|
const reports=[]; const bytes=new TextEncoder().encode('{"version":210,"document":{"title":"测试"}}');
|
|
const upstream=new Response(new ReadableStream({async start(c){ c.enqueue(bytes.slice(0,10));await wait(50);c.enqueue(bytes.slice(10));c.close();}}),{headers:{ETag:'"210"'}});
|
|
const response=await fetchCanvasResponse('https://example.test',{}, {headersMs:10,bodyMs:500,idleMs:300},async()=>upstream, report=>reports.push(report));
|
|
assert.equal(response.headers.get('etag'),'"210"');assert.equal((await response.json()).version,210);
|
|
assert.equal(reports.length,1);assert.equal(reports[0].outcome,'complete');assert.equal(reports[0].bytes,bytes.length);assert.ok(reports[0].transfer_ms>=30);assert.equal(reports[0].chunks,2);
|
|
});
|
|
test('snapshot idle timeout aborts unfinished bodies instead of returning a valid empty snapshot',async()=>{
|
|
const reports=[];let cancelled=false;
|
|
const upstream=new Response(new ReadableStream({start(c){c.enqueue(new TextEncoder().encode('{"version":'));},cancel(){cancelled=true;}}));
|
|
const response=await fetchCanvasResponse('https://example.test',{}, {headersMs:100,bodyMs:500,idleMs:15},async()=>upstream,r=>reports.push(r));
|
|
await assert.rejects(response.json(),{name:'TimeoutError'});assert.ok(cancelled);assert.equal(reports[0].outcome,'error');assert.equal(reports[0].error,'TimeoutError');
|
|
});
|
|
test('header deadline rejects a server that has not responded',async()=>{
|
|
await assert.rejects(fetchCanvasResponse('https://example.test',{}, {headersMs:10,bodyMs:500,idleMs:100},(_url,options)=>new Promise((_resolve,reject)=>options.signal.addEventListener('abort',()=>reject(options.signal.reason)))),{name:'TimeoutError'});
|
|
});
|
|
test('disconnect and consumer cancellation release the upstream read',async()=>{
|
|
const abort=new AbortController();let cancelled=0;
|
|
const make=()=>new Response(new ReadableStream({cancel(){cancelled++;}}));
|
|
const response=await fetchCanvasResponse('https://example.test',{signal:abort.signal},{headersMs:100,bodyMs:500,idleMs:100},async()=>make());
|
|
abort.abort();await assert.rejects(response.text(),{name:'AbortError'});
|
|
const second=await fetchCanvasResponse('https://example.test',{}, {headersMs:100,bodyMs:500,idleMs:100},async()=>make());await second.body.cancel();assert.equal(cancelled,2);
|
|
});
|
|
test('client distinguishes a failed 200 body transfer, malformed JSON, server errors and 204',async()=>{
|
|
const original=globalThis.fetch;
|
|
try {
|
|
globalThis.fetch=async()=>new Response(new ReadableStream({start(c){c.enqueue(new TextEncoder().encode('{"version":'));c.error(new TypeError('terminated'));}}));
|
|
await assert.rejects(api('canvas'),error=>error instanceof ApiError && error.message==='response_interrupted');
|
|
globalThis.fetch=async()=>new Response('{"version":');await assert.rejects(api('canvas'),/invalid_api_response/);
|
|
globalThis.fetch=async()=>Response.json({error:'upstream_timeout'},{status:504});await assert.rejects(api('canvas'),/upstream_timeout/);
|
|
globalThis.fetch=async()=>new Response(null,{status:204});assert.equal(await api('canvas'),null);
|
|
} finally {globalThis.fetch=original;}
|
|
});
|