local-seed-material-index-smoke.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  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 { sanitizeCell, sanitizePublicUrl } = require('./local-seed-material-index');
  7. const ROOT = path.resolve(__dirname, '..');
  8. function main() {
  9. const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'tihao-local-seed-index-'));
  10. const workspaceRoot = path.join(temp, 'workspace');
  11. const outputs = path.join(temp, 'outputs');
  12. const output = path.join(temp, 'index');
  13. fs.mkdirSync(workspaceRoot, { recursive: true });
  14. fs.mkdirSync(outputs, { recursive: true });
  15. writeJson(path.join(outputs, 'optimization-pipeline-latest', 'optimization-pipeline-summary.json'), {
  16. readyForClaim: false,
  17. counts: { pass: 6, fail: 3 },
  18. inputs: { currentManualSupplementCount: '0' },
  19. proofGapClosure: { complete: false, openCount: 5, closedCount: 0 }
  20. });
  21. writeJson(path.join(outputs, 'optimization-pipeline-latest', 'history-audit', 'historical-dataset-audit.json'), {
  22. briefCount: 1,
  23. acceptance: {
  24. readyForCustomerEffectProof: false,
  25. readyForLongRun: false,
  26. categoryCoverageMet: false
  27. }
  28. });
  29. writeJson(path.join(outputs, 'optimization-pipeline-latest', 'customer-effect-audit', 'customer-effect-summary.json'), {
  30. acceptance: {
  31. overallPass: false,
  32. customerSelectedRateMeasured: false,
  33. referenceCustomerSelectedRateMeasured: false,
  34. historyReadyForCustomerEffectProof: false
  35. },
  36. missingEvidence: ['缺少客户最终选择字段']
  37. });
  38. writeJson(path.join(outputs, 'overnight-quality-live', 'aggregate-summary.json'), {
  39. manifest: { liveEnabled: true },
  40. acceptance: { overallPass: true, failedGateCount: 0 },
  41. failureCount: 0,
  42. runCount: 2,
  43. fixtureCount: 1,
  44. variantCount: 2
  45. });
  46. const result = spawnSync(process.execPath, [
  47. 'scripts/local-seed-material-index.js',
  48. '--workspace-root',
  49. workspaceRoot,
  50. '--outputs',
  51. outputs,
  52. '--output',
  53. output,
  54. '--strict'
  55. ], {
  56. cwd: ROOT,
  57. encoding: 'utf8',
  58. shell: false
  59. });
  60. if (result.stdout) process.stdout.write(result.stdout);
  61. if (result.stderr) process.stderr.write(result.stderr);
  62. assert(result.status === 0, 'local seed material index should pass without local workbooks');
  63. const summary = readJson(path.join(output, 'local-seed-material-index-summary.json'));
  64. const report = fs.readFileSync(path.join(output, 'local-seed-material-index.md'), 'utf8');
  65. const csv = fs.readFileSync(path.join(output, 'local-seed-material-index.csv'), 'utf8');
  66. assert(summary.proofLevel === 'not_business_proof', 'summary proof level should be not_business_proof');
  67. assert(summary.directCustomerProof === false, 'summary should not be direct customer proof');
  68. assert(summary.canCloseProofGap === false, 'summary should not close proof gaps');
  69. assert(summary.existingMaterialCount === 0, 'empty smoke workspace should have zero local materials');
  70. assert(summary.pipelineArtifacts.pipeline.readyForClaim === false, 'pipeline boundary should be retained');
  71. assert(summary.pipelineArtifacts.historyAudit.readyForCustomerEffectProof === false, 'history audit should remain not ready');
  72. assert(summary.pipelineArtifacts.customerEffect.overallPass === false, 'customer effect should remain not passed');
  73. assert(summary.liveAutomationCandidates.length === 1, 'live automation candidate should be indexed');
  74. assert(summary.liveAutomationCandidates[0].liveAutomationPass === true, 'live automation candidate should be detected');
  75. assert(summary.liveAutomationCandidates[0].canCloseProofGapNow === false, 'live automation candidate should not close proof gap directly');
  76. const dirtyShareUrl = 'https://www.xiaohongshu.com/discovery/item/abc123?app_version=9.26.0&xsec_token=secret-token&author_share=1#frag';
  77. const cleanShareUrl = sanitizePublicUrl(dirtyShareUrl);
  78. assert(cleanShareUrl === 'https://www.xiaohongshu.com/discovery/item/abc123', 'share URL should drop query and hash parameters');
  79. assert(!sanitizeCell(dirtyShareUrl).includes('xsec_token'), 'display cells should not expose share token parameters');
  80. const bearerValue = ['Authorization: Bearer', 'abcdefghijklmnopqrstuvwxyz'].join(' ');
  81. assert(sanitizeCell(bearerValue) === '[REDACTED_SECRET_LIKE_VALUE]', 'secret-like values should be redacted');
  82. assert(report.includes('本地种子材料索引'), 'report should render title');
  83. assert(report.includes('canCloseProofGap:false'), 'report should render proof boundary');
  84. assert(csv.includes('local-material'), 'csv should include local material rows');
  85. console.log(JSON.stringify({
  86. ok: true,
  87. materialCount: summary.materialCount,
  88. existingMaterialCount: summary.existingMaterialCount,
  89. liveAutomationCandidates: summary.liveAutomationCandidates.length
  90. }, null, 2));
  91. }
  92. function writeJson(file, value) {
  93. fs.mkdirSync(path.dirname(file), { recursive: true });
  94. fs.writeFileSync(file, JSON.stringify(value, null, 2), 'utf8');
  95. }
  96. function readJson(file) {
  97. return JSON.parse(fs.readFileSync(file, 'utf8'));
  98. }
  99. function assert(condition, message) {
  100. if (!condition) throw new Error(message);
  101. }
  102. main();