replay_test.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
  1. /**
  2. * 回放测试:直接加载生产文件 tiktok_stats.js,只把 httpGetJson 换成「录制的真实响应」,
  3. * 用真实数据验证 getCommentsViaMirror 是否能正确跨过 p_400 那个「假 hasMore=false」页。
  4. */
  5. const fs = require('fs');
  6. const path = require('path');
  7. const pagesDir = path.join(__dirname, 'pages');
  8. const rec = {}; // cursor -> parsed json
  9. fs.readdirSync(pagesDir).filter(f => /^p_\d+\.json$/.test(f)).forEach(f => {
  10. const cur = parseInt(f.match(/_(\d+)\./)[1], 10);
  11. rec[cur] = fs.readFileSync(path.join(pagesDir, f), 'utf8');
  12. });
  13. console.log('已录制页面 cursor:', Object.keys(rec).map(Number).sort((a, b) => a - b).join(', '));
  14. let httpCalls = 0;
  15. const mock = (url) => {
  16. httpCalls++;
  17. const m = /cursor=(\d+)/.exec(url);
  18. const cur = m ? parseInt(m[1], 10) : -1;
  19. if (rec[cur] === undefined) return null; // 未录制 → 模拟空页
  20. try { return JSON.parse(rec[cur]); } catch (e) { return null; }
  21. };
  22. const logs = [];
  23. const consoleStub = {
  24. log: (...a) => { const s = a.join(' '); logs.push(s); console.log(' | ' + s); },
  25. show: () => { }, error: () => { }, warn: () => { }
  26. };
  27. const filesStub = { exists: () => false, read: () => '', write: () => { }, getSdcardPath: () => '/sdcard', cwd: () => '.' };
  28. const javaStub = { lang: { Thread: { sleep: () => { } } } };
  29. let src = fs.readFileSync(path.join(__dirname, '..', 'tiktok_stats.js'), 'utf8');
  30. // 去掉结尾的 main() 调用,避免真跑主流程
  31. src = src.replace(/\nmain\(\);\s*$/, '\n');
  32. const factory = new Function('console', 'files', 'java', 'sleep', '__mock', '__dirname_', 'require', 'process',
  33. src + `
  34. ;httpGetJson = function (url, headers, tag) { return __mock(url, headers, tag); };
  35. ;return { CONFIG: CONFIG, getCommentsViaMirror: getCommentsViaMirror,
  36. getRepliesViaMirror: getRepliesViaMirror, normComment: normComment };
  37. `);
  38. const api = factory(consoleStub, filesStub, javaStub, () => { }, mock, __dirname, require, process);
  39. console.log('\n=== 开始回放 getCommentsViaMirror(limit=0) ===');
  40. const got = api.getCommentsViaMirror('7552202593888603410', 0);
  41. // 期望值
  42. let expectUnique = 0; const seenAll = {};
  43. Object.keys(rec).map(Number).sort((a, b) => a - b).forEach(cur => {
  44. const j = JSON.parse(rec[cur]);
  45. (j.data.comments || []).forEach(c => { if (!seenAll[c.id]) { seenAll[c.id] = 1; expectUnique++; } });
  46. });
  47. console.log('\n=== 结果 ===');
  48. console.log('HTTP 调用次数 :', httpCalls);
  49. console.log('抓到的评论数 :', got.length);
  50. console.log('录制数据的唯一评论数:', expectUnique);
  51. console.log('是否完全一致 :', got.length === expectUnique ? '✓ 通过' : '✗ 不一致');
  52. const bad = logs.filter(s => s.indexOf('假 hasMore=false') !== -1 || s.indexOf('hasMore=false,但实际返回') !== -1);
  53. console.log('识别出的假 hasMore :', bad.length, '次');
  54. bad.forEach(s => console.log(' →', s));
  55. const emptyLogs = logs.filter(s => s.indexOf('空(已重试') !== -1);
  56. console.log('空页提示 :', emptyLogs.length, '次(预期 3 次后停止)');
  57. console.log('前 5 条评论样例 :');
  58. got.slice(0, 5).forEach((c, i) => console.log(' ' + (i + 1) + '. [' + c.user + ' | 赞' + c.likes + ' | 回复' + c.replyCount + '] ' + c.text.slice(0, 40)));
  59. console.log('第 426 条之后是否还有数据(旧版就在此断掉):',
  60. got.length > 426 ? '✓ 有,共 ' + got.length + ' 条(旧版只有 426)' : '✗ 仍然断在 426');