Files
Blizzard 8be6d9b1ab feat: add account deletion, announcements and subscription renewal
Support personal records and persistent smoking visibility. Add upload quota preflight, file cleanup, privacy entry, image caching and revision-based page refresh across the backend, admin and mini program.
2026-11-03 22:06:47 +08:00

77 lines
4.9 KiB
JavaScript

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