Files
web_hgdjc_h5/tests/request-log.test.mjs
2026-11-06 00:13:27 +07:00

71 lines
4.9 KiB
JavaScript

import {linkShared} from './helpers/shared-modules.mjs';
import {test} from 'node:test';
import assert from 'node:assert/strict';
import {readFile} from 'node:fs/promises';
import ts from 'typescript';
async function moduleUrl(file,transform=code=>code){
const source=await readFile(new URL(file,import.meta.url),'utf8');
const code=ts.transpileModule(source,{fileName:new URL(file,import.meta.url).pathname,compilerOptions:{module:ts.ModuleKind.ESNext,target:ts.ScriptTarget.ES2022,jsx:ts.JsxEmit.ReactJSX}}).outputText;
return 'data:text/javascript;base64,'+Buffer.from(linkShared(transform(code))).toString('base64');
}
const protocol=await moduleUrl('../src/lib/api/protocol.ts'),auth=await moduleUrl('../src/lib/client/auth.ts');
const apiUrl=await moduleUrl('../src/lib/client/api.ts',code=>code.replace('"../api/protocol"',JSON.stringify(protocol)).replace('"./auth"',JSON.stringify(auth)));
const {ApiClient}=await import(apiUrl);
const startupUrl='data:text/javascript,export const calls=[];export function startTracker(){calls.push("tracker")}export function startApplication(...args){calls.push(args)}';
const startup=await import(startupUrl);
const scriptUrl='data:text/javascript,export default function Script(){}';
const runtimeUrl=await moduleUrl('../src/components/site/SiteRuntime.tsx',code=>{
const imports={'react/jsx-runtime':import.meta.resolve('react/jsx-runtime'),'next/script':scriptUrl,'@/lib/client/application':startupUrl};
for(const [path,url] of Object.entries(imports))for(const quote of ["'",'"'])code=code.replace(quote+path+quote,JSON.stringify(url));
return code;
});
const {SiteRuntime}=await import(runtimeUrl);
async function browser(work){
const previous={window:globalThis.window,fetch:globalThis.fetch},logs=[];
globalThis.window={console:Object.fromEntries(['log','info','debug','warn','error','group','groupCollapsed','groupEnd','table','trace'].map(method=>[method,(...args)=>logs.push({method,args})]))};
try{await work(logs);assert.deepEqual(logs,[]);}
finally{globalThis.fetch=previous.fetch;if(previous.window===undefined)delete globalThis.window;else globalThis.window=previous.window;}
}
test('browser requests retain query, credentials, decoded responses and multipart bodies without console output',async()=>{
await browser(async()=>{
const body={account:'player',password:'unchanged-password'},query={pageNum:2,token:'query-token',empty:''};
const payload={code:200,data:JSON.stringify({id:31,moreUrl:'/rank/new/',access_token:'response-token'})};
let received;
globalThis.fetch=async(url,options)=>{received={url,options};return new Response(JSON.stringify(payload),{status:200});};
const result=await new ApiClient().raw('auth/login',query,body);
assert.equal(received.url,'/api/app/auth/login?pageNum=2&token=query-token');
assert.equal(received.options.credentials,'include');assert.equal(received.options.headers.Accept,'application/json');assert.equal(JSON.parse(received.options.body).password,body.password);
assert.equal(result.access_token,'response-token');assert.equal(result.moreUrl,'/rank/new/');
const form=new FormData();form.append('image',new Blob(['image bytes'],{type:'image/png'}),'cover.png');
await new ApiClient().raw('upload',{},form);assert.equal(received.url,'/api/app/upload');assert.equal(received.options.body,form);assert.equal(received.options.headers['Content-Type'],undefined);
});
});
test('API business failures, invalid JSON and network failures remain rejected without console output',async()=>{
await browser(async()=>{
const api=new ApiClient();
globalThis.fetch=async()=>new Response(JSON.stringify({code:5100,msg:'搜索服务暂不可用',data:null}),{status:200});
await assert.rejects(api.raw('playlet/search',{keyword:'都市'}),error=>error.code===5100&&error.message==='搜索服务暂不可用');
globalThis.fetch=async()=>new Response('invalid json',{status:502});
await assert.rejects(api.raw('playlet/home'),SyntaxError);
globalThis.fetch=async()=>{throw new TypeError('Network offline');};
await assert.rejects(api.raw('playlet/home'),/Network offline/);
});
});
test('home, player and info hydration starts the application only, without analytics, logging or refetching',async()=>{
await browser(async()=>{
globalThis.fetch=()=>assert.fail('hydration must use existing server data');
const home={homeConfigData:[{id:31,moreUrl:'/topics/'}]},navigation=[{name:'短剧',url:'/shorts/'}];
for(const page of [{path:'/',panel:'home'},{path:'/detail/7/',player:{title:'短剧'}},{path:'/info/about-us/',panel:'info',info:{key:'about-us'}}]){
const start=startup.calls.length;
SiteRuntime({home,homeLoaded:true,page,navigation}).props.onReady();
assert.equal(startup.calls.length,start+1);assert.equal(startup.calls[start][0],home);assert.equal(startup.calls[start][3],page);
}
assert.ok(!startup.calls.includes('tracker'));
});
});