ChapterReader.test.js 2.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. import { test } from 'node:test'
  2. import assert from 'node:assert/strict'
  3. import path from 'node:path'
  4. import { fileURLToPath } from 'node:url'
  5. import { ChapterReader } from '../../../src/storage/adapters/ChapterReader.js'
  6. const __dirname = path.dirname(fileURLToPath(import.meta.url))
  7. const fixtureRoot = path.join(__dirname, '../../fixtures/sample-book')
  8. test('readFrontMatter:读取章节 front matter', async () => {
  9. const reader = new ChapterReader(fixtureRoot)
  10. const result = await reader.readFrontMatter(1)
  11. assert.equal(result.ok, true)
  12. assert.equal(result.data.章号, 1)
  13. assert.equal(result.data.标题, '开局')
  14. assert.equal(result.data.卷, 1)
  15. assert.equal(result.data.视角, '林晚')
  16. })
  17. test('readFrontMatter:不存在的章号', async () => {
  18. const reader = new ChapterReader(fixtureRoot)
  19. const result = await reader.readFrontMatter(999)
  20. assert.equal(result.ok, false)
  21. assert.ok(result.error.includes('不存在'))
  22. })
  23. test('readBody:读取章节正文', async () => {
  24. const reader = new ChapterReader(fixtureRoot)
  25. const result = await reader.readBody(1)
  26. assert.equal(result.ok, true)
  27. assert.ok(result.body.includes('林晚抬头望着宗门大殿'))
  28. assert.ok(result.body.includes('消失在夜色中'))
  29. assert.ok(!result.body.includes('---')) // 不含 front matter
  30. })
  31. test('readTail:读取章节末尾 N 字', async () => {
  32. const reader = new ChapterReader(fixtureRoot)
  33. const result = await reader.readTail(1, 50)
  34. assert.equal(result.ok, true)
  35. assert.ok(result.text.includes('消失在夜色中'))
  36. assert.ok(result.text.length <= 60) // 允许少量误差(字符 vs 字)
  37. })
  38. test('readHead:读取章节开头 N 字', async () => {
  39. const reader = new ChapterReader(fixtureRoot)
  40. const result = await reader.readHead(1, 20)
  41. assert.equal(result.ok, true)
  42. assert.ok(result.text.includes('林晚抬头'))
  43. })
  44. // A6:接口形状不随缓存冷热漂移——缓存命中与文件降级都输出中文键
  45. test('readFrontMatter:缓存命中与文件降级输出同一形状(中文键)', async () => {
  46. const fakeCache = {
  47. query: async () => [
  48. {
  49. chapter_num: 1, title: '开局', volume_num: 1, perspective: '林晚',
  50. story_time: null, word_count: 100, chapter_position: '开篇',
  51. hook_type: '悬念钩-强', mood_position: null, is_volume_end: 0,
  52. file_path: '/x/0001.md', is_key_chapter: 0,
  53. },
  54. ],
  55. }
  56. const warm = await new ChapterReader(fixtureRoot, fakeCache).readFrontMatter(1)
  57. const cold = await new ChapterReader(fixtureRoot).readFrontMatter(1)
  58. assert.equal(warm.ok, true)
  59. assert.equal(cold.ok, true)
  60. assert.equal(warm.data.章号, 1)
  61. assert.equal(warm.data.标题, '开局')
  62. assert.equal(warm.data.chapter_num, undefined, '缓存命中不得泄漏英文列名')
  63. assert.equal(warm.data.file_path, undefined, '不得泄漏文件路径')
  64. assert.equal(cold.data.标题, '开局')
  65. })