1
0

finalize.test.js 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  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 { execFile } from 'node:child_process'
  6. import { promisify } from 'node:util'
  7. import { finalizeChapter } from '../../src/finalize/index.js'
  8. import { createGit } from '../../src/finalize/git.js'
  9. import { gitBookCtx } from '../commands/_helper.js'
  10. const execFileAsync = promisify(execFile)
  11. function payload() {
  12. return {
  13. chapterNum: 3,
  14. frontMatter: {
  15. 章号: 3,
  16. 标题: '初露',
  17. 卷: 1,
  18. 视角: '林晚',
  19. 字数: 100,
  20. 章定位: '推进',
  21. 钩子: '危机钩-强',
  22. 情绪定位: '铺垫',
  23. },
  24. body: '林晚查到玉佩的第一条线索,心头巨震。\n',
  25. summary: '林晚查到玉佩的第一条线索。',
  26. threadUpdates: [
  27. { id: '伏笔-001', updates: { 最后推进章: 3 }, history: '第3章:推进——林晚查到线索' },
  28. ],
  29. characterUpdates: [{ name: '林晚', updates: { 最后变更章: 3 } }],
  30. timelineRows: [
  31. { volumeNum: 1, row: { 章: 3, 书内时间: '春月初三', 一句话事件: '查到玉佩线索', 在场: '林晚' } },
  32. ],
  33. commitLines: { 条目: '~伏笔-001', 设定: '林晚.最后变更章=3' },
  34. workspaceFiles: ['细纲.md'],
  35. }
  36. }
  37. test('finalizeChapter 正常定稿:落档 + git commit + 清工作区', async () => {
  38. const { ctx, cleanup } = await gitBookCtx()
  39. try {
  40. const git = createGit(ctx.repoPath)
  41. const before = await git.revCount()
  42. const r = await finalizeChapter(ctx, payload())
  43. assert.equal(r.ok, true, r.error)
  44. const ch = await fs.readFile(path.join(ctx.repoPath, '定稿/正文/0003-初露.md'), 'utf8')
  45. assert.match(ch, /标题: 初露/)
  46. await fs.access(path.join(ctx.repoPath, '定稿/摘要/章摘要/0003.md'))
  47. const log = await git.log()
  48. assert.match(log, /ch\(3\):/)
  49. assert.equal(await git.revCount(), before + 1)
  50. await assert.rejects(() => fs.access(path.join(ctx.repoPath, '工作区/细纲.md')))
  51. } finally {
  52. await cleanup()
  53. }
  54. })
  55. test('finalizeChapter 断电注入:无新 commit + 工作区原样 + 定稿净恢复(出口)', async () => {
  56. const { ctx, cleanup } = await gitBookCtx()
  57. try {
  58. const git = createGit(ctx.repoPath)
  59. const before = await git.revCount()
  60. const r = await finalizeChapter(ctx, payload(), { faultAfterWrite: true })
  61. assert.equal(r.ok, false)
  62. // 无新 commit
  63. assert.equal(await git.revCount(), before)
  64. // 工作区草稿原样(细纲还在)
  65. await fs.access(path.join(ctx.repoPath, '工作区/细纲.md'))
  66. // 定稿 未残留半成品章
  67. await assert.rejects(() => fs.access(path.join(ctx.repoPath, '定稿/正文/0003-初露.md')))
  68. // 定稿/大纲 工作树干净(回滚成功)
  69. const { stdout } = await execFileAsync(
  70. 'git',
  71. ['status', '--porcelain', '--', '定稿', '大纲'],
  72. { cwd: ctx.repoPath, encoding: 'utf8' }
  73. )
  74. assert.equal(stdout.trim(), '')
  75. } finally {
  76. await cleanup()
  77. }
  78. })
  79. test('finalizeChapter 定稿后删 .cache 全量重建一致(不变量 2)', async () => {
  80. const { ctx, cleanup } = await gitBookCtx()
  81. try {
  82. const r = await finalizeChapter(ctx, payload())
  83. assert.equal(r.ok, true, r.error)
  84. await ctx.cache.rebuildFromSource(ctx.repoPath)
  85. const rows = await ctx.cache.query('SELECT * FROM chapters WHERE chapter_num = 3')
  86. assert.equal(rows.length, 1)
  87. assert.equal(rows[0].title, '初露')
  88. } finally {
  89. await cleanup()
  90. }
  91. })