local-seed-to-intake-worklist-smoke.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. #!/usr/bin/env node
  2. const fs = require('fs');
  3. const os = require('os');
  4. const path = require('path');
  5. const { spawnSync } = require('child_process');
  6. const { buildLocalSeedToIntakeWorklist } = require('./local-seed-to-intake-worklist');
  7. const ROOT = path.resolve(__dirname, '..');
  8. function main() {
  9. const fakeSeedIndex = {
  10. proofLevel: 'not_business_proof',
  11. directCustomerProof: false,
  12. canCloseProofGap: false,
  13. materialCount: 2,
  14. existingMaterialCount: 2,
  15. materials: [
  16. {
  17. id: 'dha-brief-workbook',
  18. path: '../dha_brief.xlsx',
  19. exists: true,
  20. workbook: {
  21. sheets: [
  22. {
  23. name: '参考链接解析',
  24. sampleRows: [
  25. ['视频', 'http://xhslink.com/o/example', 'note-1', '参考妈妈', 'uid-1', 'xhs-1', '', '', '已解析', 'https://www.xiaohongshu.com/discovery/item/note-1?xsec_token=secret#frag']
  26. ]
  27. }
  28. ]
  29. }
  30. },
  31. {
  32. id: 'dha-poc-recommendation-workbook',
  33. path: '../output/dha-tihao-poc/poc.xlsx',
  34. exists: true,
  35. workbook: {
  36. sheets: [
  37. {
  38. name: '主推账号',
  39. sampleRows: [
  40. ['主推', '小爱十月', 'aixiaolan94', '山东 潍坊', '30454', '3800', '4500', '视频', '4500', '172419', '6545', '0.026'],
  41. ['主推', '我是在在吖', 'wangshuyun711', '浙江 宁波', '16631', '2400', '3200', '图文', '2400', '7475', '815', '0.321']
  42. ]
  43. },
  44. {
  45. name: '备选复核',
  46. sampleRows: [
  47. ['备选/补充', '育婴师 Joyce', '567679811', '安徽 亳州', '26297', '4000', '4800', '图文', '4000', '18964', '0', '0.211']
  48. ]
  49. }
  50. ]
  51. }
  52. }
  53. ]
  54. };
  55. const summary = buildLocalSeedToIntakeWorklist({ root: ROOT, seedIndex: fakeSeedIndex });
  56. assert(summary.proofLevel === 'not_business_proof', 'worklist should stay not_business_proof');
  57. assert(summary.directCustomerProof === false, 'worklist should not be direct customer proof');
  58. assert(summary.canCloseProofGap === false, 'worklist should not close proof gaps');
  59. assert(summary.historyDraftRows.length === 3, 'worklist should expand candidate seeds into history draft rows');
  60. assert(summary.reviewDraftRows.length === 3, 'worklist should create manual review draft rows');
  61. assert(summary.referenceSeedRows.length === 1, 'worklist should extract reference seed rows');
  62. assert(summary.referenceSeedRows[0].finalLink === 'https://www.xiaohongshu.com/discovery/item/note-1', 'worklist should sanitize reference URLs');
  63. assert(summary.candidateSeedRows.length === 3, 'worklist should extract candidate seed rows');
  64. assert(summary.proofGapMaterialBridge.length === 3, 'worklist should map seed material to the three real proof gaps');
  65. assert(summary.videoWorklistRows.some(row => row.role === '候选视频' && row.creatorName === '小爱十月'), 'worklist should create candidate video fill row');
  66. assert(summary.missingProofRequirements.includes('customer-effect:audit overallPass=true'), 'worklist should keep customer-effect missing proof');
  67. const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'tihao-seed-worklist-'));
  68. const output = path.join(temp, 'worklist');
  69. const result = spawnSync(process.execPath, [
  70. 'scripts/local-seed-to-intake-worklist.js',
  71. '--workspace-root',
  72. path.join(temp, 'workspace'),
  73. '--outputs',
  74. path.join(temp, 'outputs'),
  75. '--output',
  76. output,
  77. '--strict'
  78. ], {
  79. cwd: ROOT,
  80. encoding: 'utf8',
  81. shell: false
  82. });
  83. if (result.stdout) process.stdout.write(result.stdout);
  84. if (result.stderr) process.stderr.write(result.stderr);
  85. assert(result.status === 0, 'CLI should pass even when no seed files exist');
  86. const cliSummary = readJson(path.join(output, 'local-seed-to-intake-worklist-summary.json'));
  87. const report = fs.readFileSync(path.join(output, 'local-seed-to-intake-worklist.md'), 'utf8');
  88. const csv = fs.readFileSync(path.join(output, 'local-seed-to-intake-worklist.csv'), 'utf8');
  89. assert(cliSummary.proofLevel === 'not_business_proof', 'CLI summary should keep proof boundary');
  90. assert(cliSummary.canCloseProofGap === false, 'CLI summary should not close proof gap');
  91. assert(report.includes('本地种子转 intake 补表 worklist'), 'report should render title');
  92. assert(csv.includes('类型'), 'unified CSV should render headers');
  93. assert(fs.existsSync(path.join(output, 'history-intake-draft.csv')), 'CLI should write history draft CSV');
  94. assert(fs.existsSync(path.join(output, 'manual-review-draft.csv')), 'CLI should write manual review draft CSV');
  95. assert(fs.existsSync(path.join(output, 'video-resource-worklist.csv')), 'CLI should write video worklist CSV');
  96. assert(fs.existsSync(path.join(output, 'candidate-seed-worklist.csv')), 'CLI should write candidate worklist CSV');
  97. assert(!report.includes('xsec_token'), 'report should not leak xsec token');
  98. assert(!csv.includes('xsec_token'), 'csv should not leak xsec token');
  99. console.log(JSON.stringify({
  100. ok: true,
  101. historyDraftRows: summary.historyDraftRows.length,
  102. reviewDraftRows: summary.reviewDraftRows.length,
  103. referenceSeedRows: summary.referenceSeedRows.length,
  104. candidateSeedRows: summary.candidateSeedRows.length,
  105. cliHistoryDraftRows: cliSummary.historyDraftRows.length
  106. }, null, 2));
  107. }
  108. function readJson(file) {
  109. return JSON.parse(fs.readFileSync(file, 'utf8'));
  110. }
  111. function assert(condition, message) {
  112. if (!condition) throw new Error(message);
  113. }
  114. main();