Files
Endoleap/tests/canvas-response.test.mjs

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;}
});