session.test.js 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. import { test } from 'node:test'
  2. import assert from 'node:assert/strict'
  3. import os from 'node:os'
  4. import path from 'node:path'
  5. import { promises as fs } from 'node:fs'
  6. import {
  7. readBooksRegistry,
  8. scanRebuildBooks,
  9. assembleSessionContext,
  10. } from '../../src/session/index.js'
  11. async function tmpWorkdir() {
  12. const root = await fs.mkdtemp(path.join(os.tmpdir(), 'wnw-wd-'))
  13. return { root, cleanup: () => fs.rm(root, { recursive: true, force: true }) }
  14. }
  15. async function writeRegistry(root, lines) {
  16. await fs.mkdir(path.join(root, '.webnovel'), { recursive: true })
  17. await fs.writeFile(path.join(root, '.webnovel', 'books.jsonl'), lines.join('\n') + '\n', 'utf8')
  18. }
  19. async function makeBookDir(root, name) {
  20. await fs.mkdir(path.join(root, name), { recursive: true })
  21. await fs.writeFile(path.join(root, name, 'book.yaml'), `spec_version: "7.0"\n书名: ${name}\n`, 'utf8')
  22. }
  23. test('readBooksRegistry:解析合法行,损坏行跳过并计数', async () => {
  24. const { root, cleanup } = await tmpWorkdir()
  25. try {
  26. await writeRegistry(root, [
  27. JSON.stringify({ 书名: '剑起青云', 目录: '剑起青云', 当前: true }),
  28. '{坏的 json',
  29. JSON.stringify({ 书名: '星海', 目录: '星海', 当前: false }),
  30. ])
  31. const r = await readBooksRegistry(root)
  32. assert.equal(r.ok, true)
  33. assert.equal(r.books.length, 2)
  34. assert.equal(r.corrupt, 1)
  35. } finally { await cleanup() }
  36. })
  37. test('readBooksRegistry:缺文件 → missing=true 不抛', async () => {
  38. const { root, cleanup } = await tmpWorkdir()
  39. try {
  40. const r = await readBooksRegistry(root)
  41. assert.equal(r.missing, true)
  42. assert.equal(r.books.length, 0)
  43. } finally { await cleanup() }
  44. })
  45. test('scanRebuildBooks:扫含 book.yaml 子目录重建,需作者选当前书', async () => {
  46. const { root, cleanup } = await tmpWorkdir()
  47. try {
  48. await makeBookDir(root, '剑起青云')
  49. await makeBookDir(root, '星海')
  50. const r = await scanRebuildBooks(root)
  51. assert.equal(r.ok, true)
  52. assert.equal(r.books.length, 2)
  53. assert.ok(r.books.some((b) => b.书名 === '剑起青云'))
  54. assert.equal(r.needsAuthorPick, true)
  55. } finally { await cleanup() }
  56. })
  57. test('assembleSessionContext:有登记 → 注入含当前书与本数', async () => {
  58. const { root, cleanup } = await tmpWorkdir()
  59. try {
  60. await writeRegistry(root, [
  61. JSON.stringify({ 书名: '剑起青云', 目录: '剑起青云', 当前: true }),
  62. JSON.stringify({ 书名: '星海', 目录: '星海', 当前: false }),
  63. ])
  64. const r = await assembleSessionContext(root)
  65. assert.equal(r.ok, true)
  66. assert.match(r.text, /剑起青云/)
  67. assert.match(r.text, /2 本/)
  68. assert.equal(r.current.书名, '剑起青云')
  69. } finally { await cleanup() }
  70. })
  71. test('assembleSessionContext:登记缺失 → 扫描重建并标记', async () => {
  72. const { root, cleanup } = await tmpWorkdir()
  73. try {
  74. await makeBookDir(root, '剑起青云')
  75. const r = await assembleSessionContext(root)
  76. assert.equal(r.ok, true)
  77. assert.equal(r.rebuilt, true)
  78. assert.match(r.text, /剑起青云/)
  79. } finally { await cleanup() }
  80. })
  81. test('无 hook 等价:hook 入口与状态机入口调同一函数 → 注入文本逐字一致', async () => {
  82. const { root, cleanup } = await tmpWorkdir()
  83. try {
  84. await writeRegistry(root, [JSON.stringify({ 书名: '剑起青云', 目录: '剑起青云', 当前: true })])
  85. const hookText = (await assembleSessionContext(root)).text // Claude Code SessionStart hook
  86. const smText = (await assembleSessionContext(root)).text // 无 hook 宿主由状态机入口调
  87. assert.equal(hookText, smText)
  88. } finally { await cleanup() }
  89. })