Prechádzať zdrojové kódy

test(v7): M1 重建 R5b——补齐 6 个缺失 adapter 测试(AC11/AC12)

补全端口直测,满足"测试镜像 src"与"可单独 import 某一端口":
- Writer.test:ChapterWriter/ThreadLedgerWriter 调用抛 M2 占位错误(不静默成功)
- BookConfigReader.test:读 book.yaml 平铺字段 + 缺文件不崩
- SecretReader.test:基本信息/内容/不存在/无 cache 空数组
- OutlineReader.test:总纲小节/卷纲/不存在卷/列卷
- TimelineReader.test:卷时间线/按在场过滤/不存在卷空表
lingfengQAQ 1 deň pred
rodič
commit
cca0bd6038

+ 22 - 0
v7/test/storage/adapters/BookConfigReader.test.js

@@ -0,0 +1,22 @@
+import { test } from 'node:test'
+import assert from 'node:assert/strict'
+import path from 'node:path'
+import os from 'node:os'
+import { fileURLToPath } from 'node:url'
+import { BookConfigReader } from '../../../src/storage/adapters/BookConfigReader.js'
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url))
+const fixtureRoot = path.join(__dirname, '../../fixtures/sample-book')
+
+test('BookConfigReader 读 book.yaml 平铺字段', async () => {
+  const r = await new BookConfigReader(fixtureRoot).read()
+  assert.equal(r.ok, true)
+  assert.equal(r.data.书名, '测试书')
+  assert.equal(r.data.每章目标字数, 3000)
+  assert.equal(r.data.伏笔悬了太久章数, 10)
+})
+
+test('BookConfigReader 缺 book.yaml → ok=false(不崩)', async () => {
+  const r = await new BookConfigReader(path.join(os.tmpdir(), 'wnw-nonexistent-xyz')).read()
+  assert.equal(r.ok, false)
+})

+ 30 - 0
v7/test/storage/adapters/OutlineReader.test.js

@@ -0,0 +1,30 @@
+import { test } from 'node:test'
+import assert from 'node:assert/strict'
+import path from 'node:path'
+import { fileURLToPath } from 'node:url'
+import { OutlineReader } from '../../../src/storage/adapters/OutlineReader.js'
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url))
+const fixtureRoot = path.join(__dirname, '../../fixtures/sample-book')
+
+test('OutlineReader.readOutlineSection 读总纲指定小节', async () => {
+  const r = await new OutlineReader(fixtureRoot).readOutlineSection('结局')
+  assert.equal(r.ok, true)
+  assert.ok(r.content.includes('血仇得报'))
+})
+
+test('OutlineReader.readVolumeOutline 读卷纲全文', async () => {
+  const r = await new OutlineReader(fixtureRoot).readVolumeOutline(1)
+  assert.equal(r.ok, true)
+  assert.ok(r.content.includes('卷定位'))
+})
+
+test('OutlineReader.readVolumeOutline 不存在的卷 → ok=false', async () => {
+  const r = await new OutlineReader(fixtureRoot).readVolumeOutline(99)
+  assert.equal(r.ok, false)
+})
+
+test('OutlineReader.listVolumes 列出卷号', async () => {
+  const vols = await new OutlineReader(fixtureRoot).listVolumes()
+  assert.deepEqual(vols, [1])
+})

+ 30 - 0
v7/test/storage/adapters/SecretReader.test.js

@@ -0,0 +1,30 @@
+import { test } from 'node:test'
+import assert from 'node:assert/strict'
+import path from 'node:path'
+import { fileURLToPath } from 'node:url'
+import { SecretReader } from '../../../src/storage/adapters/SecretReader.js'
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url))
+const fixtureRoot = path.join(__dirname, '../../fixtures/sample-book')
+
+test('SecretReader.readBasicInfo 读信息差 front matter', async () => {
+  const r = await new SecretReader(fixtureRoot).readBasicInfo('信息差-001')
+  assert.equal(r.ok, true)
+  assert.equal(r.data.读者知道, false)
+})
+
+test('SecretReader.readContent 读 ## 内容 段', async () => {
+  const r = await new SecretReader(fixtureRoot).readContent('信息差-001')
+  assert.equal(r.ok, true)
+  assert.ok(r.content.includes('封印邪灵'))
+})
+
+test('SecretReader 不存在的信息差 → ok=false', async () => {
+  const r = await new SecretReader(fixtureRoot).readBasicInfo('信息差-999')
+  assert.equal(r.ok, false)
+})
+
+test('SecretReader.listUnrevealed 无 cache 时返回空数组(不崩)', async () => {
+  const rows = await new SecretReader(fixtureRoot).listUnrevealed()
+  assert.deepEqual(rows, [])
+})

+ 27 - 0
v7/test/storage/adapters/TimelineReader.test.js

@@ -0,0 +1,27 @@
+import { test } from 'node:test'
+import assert from 'node:assert/strict'
+import path from 'node:path'
+import { fileURLToPath } from 'node:url'
+import { TimelineReader } from '../../../src/storage/adapters/TimelineReader.js'
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url))
+const fixtureRoot = path.join(__dirname, '../../fixtures/sample-book')
+
+test('TimelineReader.readVolumeRange 读卷时间线(Markdown 表格行)', async () => {
+  const r = await new TimelineReader(fixtureRoot).readVolumeRange(1, 1)
+  assert.equal(r.ok, true)
+  assert.ok(r.timeline.length >= 1)
+  assert.ok(r.timeline[0].在场.includes('林晚'))
+})
+
+test('TimelineReader.readByParticipant 按在场角色过滤', async () => {
+  const rows = await new TimelineReader(fixtureRoot).readByParticipant(1, '林晚')
+  assert.ok(rows.length >= 1)
+  assert.ok(rows.every((e) => e.在场.includes('林晚')))
+})
+
+test('TimelineReader 不存在的卷 → 空时间线(不崩)', async () => {
+  const r = await new TimelineReader(fixtureRoot).readVolumeRange(99, 99)
+  assert.equal(r.ok, true)
+  assert.deepEqual(r.timeline, [])
+})

+ 17 - 0
v7/test/storage/adapters/Writer.test.js

@@ -0,0 +1,17 @@
+import { test } from 'node:test'
+import assert from 'node:assert/strict'
+import { ChapterWriter } from '../../../src/storage/adapters/ChapterWriter.js'
+import { ThreadLedgerWriter } from '../../../src/storage/adapters/ThreadLedgerWriter.js'
+
+// Writer 端口在 M1 只定接口占位,调用应抛明确的 M2 提示(不静默成功)
+test('ChapterWriter 方法抛 M2 占位错误', async () => {
+  const w = new ChapterWriter('/x')
+  await assert.rejects(() => w.writeChapter(1, {}, ''), /M2/)
+  await assert.rejects(() => w.updateFrontMatter(1, {}), /M2/)
+})
+
+test('ThreadLedgerWriter 方法抛 M2 占位错误', async () => {
+  const w = new ThreadLedgerWriter('/x')
+  await assert.rejects(() => w.updateThread('伏笔-001', {}), /M2/)
+  await assert.rejects(() => w.appendHistory('伏笔-001', {}), /M2/)
+})