douyin-release-gate / verify (push) Failing after 18m54s
Add Douyin notification polling, event details, and manual multi-account private messaging. Refine environment memory settings, account operations, login collection recovery, and message UI; update tests and documentation.
104 lines
8.8 KiB
JavaScript
104 lines
8.8 KiB
JavaScript
const test = require('node:test');
|
||
const assert = require('node:assert/strict');
|
||
const { readFileSync } = require('node:fs');
|
||
const { resolve } = require('node:path');
|
||
const Module = require('node:module');
|
||
const ts = require('typescript');
|
||
const React = require('react');
|
||
|
||
function transpile(file) { return ts.transpileModule(readFileSync(file,'utf8'), {compilerOptions:{module:ts.ModuleKind.CommonJS,jsx:ts.JsxEmit.ReactJSX,target:ts.ScriptTarget.ES2020}}).outputText; }
|
||
function harness(options={}) {
|
||
const states=[],effects=[],queued=[],calls=[];
|
||
let cursor=0;
|
||
const antd={};
|
||
for(const name of ['Alert','Avatar','Button','Card','Flex','Select','Table','Tag','Tooltip']){antd[name]=()=>null;antd[name].displayName=name;}
|
||
antd.Typography={Text:()=>null,Paragraph:()=>null,Link:()=>null};
|
||
const utilityFile=resolve(__dirname,'../src/utils/account-events.ts');
|
||
const utility=new Module(utilityFile,module);utility._compile(transpile(utilityFile),utilityFile);
|
||
const file=resolve(__dirname,'../src/pages/creator/events/index.tsx');
|
||
const loaded=new Module(file,module);loaded.filename=file;
|
||
loaded.require=id=>{
|
||
if(id==='react')return{useState(initial){const index=cursor++;if(!(index in states))states[index]=initial;return[states[index],value=>{states[index]=typeof value==='function'?value(states[index]):value;}]},
|
||
useEffect(callback,deps){const index=cursor++;const prior=effects[index];if(!prior||deps.some((v,i)=>!Object.is(v,prior.deps[i]))){if(prior?.cleanup)prior.cleanup();effects[index]={deps};queued.push(()=>{effects[index].cleanup=callback();});}}};
|
||
if(id==='react/jsx-runtime')return require(id);
|
||
if(id==='antd')return antd;
|
||
if(id==='@ant-design/icons')return{ReloadOutlined:()=>null,VideoCameraOutlined:()=>null};
|
||
if(id==='@/components/creator/WorksGallery')return{WorkCover:Object.assign(()=>null,{displayName:'WorkCover'})};
|
||
if(id==='@/utils/account-events')return utility.exports;
|
||
if(id==='@/utils/table')return{useTableSticky:()=>false,tablePagination:()=>({})};
|
||
if(id==='@/utils/helpers')return{dateTime:value=>`time:${value}`,conflictMessage:(cause,fallback)=>cause.message||fallback};
|
||
if(id==='@/services/api')return{creatorGet:async path=>{calls.push(path);if(options.get)return options.get(path);if(path==='/creator/accounts')return options.accounts||[];return options.page||{data:[],total:0};}};
|
||
return require(id);
|
||
};
|
||
loaded._compile(transpile(file),file);
|
||
return{antd,calls,render(){cursor=0;return loaded.exports.default();},async effects(){while(queued.length)queued.shift()();for(let i=0;i<8;i++)await Promise.resolve();},dispose(){for(const effect of effects)effect?.cleanup?.();}};
|
||
}
|
||
function all(node,predicate){const result=[];function visit(value){if(Array.isArray(value))return value.forEach(visit);if(!React.isValidElement(value))return;if(predicate(value))result.push(value);visit(value.props.children);}visit(node);return result;}
|
||
function text(node){if(node==null)return'';if(typeof node==='string'||typeof node==='number')return String(node);if(Array.isArray(node))return node.map(text).join('');return React.isValidElement(node)?text(node.props.children):'';}
|
||
function select(h,tree,label){return all(tree,node=>node.type===h.antd.Select&&node.props['aria-label']===label)[0];}
|
||
function table(h,tree){return all(tree,node=>node.type===h.antd.Table)[0];}
|
||
|
||
test('only enabled accounts are offered, event filters are server-wide and changes reset page',async t=>{
|
||
const h=harness({accounts:[{id:'on',name:'开启账号',platform_account_key:'1',listener_enabled:true},{id:'off',name:'关闭账号',listener_enabled:false}],page:{data:[{id:'event'}],total:40}});t.after(()=>h.dispose());
|
||
h.render();await h.effects();let tree=h.render();
|
||
assert.deepEqual(select(h,tree,'所属账号').props.options.map(o=>o.value),['on']);
|
||
assert.equal(select(h,tree,'所属账号').props.options[0].label,'开启账号');
|
||
assert.equal(table(h,tree).props.pagination.total,40);
|
||
table(h,tree).props.pagination.onChange(2,25);h.render();await h.effects();tree=h.render();
|
||
assert.equal(table(h,tree).props.pagination.current,2);
|
||
select(h,tree,'所属账号').props.onChange('on');h.render();await h.effects();tree=h.render();
|
||
assert.equal(table(h,tree).props.pagination.current,1);
|
||
select(h,tree,'事件类型').props.onChange('dm');h.render();await h.effects();tree=h.render();
|
||
select(h,tree,'接收时间范围').props.onChange(24);h.render();await h.effects();tree=h.render();
|
||
const query=new URLSearchParams(h.calls.filter(p=>p.startsWith('/creator/events')).at(-1).split('?')[1]);
|
||
assert.equal(query.get('event_type'),'dm');assert.equal(query.get('account_id'),'on');assert.equal(query.get('hours'),'24');assert.equal(query.get('page'),'1');
|
||
table(h,tree).props.pagination.onChange(3,50);h.render();await h.effects();tree=h.render();
|
||
assert.equal(table(h,tree).props.pagination.current,1);assert.equal(table(h,tree).props.pagination.pageSize,50);
|
||
all(tree,node=>node.type===h.antd.Button)[0].props.onClick();h.render();await h.effects();
|
||
assert.equal(h.calls.filter(p=>p.startsWith('/creator/events')).length,7);
|
||
});
|
||
|
||
test('event contents distinguish missing data, non-text messages and baseline history',async t=>{
|
||
const h=harness();t.after(()=>h.dispose());h.render();await h.effects();const cols=table(h,h.render()).props.columns;
|
||
const column=title=>cols.find(c=>c.title===title);
|
||
assert.equal(text(column('事件类型').render('comment')),'评论');
|
||
assert.equal(text(column('内容').render(null,{message_text:'测试内容',message_type:'text'})),'测试内容');
|
||
assert.equal(text(column('内容').render(null,{message_text:'',message_type:'image'})),'图片消息');
|
||
assert.equal(text(column('内容').render(null,{message_text:'',message_type:'text'})),'—');
|
||
assert.equal(column('发生时间').render(null),'—');assert.equal(column('发生时间').render('date'),'time:date');
|
||
assert.equal(column('对应作品').render(''),'—');
|
||
const link=column('对应作品').render('123',{id:'event',work_id:'123',work_url:'https://www.douyin.com/video/123',has_work_cover:true});
|
||
assert.equal(link.props.href,'https://www.douyin.com/video/123');
|
||
const cover=all(link,n=>n.type.displayName==='WorkCover')[0];
|
||
assert.equal(cover.props.width,48);assert.equal(cover.props.coverPath,'/creator/events/event/cover');
|
||
const missing=column('对应作品').render('123',{id:'event',work_id:'123',work_url:'',has_work_cover:false,work_cover_error:'下载失败'});
|
||
assert.equal(missing.props.href,'https://www.douyin.com/video/123');
|
||
assert.equal(all(missing,n=>n.type===h.antd.Avatar)[0].props.size,48);
|
||
assert.equal(all(missing,n=>n.type===h.antd.Tooltip)[0].props.title,'下载失败');
|
||
assert.equal(text(column('来源').render(true)),'历史记录');assert.equal(text(column('来源').render(false)),'开启后通知');
|
||
const user=column('互动用户').render(null,{interactor_uid:'22',interactor_name:'用户',interactor_sec_uid:'MS4wLjABAAAAprofile'});
|
||
const profile=all(user,n=>n.type===h.antd.Typography.Link)[0];
|
||
assert.equal(profile.props.href,'https://www.douyin.com/user/MS4wLjABAAAAprofile');
|
||
assert.equal(profile.props.target,'_blank');assert.match(text(user),/用户/);assert.match(text(user),/UID:22/);
|
||
const unknown=column('互动用户').render(null,{interactor_uid:'22',interactor_name:'',interactor_sec_uid:''});
|
||
assert.equal(all(unknown,n=>n.type===h.antd.Typography.Link).length,0);assert.match(text(unknown),/昵称未获取/);
|
||
assert.equal(text(column('所属账号').render(null,{account_name:'账号',account_uid:'1'})),'账号');
|
||
assert.deepEqual(cols.slice(0,2).map(c=>c.title),['发生时间','接收时间']);
|
||
});
|
||
|
||
test('read failures are visible and retries do not fake an empty success',async t=>{
|
||
let failed=true;const h=harness({get:async path=>{if(failed)throw new Error('服务不可用');return path==='/creator/accounts'?[]:{data:[{id:'1'}],total:1};}});t.after(()=>h.dispose());
|
||
h.render();await h.effects();let tree=h.render();
|
||
const error=all(tree,n=>n.type===h.antd.Alert)[0];assert.equal(error.props.title,'事件读取失败');assert.equal(error.props.description,'服务不可用');
|
||
failed=false;all(tree,n=>n.type===h.antd.Button)[0].props.onClick();h.render();await h.effects();tree=h.render();
|
||
assert.equal(all(tree,n=>n.type===h.antd.Alert).length,0);assert.equal(table(h,tree).props.dataSource.length,1);
|
||
});
|
||
|
||
test('a late response for previous filters cannot overwrite current events',async t=>{
|
||
let resolveOld;
|
||
const h=harness({get:path=>{if(path==='/creator/accounts')return Promise.resolve([]);if(path.includes('event_type=comment'))return Promise.resolve({data:[{id:'current'}],total:1});return new Promise(resolve=>{resolveOld=resolve;});}});t.after(()=>h.dispose());
|
||
let tree=h.render();await h.effects();select(h,tree,'事件类型').props.onChange('comment');h.render();await h.effects();
|
||
resolveOld({data:[{id:'old'}],total:99});await h.effects();tree=h.render();
|
||
assert.equal(table(h,tree).props.dataSource[0].id,'current');assert.equal(table(h,tree).props.pagination.total,1);
|
||
});
|