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.
This commit is contained in:
2026-11-03 22:06:47 +08:00
parent 4c9708b562
commit 8be6d9b1ab
98 changed files with 1679 additions and 372 deletions
+18 -2
View File
@@ -24,7 +24,7 @@ test('identical reads share a request, return independent objects and expire', a
a.items[0].title='formatted';assert.equal(b.items[0].title,'old');
assert.equal((await e.api.diaryList({page:1,page_size:20})).items[0].title,'old');assert.equal(e.requests.length,1);
const different=e.api.diaryList({page:2,page_size:20});await e.flush();assert.equal(e.requests.length,2);e.respond(1,{items:[]});await different;
e.advance(45001);const expired=e.api.diaryList({page:1,page_size:20});await e.flush();assert.equal(e.requests.length,3);e.respond(2,{items:[]});await expired;
e.advance(e.cache.TTL+1);const expired=e.api.diaryList({page:1,page_size:20});await e.flush();assert.equal(e.requests.length,3);e.respond(2,{items:[]});await expired;
});
test('successful mutations invalidate related reads; unrelated and failed writes retain cache',async()=>{
const e=setup();const diary=e.api.diaryList(),album=e.api.albumList();await e.flush();e.respond(0,{items:[]});e.respond(1,[]);await Promise.all([diary,album]);
@@ -50,7 +50,7 @@ test('each upper page observes changes independently and pull refresh bypasses c
await Promise.all([a.loadData(),b.loadData()]);assert.equal(loads,2);
e.cache.mutation('diaryUpdate',{success:true});pending=Promise.all([a.loadData(),b.loadData()]);await e.flush();assert.equal(e.requests.length,2);e.respond(1,{items:[{id:1}]});await pending;assert.equal(loads,4);
pending=a.onPullDownRefresh();await e.flush();assert.equal(e.requests.length,3);e.respond(2,{items:[]});await pending;
e.advance(45001);pending=b.loadData();await e.flush();assert.equal(e.requests.length,4);e.respond(3,{items:[]});await pending;
e.advance(e.cache.TTL+1);pending=b.loadData();await e.flush();assert.equal(e.requests.length,4);e.respond(3,{items:[]});await pending;
});
test('permission reads remain live and longer profile TTL does not extend list TTL',async()=>{
const e=setup();let p=e.api.periodList();await e.flush();e.respond(0,{cycles:[]});await p;
@@ -99,3 +99,19 @@ test('photo metadata updates invalidate album and photo caches',async()=>{
pending=e.api.photoUpdate({id:1,taken_at:'',location:'公园',activity:'散步'});await e.flush();e.respond(2,{success:true});await pending;
pending=Promise.all([e.api.photos(7),e.api.albumList()]);await e.flush();assert.equal(e.requests.length,5);e.respond(3,[]);e.respond(4,[]);await pending;
});
test('storage reads are reused and upload or confirmed payment invalidates their usage',async()=>{
const e=setup();let p=e.api.storageInfo();await e.flush();e.respond(0,{used_bytes:10});await p;
await e.api.storageInfo();assert.equal(e.requests.length,1);
e.cache.mutation('upload',{success:true});p=e.api.storageInfo();await e.flush();assert.equal(e.requests.length,2);e.respond(1,{used_bytes:20});await p;
e.cache.mutation('storageOrderStatus',{status:'paid'});p=e.api.storageInfo();await e.flush();assert.equal(e.requests.length,3);e.respond(2,{quota_bytes:1024});await p;
});
test('remote versions retain unchanged data and invalidate every page after partner or admin changes',async()=>{
const e=setup();e.cache.applyRemoteRevision('initial');let loads=0;
const page=e.withDataCache({data:{},async load(){loads++;this.data.items=await e.api.diaryList();}},{load:['diaryList']});
let p=page.load();await e.flush();e.respond(0,{items:[]});await p;
assert.equal(e.cache.applyRemoteRevision('initial'),false);await page.load();assert.equal(e.requests.length,1);
assert.equal(e.cache.applyRemoteRevision('partner-change'),true);p=page.onDataChanged();await e.flush();e.respond(1,{items:[{id:1}]});await p;assert.equal(loads,2);assert.equal(page.data.items.items[0].id,1);
const stale=e.api.photos(7);await e.flush();e.cache.applyRemoteRevision('admin-change');e.respond(2,{items:[{id:'old'}]});await stale;
p=e.api.photos(7);await e.flush();assert.equal(e.requests.length,4);e.respond(3,{items:[{id:'new'}]});await p;
});