const test = require('node:test'); const assert = require('node:assert/strict'); const fs = require('node:fs'); const vm = require('node:vm'); const path = require('node:path'); function setup() { const storage = new Map(); const logins = [], requests = []; let app; const wx = { getStorageSync: key => storage.get(key), setStorageSync: (key,value) => storage.set(key,value), clearStorageSync:()=>storage.clear(), removeStorageSync: key => storage.delete(key), getStorageInfoSync: () => ({keys:[...storage.keys()]}), login: options => logins.push(options), request: options => requests.push(options), getNetworkType: options => options.success({networkType:'wifi'}), onNetworkStatusChange(){}, hideLoading(){}, showToast(){} }; const loadModule = require('./load-module.cjs')({wx, getApp:()=>app, console, Promise, Date}); const ctx = {wx, getApp:()=>app, console, Promise, setTimeout, clearTimeout, module:{exports:{}}, require: name => loadModule(path.join(__dirname,'../utils', name + '.js'))}; vm.runInNewContext(fs.readFileSync(path.join(__dirname,'../utils/request.js'),'utf8'),ctx); const request = ctx.module.exports; ctx.require = name => name === './utils/request' ? request : loadModule(path.join(__dirname,'..', name + '.js')); ctx.App = definition => {app=definition;}; vm.runInNewContext('(function(){' + fs.readFileSync(path.join(__dirname,'../app.js'),'utf8') + '\n})()',ctx); return {app,wx,storage,logins,requests,api:request.api}; } const flush = async()=> {for(let i=0;i<40;i++) await Promise.resolve();}; async function finishLogin(env, token='fresh') { env.logins.at(-1).success({code:'wechat-code'}); await flush(); const login = env.requests.findLast(req=>req.url.endsWith('/wechatLogin')); assert.ok(login, 'login endpoint bypasses auth gate'); login.success({statusCode:200,data:{token,userId:87}}); await flush(); } test('cold start refreshes even cached token and holds concurrent business requests until login completes',async()=>{ const env=setup(); env.storage.set('token','old'); env.app.onLaunch(); const first=env.api.coupleInfo(), second=env.api.diaryList(); await flush(); assert.equal(env.logins.length,1); assert.equal(env.requests.length,0); await finishLogin(env); assert.equal(env.app.globalData.authState,'ready'); const business=env.requests.filter(req=>!req.url.endsWith('/wechatLogin')); assert.equal(business.length,2); business.forEach(req=>{assert.equal(req.header.Authorization,'Bearer fresh');req.success({statusCode:200,data:[]});}); await Promise.all([first,second]); }); test('first launch without any stored token issues no anonymous business request',async()=>{ const env=setup();env.app.onLaunch();const pending=env.api.periodList(); await flush();assert.equal(env.requests.length,0);assert.equal(env.logins.length,1); await finishLogin(env);const business=env.requests.find(req=>req.url.endsWith('/periodList')); assert.equal(business.header.Authorization,'Bearer fresh'); business.success({statusCode:200,data:{cycles:[],canAccess:true}});await pending; }); test('login failure rejects waiting loads and explicit retry obtains a usable token',async()=>{ const env=setup();env.app.onLaunch(); const failed=assert.rejects(env.api.diaryList(),error=>error.authFailed===true); env.logins[0].success({});await failed;await flush(); assert.equal(env.app.globalData.authState,'error');assert.equal(env.requests.length,0); await assert.rejects(env.app.ensureAuth());assert.equal(env.logins.length,1); const retry=env.app.silentLogin();await finishLogin(env);await retry; assert.equal(env.storage.get('token'),'fresh'); }); test('parallel 401 responses share token refresh and retry at most once',async()=>{ const env=setup();env.app.onLaunch();await finishLogin(env,'first'); const one=env.api.coupleInfo(),two=env.api.diaryList();await flush(); const old=env.requests.filter(req=>!req.url.endsWith('/wechatLogin')); old.forEach(req=>req.success({statusCode:401,data:{error:'expired'}}));await flush(); assert.equal(env.logins.length,2);await finishLogin(env,'second'); const retried=env.requests.filter(req=>req.header.Authorization==='Bearer second');assert.equal(retried.length,2); const failed=assert.rejects(one,error=>error._statusCode===401); retried[0].success({statusCode:401,data:{error:'still unauthorized'}});retried[1].success({statusCode:200,data:[]}); await failed;await two;assert.equal(env.logins.length,2); }); test('account deletion clears drafts and credentials and prevents automatic account recreation',async()=>{ const env=setup();env.storage.set('token','private');env.storage.set('publishDraft','private draft');env.app.finishAccountDeletion();assert.equal(env.storage.has('publishDraft'),false);assert.equal(env.storage.has('token'),false);assert.equal(env.storage.get('accountDeleted'),true);assert.equal(env.app.globalData.authState,'error');await assert.rejects(env.app.refreshAuth('private'));assert.equal(env.logins.length,0); });