ChapterWriter.test.js 3.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  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 { ChapterWriter } from '../../../src/storage/adapters/ChapterWriter.js'
  6. import { makeRepo, cleanup, read } from '../_tmprepo.js'
  7. const listChapters = (root) =>
  8. fs.readdir(path.join(root, '定稿', '正文')).then((fs_) => fs_.filter((f) => f.endsWith('.md')))
  9. test('ChapterWriter.writeChapter 写出定稿正文(防呆 front matter + 正文)', async () => {
  10. const root = await makeRepo()
  11. try {
  12. const w = new ChapterWriter(root)
  13. const fm = {
  14. 章号: 152,
  15. 标题: '北境的雪',
  16. 卷: 5,
  17. 字数: 3120,
  18. 章定位: '推进',
  19. 伏笔: ['埋下 伏笔-058'],
  20. }
  21. const r = await w.writeChapter(152, fm, '正文第一段。\n')
  22. assert.equal(r.ok, true)
  23. const content = await read(root, '定稿/正文/0152-北境的雪.md')
  24. assert.match(content, /^---\n/)
  25. assert.match(content, /标题: 北境的雪/)
  26. assert.match(content, /伏笔:\n {2}- 埋下 伏笔-058/) // 块列表防呆,两空格缩进
  27. assert.match(content, /正文第一段。/)
  28. } finally {
  29. await cleanup(root)
  30. }
  31. })
  32. test('ChapterWriter.writeChapter 危险值加引号(防呆)', async () => {
  33. const root = await makeRepo()
  34. try {
  35. const w = new ChapterWriter(root)
  36. const r = await w.writeChapter(1, { 章号: 1, 标题: '123', 卷: 1 }, '正文')
  37. assert.equal(r.ok, true)
  38. const content = await read(root, '定稿/正文/0001-123.md')
  39. assert.match(content, /标题: "123"/) // 纯数字串加引号
  40. } finally {
  41. await cleanup(root)
  42. }
  43. })
  44. test('ChapterWriter.writeChapter 重写同章改标题 → 删旧文件(P0-3a:不撞 PRIMARY KEY)', async () => {
  45. const root = await makeRepo()
  46. try {
  47. const w = new ChapterWriter(root)
  48. await w.writeChapter(5, { 章号: 5, 标题: '旧题', 卷: 1 }, '正文')
  49. let files = await listChapters(root)
  50. assert.equal(files.length, 1)
  51. await w.writeChapter(5, { 章号: 5, 标题: '新题', 卷: 1 }, '正文')
  52. files = await listChapters(root)
  53. assert.equal(files.length, 1, '重写同章应删旧文件,不留两条同章号 → scanChapters 不再撞 PRIMARY KEY')
  54. assert.ok(files[0].includes('新题'))
  55. } finally {
  56. await cleanup(root)
  57. }
  58. })
  59. test('ChapterWriter.writeChapter 标题含 Windows 非法字符 → 文件名净化不炸写盘(P2-1)', async () => {
  60. const root = await makeRepo()
  61. try {
  62. const w = new ChapterWriter(root)
  63. const r = await w.writeChapter(7, { 章号: 7, 标题: 'a:b?c*d"e|f<g>', 卷: 1 }, '正文')
  64. assert.equal(r.ok, true, r.error)
  65. const files = await listChapters(root)
  66. assert.equal(files.length, 1)
  67. assert.ok(!/[<>:"/\\|?*]/.test(files[0]), '文件名应剔除 Windows 非法字符')
  68. // 标题本体在 front matter 里保留(可能被 YAML 加引号,只断子串)
  69. const content = await read(root, `定稿/正文/${files[0]}`)
  70. assert.match(content, /a:b\?c\*d/)
  71. } finally {
  72. await cleanup(root)
  73. }
  74. })