const test=require('node:test'), assert=require('node:assert/strict'), fs=require('node:fs'),vm=require('node:vm'); const {createRequire}=require('node:module'); const adminRequire=createRequire(require('node:path').resolve('pets-be/web/admin/package.json')); const ts=adminRequire('typescript'),axios=adminRequire('axios').default; const tick=()=>new Promise(resolve=>setImmediate(resolve)); function runtime(adapter) { const storage=new Map(),events=[]; const localStorage={getItem:k=>storage.get(k)||null,setItem:(k,v)=>storage.set(k,v),removeItem:k=>storage.delete(k)}; axios.defaults.adapter=adapter; const module={exports:{}}; const code=ts.transpileModule(fs.readFileSync('pets-be/web/admin/src/lib/api.ts','utf8'),{compilerOptions:{module:ts.ModuleKind.CommonJS,esModuleInterop:true,target:ts.ScriptTarget.ES2022}}).outputText; vm.runInNewContext(code,{module,exports:module.exports,require:adminRequire,localStorage,navigator:{},window:{dispatchEvent:e=>events.push(e.type)},Event:class{constructor(type){this.type=type;}}}); return {...module.exports,storage,events}; } const response=(config,data)=>({status:200,statusText:'OK',config,headers:{},data:{code:0,data}}); const unauthorized=config=>Promise.reject(new axios.AxiosError('expired','ERR_BAD_REQUEST',config,null,{status:401,data:{message:'expired'},config})); const pair=token=>({token,refresh_token:'refresh-'+token,expires_at:Date.now()/1000+7200,refresh_expires_at:Date.now()/1000+7200}); test('管理端并发请求只刷新一次,再携带新 token 请求',async()=>{ let releases,refreshes=0,seen=[]; const r=runtime(config=>{if(config.url==='/admin/refresh'){refreshes++;return new Promise(resolve=>releases=()=>resolve(response(config,pair('new'))));}seen.push(config.headers.Authorization);return Promise.resolve(response(config,{users:1}));}); r.setToken({...pair('old'),expires_at:Date.now()/1000+30});const calls=[r.api.stats(),r.api.stats(),r.api.stats()];await tick();assert.equal(refreshes,1);releases();await Promise.all(calls);assert.deepEqual(seen,['Bearer new','Bearer new','Bearer new']);assert.equal(r.getToken(),'new'); }); test('刷新网络失败保留登录,过期拒绝则清空会话',async()=>{ let rejectAuth=false; const r=runtime(config=>config.url==='/admin/refresh' ? rejectAuth?unauthorized(config):Promise.reject(new Error('offline')):unauthorized(config)); r.setToken({...pair('old'),expires_at:Date.now()/1000+30});await assert.rejects(r.api.stats());assert.equal(r.getToken(),'old');assert.equal(r.events.length,0); rejectAuth=true;await assert.rejects(r.api.stats());assert.equal(r.getToken(),'');assert.ok(r.events.includes('admin-auth-expired')); }); test('退出期间的刷新响应不会恢复会话,登录密码错误也不会递归刷新',async()=>{ let release,refreshes=0; const r=runtime(config=>{if(config.url==='/admin/login')return unauthorized(config);if(config.url==='/admin/refresh'){refreshes++;return new Promise(resolve=>release=()=>resolve(response(config,pair('new'))));}return Promise.resolve(response(config,{}));}); r.setToken({...pair('old'),expires_at:Date.now()/1000+30});const request=r.api.stats();await tick();r.clearToken();release();await assert.rejects(request);assert.equal(r.getToken(),''); await assert.rejects(r.api.login('sundynix','bad'));assert.equal(refreshes,1); });