Files
sundynix-pets/tests/admin-refresh.test.cjs
T
Blizzard 9d7cf720a5
ci-cd / verify (push) Successful in 1m59s
ci-cd / deploy (push) Skipped
feat: add multi-source breed encyclopedia and two-hour refresh sessions
2026-10-05 13:38:24 +08:00

35 lines
3.3 KiB
JavaScript

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