Files
web_hgdjc_h5/tests/public-cache.test.mjs
T
2026-11-06 00:13:27 +07:00

58 lines
5.7 KiB
JavaScript

import {linkShared} from './helpers/shared-modules.mjs';
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(linkShared(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('trackerConfigured' in data,false);assert.ok(!text.includes('t.me/business'));assert.deepEqual(data.footer.footerInfo.contactList,[]);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}));
const filtered=publicSiteData({},{footerInfo:{navList:[{name:'帮助',translationList:[{name:'关于我们',urlValue:'/richtext/关于我们'},{name:'意见反馈',urlValue:'/info/feedback'},{name:'在线客服',urlValue:'https://help.test'},{name:'邮箱',urlValue:'mailto:a@b.test'},{name:'充值',urlValue:'/info/deposit/'}]}]}});
assert.deepEqual(filtered.footer.footerInfo.navList[0].translationList.map(item=>item.name),['关于我们']);
});