migration.test.js 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
  1. import { test } from 'node:test'
  2. import assert from 'node:assert/strict'
  3. import path from 'node:path'
  4. import { promises as fs } from 'node:fs'
  5. import { fileURLToPath } from 'node:url'
  6. const REF = fileURLToPath(new URL('../../references/', import.meta.url))
  7. // 真·v6 遗毒模式(不含会误伤 craft 的「评分」泛词)
  8. const V6 = /webnovel-(write|plan|init|review|query|dashboard|doctor|learn)|story-system|\/webnovel-|state\.json|设定集\/|webnovel\.py|创意约束|Pack [MU][0-9]|Context Agent|Checkers|评.{0,2}文笔/
  9. async function walk(dir) {
  10. const out = []
  11. let entries
  12. try {
  13. entries = await fs.readdir(dir, { withFileTypes: true })
  14. } catch {
  15. return out
  16. }
  17. for (const e of entries) {
  18. const full = path.join(dir, e.name)
  19. if (e.isDirectory()) out.push(...(await walk(full)))
  20. else out.push(full)
  21. }
  22. return out
  23. }
  24. test('迁移树零 v6 遗毒(逐文件 grep 把关)', async () => {
  25. const files = await walk(REF)
  26. assert.ok(files.length > 0, 'references/ 应已有迁移产物')
  27. const dirty = []
  28. for (const f of files) {
  29. // 迁移报告.md 是说明文档,正当点名它清除/弃用的 v6 物件,不参与内容洁净度扫描
  30. if (path.basename(f) === '迁移报告.md') continue
  31. const content = await fs.readFile(f, 'utf8')
  32. if (V6.test(content)) dirty.push(path.relative(REF, f))
  33. }
  34. assert.deepEqual(dirty, [], '这些迁移文件仍含 v6 遗毒:' + dirty.join('、'))
  35. })
  36. test('题材单一真源:CSV 在、markdown 双表不迁入', async () => {
  37. await fs.access(path.join(REF, '题材模板', 'genre-index.csv'))
  38. const files = (await walk(REF)).map((f) => path.basename(f))
  39. assert.ok(!files.includes('genre-profiles.md'), '不应迁入 markdown 题材双表 genre-profiles.md')
  40. assert.ok(!files.includes('genre-tropes.md'), '不应迁入 markdown 题材双表 genre-tropes.md')
  41. })
  42. test('CSV v6 列已删(适用技能/推荐检索表)', async () => {
  43. const a = await fs.readFile(path.join(REF, '题材模板', '题材与调性推理.csv'), 'utf8')
  44. assert.ok(!a.includes('适用技能'), '题材与调性推理.csv 应删 适用技能 列')
  45. assert.ok(!a.includes('推荐基础检索表'), '应删 推荐基础检索表 列')
  46. const b = await fs.readFile(path.join(REF, '爽点节奏', '爽点与节奏.csv'), 'utf8')
  47. assert.ok(!b.includes('适用技能'), '爽点与节奏.csv 应删 适用技能 列')
  48. })
  49. test('题材模板正文迁入且剥离创意约束段', async () => {
  50. const xiuxian = await fs.readFile(path.join(REF, '题材模板', 'genres', '修仙.md'), 'utf8')
  51. assert.match(xiuxian, /核心流派细分/, 'craft 内容保留')
  52. assert.ok(!xiuxian.includes('创意约束'), '剥离 v6 创意约束段')
  53. })
  54. test('迁移报告含真源选定表', async () => {
  55. const report = await fs.readFile(path.join(REF, '迁移报告.md'), 'utf8')
  56. assert.match(report, /真源选定/)
  57. assert.match(report, /genre-index\.csv/)
  58. assert.match(report, /reading-power-taxonomy/)
  59. })