119 lines
9.0 KiB
JavaScript
119 lines
9.0 KiB
JavaScript
import {test} from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import {readFile} from 'node:fs/promises';
|
|
import vm from 'node:vm';
|
|
import ts from 'typescript';
|
|
|
|
const contract=JSON.parse(await readFile(new URL('./fixtures/sgdj-app-contract.json',import.meta.url),'utf8')).operations;
|
|
const compile=async file=>ts.transpileModule(await readFile(new URL(file,import.meta.url),'utf8'),{compilerOptions:{target:ts.ScriptTarget.ES2022,module:ts.ModuleKind.CommonJS}}).outputText;
|
|
const protocol={},routing={},rendering={},channel={},server={};
|
|
vm.runInNewContext(await compile('../src/lib/api/protocol.ts'),{exports:protocol,URL});
|
|
vm.runInNewContext(await compile('../src/lib/routing.ts'),{exports:routing,URL,URLSearchParams});
|
|
vm.runInNewContext(await compile('../src/lib/client/render.ts'),{exports:rendering,URL,require:key=>key==='../routing'?routing:protocol});
|
|
vm.runInNewContext(await compile('../src/lib/client/channel.ts'),{exports:channel,URL,URLSearchParams,require:key=>key==='../routing'?routing:key==='../api/protocol'?protocol:key==='./render'?rendering:{}});
|
|
vm.runInNewContext(await compile('../src/lib/api/server.ts'),{exports:server,require:key=>key==='react'?{cache:fn=>fn}:protocol});
|
|
const plain=value=>JSON.parse(JSON.stringify(value));
|
|
|
|
test('proxy methods and public endpoints are backed by the reviewed API contract',async()=>{
|
|
for(const path of server.PUBLIC_PATHS)assert.ok(contract['GET '+path],path);
|
|
const code=ts.createSourceFile('server.ts',await readFile(new URL('../src/lib/api/server.ts',import.meta.url),'utf8'),ts.ScriptTarget.Latest,true);
|
|
const walk=node=>{
|
|
if(ts.isStringLiteral(node)&&node.text.includes('/')&&server.allowedRequest(node.text,'POST'))assert.ok(contract['POST '+node.text],node.text);
|
|
ts.forEachChild(node,walk);
|
|
};walk(code);
|
|
for(const method of ['GET','POST'])for(const path of ['video/detail','video/all/list','playlet/hot/list','playlet/home/config/detail/list','statcenter/sync'])assert.equal(server.allowedRequest(path,method),false,method+' '+path);
|
|
});
|
|
|
|
test('member list, coin, recharge, feedback and account endpoints are no longer proxied',()=>{
|
|
for(const path of ['playlet/like/list','playlet/collect/list','playlet/history/list','coin/list','recharge/list','feedback/add','user/upload','auth/login','auth/register'])
|
|
for(const method of ['GET','POST'])assert.equal(server.allowedRequest(path,method),false,method+' '+path);
|
|
assert.equal(server.PUBLIC_PATHS.has('playlet/like/list'),false);
|
|
assert.equal(server.allowedRequest('auth/guest','POST'),true);
|
|
});
|
|
|
|
test('all public list routes send only supported query fields with their native page numbers',()=>{
|
|
for(const path of ['/shorts/page/2/','/rank/hot/page/2/','/rank/new/page/2/','/rank/follow/page/2/','/topics/page/2/','/topics/10404/page/2/','/theme/10404/page/2/','/tag/6/page/2/','/config/10081/']){
|
|
const route=routing.parseSitePath(path),request=channel.channelRequest(route,'?sort=2'),operation=contract['GET '+request.endpoint];
|
|
assert.ok(operation,path);
|
|
for(const key of Object.keys(request.query))assert.ok(operation.query.includes(key),path+' unsupported '+key);
|
|
for(const key of operation.requiredQuery)assert.notEqual(request.query[key],undefined,path+' missing '+key);
|
|
if(route.key!=='config')assert.equal(request.query.pageNum,2,path);
|
|
}
|
|
assert.deepEqual(plain(channel.channelRequest(routing.parseSitePath('/shorts/'),'?sort=2')),{endpoint:'playlet/operate/list',query:{pageNum:1,pageSize:24,type:2}});
|
|
});
|
|
|
|
test('shorts random recommendations send operate type 4 and preserve selection and pagination filters',async()=>{
|
|
const route=routing.parseSitePath('/shorts/page/2/'),calls=[];
|
|
const data=await channel.loadChannel({get:async(endpoint,query)=>{calls.push({endpoint,query});return {list:[{id:8,title:'随机短剧'}],totalCount:72};}},route,'?sort=4');
|
|
assert.deepEqual(plain(calls),[{endpoint:'playlet/operate/list',query:{pageNum:2,pageSize:24,type:4}}]);
|
|
const view=channel.channelView(route,data,'/shorts/page/2/?sort=4');
|
|
assert.ok(view.html.includes('data-sort="4" aria-pressed="true">随机推荐</button>'));
|
|
assert.equal((view.html.match(/aria-pressed="true"/g)||[]).length,1);
|
|
assert.ok(view.html.includes('href="/shorts/page/3/?sort=4"'));
|
|
assert.equal(routing.paginationPath('/shorts/page/2/?sort=2',1,{sort:4}),'/shorts/?sort=4');
|
|
for(const sort of [1,2,3,4])assert.equal(channel.channelRequest(route,'?sort='+sort).query.type,sort);
|
|
for(const sort of ['0','5','NaN','1.5'])assert.equal(channel.channelRequest(route,'?sort='+sort).query.type,1);
|
|
assert.equal(channel.channelRequest(routing.parseSitePath('/tag/6/'),'?sort=4').query.sortType,1);
|
|
});
|
|
|
|
test('home more uses the documented paginated config endpoint and actual totals',async()=>{
|
|
const calls=[];
|
|
const api={get:async(endpoint,query)=>{calls.push({endpoint,query});return {config:{id:1,slug:'editors',title:'正确栏目',desc:'栏目介绍',moduleType:1},list:[{id:8,title:'当前页短剧'}],totalCount:72,pageNum:query.pageNum,pageSize:24};}};
|
|
const data=await channel.loadChannel(api,routing.parseSitePath('/config/editors/page/2/'));
|
|
assert.equal(data.name,'正确栏目');assert.equal(data.totalCount,72);assert.equal(data.list[0].id,8);
|
|
assert.deepEqual(plain(calls),[{endpoint:'playlet/home-config/list',query:{pageNum:2,pageSize:24,slug:'editors'}}]);
|
|
const view=channel.channelView(routing.parseSitePath('/config/editors/page/2/'),data,'/config/editors/page/2/');
|
|
assert.ok(view.html.includes('href="/config/editors/page/3/"'));
|
|
assert.equal((await channel.loadChannel({get:async()=>({})},routing.parseSitePath('/config/2/'))).missing,true);
|
|
assert.equal(routing.parseSitePath('/series/'),null);assert.equal(routing.parseSitePath('/video/7/'),null);
|
|
});
|
|
|
|
test('topic groups keep their names, card identities and per-group pagination boundaries',async()=>{
|
|
const groups=[{id:1,name:'热门剧单',desc:'区块介绍',totalCount:49,list:[{id:7,name:'都市精选',slug:'city',playletCount:12}]},{id:2,name:'本周推荐',totalCount:25,list:[{id:8,name:'古装精选'}]}];
|
|
const route=routing.parseSitePath('/topics/page/2/');
|
|
const data=await channel.loadChannel({get:async()=>({list:groups,pageNum:2,pageSize:24})},route);
|
|
assert.equal(data.totalCount,49);assert.deepEqual(plain(data.list.map(item=>item.id)),[7,8]);
|
|
const view=channel.channelView(route,data,'/topics/page/2/');
|
|
assert.ok(view.html.includes('热门剧单'));assert.ok(view.html.includes('本周推荐'));assert.ok(view.html.includes('/topics/city/'));assert.ok(!view.html.includes('/topics/1/'));
|
|
assert.ok(view.html.includes('/topics/page/3/'));assert.ok(!view.html.includes('/topics/page/4/'));
|
|
assert.ok(view.html.includes('channel-topic-group'));
|
|
assert.ok(!view.html.includes('channel-summary-count'));
|
|
assert.equal(view.subtitle,'围绕一个主题,发现一组好故事。');
|
|
});
|
|
|
|
test('topic detail shows playlet count subtitle, drama list summary and moreTopics',()=>{
|
|
const route=routing.parseSitePath('/topics/city/');
|
|
const view=channel.channelView(route,{
|
|
id:7,slug:'city',name:'都市精选',desc:'都市逆袭合集',playletCount:9,totalCount:9,
|
|
list:[{id:101,title:'归家'}],
|
|
moreTopics:[{id:8,name:'古装精选',slug:'costume',playletCount:6}],
|
|
},'/topics/city/');
|
|
assert.equal(view.title,'都市精选');
|
|
assert.equal(view.subtitle,'都市逆袭合集 · 9 部精选');
|
|
assert.ok(view.html.includes('共 9 部作品'));
|
|
assert.ok(view.html.includes('更多专题'));
|
|
assert.ok(view.html.includes('/topics/costume/'));
|
|
assert.ok(view.html.includes('6部短剧'));
|
|
});
|
|
|
|
test('topic pages omit empty list, summary, moreTopics and zero playlet badges',()=>{
|
|
const list=channel.channelView(routing.parseSitePath('/topics/'),{list:[],topicGroups:[{id:1,name:'空分组',totalCount:0,list:[]}],totalCount:0},'/topics/');
|
|
assert.equal(list.html,'');
|
|
assert.equal(list.subtitle,'');
|
|
const detail=channel.channelView(routing.parseSitePath('/topics/city/'),{id:7,slug:'city',name:'都市精选',list:[],totalCount:0,moreTopics:[]},'/topics/city/');
|
|
assert.ok(!detail.html.includes('channel-summary'));
|
|
assert.ok(!detail.html.includes('class="cards"'));
|
|
assert.ok(!detail.html.includes('更多专题'));
|
|
assert.ok(!detail.html.includes('ui-empty-state'));
|
|
const card=channel.channelView(routing.parseSitePath('/topics/'),{topicGroups:[{id:1,name:'有专题',totalCount:1,list:[{id:8,name:'无数量专题',slug:'empty'}]}]},'/topics/');
|
|
assert.ok(card.html.includes('无数量专题'));
|
|
assert.ok(!card.html.includes('0部短剧'));
|
|
});
|
|
|
|
test('theme filters use the actual theme catalog and all/list rather than topic detail',async()=>{
|
|
const calls=[];
|
|
const data=await channel.loadChannel({get:async(endpoint,query)=>{calls.push({endpoint,query});return endpoint==='theme/list'?{list:[{id:9,slug:'urban',name:'都市题材'}]}:{list:[{id:7}],totalCount:30};}},routing.parseSitePath('/theme/urban/page/2/'),'?sort=3&updateStatus=2');
|
|
assert.deepEqual(plain(calls),[{endpoint:'theme/list',query:{}},{endpoint:'playlet/all/list',query:{pageNum:2,pageSize:24,themeSlug:'urban',sortType:3,updateStatus:2}}]);
|
|
assert.equal(data.id,9);assert.equal(data.name,'都市题材');
|
|
});
|