import test from 'node:test'; import assert from 'node:assert/strict'; import { StateClient } from '../shared/rpc-client.ts'; import { NETWORK, type TestnetBlock } from '../shared/testnet.ts'; const genesis='0'.repeat(64),chainId='f'.repeat(64); const block=(height:number,previousHash:string):TestnetBlock=>({ network:NETWORK,chainId,height, hash:height.toString(16).padStart(64,'0'),previousHash,transactions:[] }) as unknown as TestnetBlock; test('default browser-style fetch receives the global receiver, not StateClient',async()=>{ const original=globalThis.fetch; let calls=0; globalThis.fetch=async function(this:unknown,input:RequestInfo|URL,init?:RequestInit){ assert.equal(this,globalThis,'native browser fetch would throw Illegal invocation for another receiver'); assert.equal(input,'/api/summary');assert.equal(init?.redirect,'error');calls++; return Response.json({network:NETWORK,chainId,genesis,tip:genesis,height:0,revision:0, cumulativeWork:'0',pendingTransfers:[],peers:0,algorithm:'RandomX',maturity:2}); }; try{const state=await new StateClient().load();assert.equal(state.tip,genesis);assert.equal(calls,2);} finally{globalThis.fetch=original;} }); function fixture(count:number){ const blocks:TestnetBlock[]=[];for(let i=0;i{ calls.push(path); const url=new URL(path,'http://localhost'); const tip=blocks.at(-1)?.hash??genesis; if(url.pathname==='/api/summary')return Response.json({network:NETWORK,chainId,genesis,tip,height:blocks.length, cumulativeWork:String(blocks.length),pendingTransfers:[],peers:0,algorithm:'RandomX',maturity:2,revision:0}); if(conflict){conflict=false;return Response.json({error:'changed'},{status:409});} const offset=Number(url.searchParams.get('offset')),limit=Number(url.searchParams.get('limit')); const page=blocks.slice(offset,offset+limit); return Response.json({network:NETWORK,chainId,tip,height:blocks.length,offset,blocks:page, next:malformed?offset:(offset+page.length{malformed=true;},conflict:()=>{conflict=true;}}; } test('paged state loads contiguous history and reuses cache when unchanged or appended',async()=>{ const f=fixture(35);const initial=await f.client.load();assert.equal(initial.blocks.length,35); assert.equal(f.calls.filter(p=>p.includes('canonical')).length,3); f.calls.length=0;await f.client.load();assert.equal(f.calls.length,2); f.blocks.push(block(36,f.blocks.at(-1)!.hash));f.calls.length=0; assert.equal((await f.client.load()).blocks.length,36); assert.equal(f.calls.filter(p=>p.includes('canonical')).length,2); // prefix check + append }); test('paged state rejects malformed pagination, continuity and excessive history',async()=>{ const bad=fixture(17);bad.malform();await assert.rejects(bad.client.load(),/pagination/); const broken=fixture(3);broken.blocks[1].previousHash='a'.repeat(64);await assert.rejects(broken.client.load(),/continuity/); const big=fixture(10001);await assert.rejects(big.client.load(),/history limit/); }); test('tip conflict restarts once; parallel refreshes share bounded work',async()=>{ const f=fixture(20);f.conflict(); const [a,b]=await Promise.all([f.client.load(),f.client.load()]); assert.equal(a,b);assert.equal(a.blocks.length,20); assert.equal(f.calls.filter(p=>p==='/api/summary').length,3); }); test('oversized node responses are rejected before JSON parsing',async()=>{ const client=new StateClient((async()=>new Response('{}',{headers:{'Content-Type':'application/json','Content-Length':'99999999'}})) as typeof fetch); await assert.rejects(client.load(),/page limit/); });