Files
web_hgdjc_h5/tests/public-cache.test.mjs
T
谢宇宁andClaude Opus 5.5 f65d10e998 Initial commit
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 23:07:59 +07:00

55 lines
5.1 KiB
JavaScript

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {readFile} from 'node:fs/promises';
import ts from 'typescript';
async function moduleURL(path,replace=value=>value){
const code=ts.transpileModule(await readFile(new URL(path,import.meta.url),'utf8'),{compilerOptions:{target:ts.ScriptTarget.ES2022,module:ts.ModuleKind.ESNext}}).outputText;
return 'data:text/javascript;base64,'+Buffer.from(replace(code)).toString('base64');
}
const {PublicDataCache,publicCachePolicy}=await import(await moduleURL('../src/lib/api/public-cache.ts'));
const protocol=await moduleURL('../src/lib/api/protocol.ts');
const {publicSiteData}=await import(await moduleURL('../src/lib/site-shared-data.ts',code=>code.replace("'./api/protocol'",JSON.stringify(protocol))));
test('public cache coalesces concurrent requests, isolates returned objects and refreshes after expiry',async()=>{
let now=0,reads=0,release;const cache=new PublicDataCache(()=>now);
const load=()=>{reads++;return new Promise(resolve=>release=resolve);};
const a=cache.read('api-a|config',100,load),b=cache.read('api-a|config',100,load);
assert.equal(reads,1);release({list:[{name:'original'}]});
const [first,second]=await Promise.all([a,b]);first.list[0].name='mutated';assert.equal(second.list[0].name,'original');
assert.equal((await cache.read('api-a|config',100,load)).list[0].name,'original');assert.equal(reads,1);
now=101;const refreshed=cache.read('api-a|config',100,load);assert.equal(reads,2);release({list:[{name:'updated'}]});assert.equal((await refreshed).list[0].name,'updated');
assert.equal((await cache.read('api-b|config',100,async()=>({list:[]}))).list.length,0);
});
test('only successful payloads are cached; configured stale values survive outages without caching failures',async()=>{
let now=0,reads=0;const cache=new PublicDataCache(()=>now),options={stale:true,accept:value=>value.code===200};
await cache.read('config',10,async()=>({code:200,name:'good'}),options);now=11;
const failed=async()=>{reads++;throw new Error('upstream offline');};
assert.equal((await cache.read('config',10,failed,options)).name,'good');assert.equal((await cache.read('config',10,failed,options)).name,'good');assert.equal(reads,2);
assert.equal((await cache.read('config',10,async()=>({code:500}),options)).name,'good');
await assert.rejects(cache.read('navigation',10,failed),/offline/);
assert.equal((await cache.read('navigation',10,async()=>({code:200,name:'recovered'}))).name,'recovered');
await cache.read('invalid',10,async()=>({code:500}),{accept:value=>value.code===200});
assert.equal((await cache.read('invalid',10,async()=>({code:200,name:'valid'}))).name,'valid');
});
test('cache bounds and endpoint policies keep private data and indefinite growth out of the shared cache',async()=>{
const cache=new PublicDataCache(Date.now,2);let reads=0;
const load=async()=>({count:++reads});
for(const key of ['a','b','c'])await cache.read(key,10000,load);
assert.equal((await cache.read('a',10000,load)).count,4);
await cache.read('a',0,load);assert.equal(reads,5);
assert.equal(publicCachePolicy('playlet/home').ttl,30000);assert.equal(publicCachePolicy('config/info').ttl,60000);
for(const endpoint of ['auth/getInfo','auth/guest','coin/trans/list','playlet/detail','playlet/chapter/detail','playlet/history/list','recharge/package/list'])assert.equal(publicCachePolicy(endpoint),undefined);
assert.equal(publicCachePolicy('config/info','0').ttl,0);assert.equal(publicCachePolicy('playlet/home','999999').ttl,30000);
});
test('shared browser snapshot contains visible configuration and footer fields without SDK or unknown secrets',()=>{
const data=publicSiteData({reportSdkConfig:'private-sdk-cipher',token:'private-token',config:{officialAppAddr:'https://app.test/download',telegram:'https://t.me/business',apiKey:'private-api-key'},advitise:{dialogList:[{id:'a',title:'广告',images:'https://ad.test/image',link:'https://ad.test',token:'private-ad-token'}],unknown:[{secret:'private-slot'}]}},{footerInfo:{brand:{title:'黄果短剧',content:'真实页脚',token:'private-brand-token'},contactList:[{name:'服务时间',value:'每天',token:'private-contact-token'}],navList:[{id:1,name:'规则',urlValue:'/ignored/',token:'private-nav-token',translationList:[{id:2,name:'关于我们',urlValue:'/richetext/关于我们',secret:'private-nav-child'}]}]}});
const text=JSON.stringify(data);for(const secret of ['private-sdk-cipher','private-token','private-api-key','private-ad-token','private-slot','private-contact-token','private-nav-token','private-nav-child','private-brand-token'])assert.ok(!text.includes(secret));
assert.equal(data.trackerConfigured,true);assert.equal(data.config.config.officialAppAddr,'https://app.test/download');assert.equal(data.footer.footerInfo.brand.title,'黄果短剧');assert.equal(data.footer.footerInfo.brand.content,'真实页脚');
assert.equal(data.footer.footerInfo.navList[0].translationList[0].name,'关于我们');assert.equal(data.footer.footerInfo.navList[0].translationList[0].urlValue,'/richetext/关于我们');
assert.doesNotThrow(()=>publicSiteData({config:null,advitise:null},{footerInfo:null}));
});