registry-write.test.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  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. registerBook,
  8. setCurrentBook,
  9. touchLastOpened,
  10. readBooksRegistry,
  11. loadBooks,
  12. } from '../../src/session/index.js'
  13. async function tmpWorkdir() {
  14. const root = await fs.mkdtemp(path.join(os.tmpdir(), 'wnw-reg-'))
  15. return { root, cleanup: () => fs.rm(root, { recursive: true, force: true }) }
  16. }
  17. async function writeRegistry(root, lines) {
  18. await fs.mkdir(path.join(root, '.webnovel'), { recursive: true })
  19. await fs.writeFile(path.join(root, '.webnovel', 'books.jsonl'), lines.join('\n') + '\n', 'utf8')
  20. }
  21. const DATE_RE = /^\d{4}-\d{2}-\d{2}$/
  22. test('registerBook:空工作目录登记首本,置当前并带最后打开', async () => {
  23. const { root, cleanup } = await tmpWorkdir()
  24. try {
  25. const r = await registerBook(root, { 书名: '剑起青云', 目录: '剑起青云' })
  26. assert.equal(r.ok, true)
  27. const reg = await readBooksRegistry(root)
  28. assert.equal(reg.books.length, 1)
  29. assert.equal(reg.books[0].书名, '剑起青云')
  30. assert.equal(reg.books[0].当前, true)
  31. assert.match(reg.books[0].最后打开, DATE_RE)
  32. } finally {
  33. await cleanup()
  34. }
  35. })
  36. test('registerBook:新书置当前,旧当前退位', async () => {
  37. const { root, cleanup } = await tmpWorkdir()
  38. try {
  39. await writeRegistry(root, [JSON.stringify({ 书名: '星海', 目录: '星海', 当前: true })])
  40. const r = await registerBook(root, { 书名: '剑起青云', 目录: '剑起青云' })
  41. assert.equal(r.ok, true)
  42. const reg = await readBooksRegistry(root)
  43. assert.equal(reg.books.length, 2)
  44. assert.equal(reg.books.find((b) => b.书名 === '星海').当前, false)
  45. assert.equal(reg.books.find((b) => b.书名 === '剑起青云').当前, true)
  46. } finally {
  47. await cleanup()
  48. }
  49. })
  50. test('registerBook:同目录重复登记不产生重复行,书名更新', async () => {
  51. const { root, cleanup } = await tmpWorkdir()
  52. try {
  53. await writeRegistry(root, [JSON.stringify({ 书名: '旧名', 目录: '书目录', 当前: true })])
  54. const r = await registerBook(root, { 书名: '新名', 目录: '书目录' })
  55. assert.equal(r.ok, true)
  56. const reg = await readBooksRegistry(root)
  57. assert.equal(reg.books.length, 1)
  58. assert.equal(reg.books[0].书名, '新名')
  59. } finally {
  60. await cleanup()
  61. }
  62. })
  63. test('registerBook:缺书名/目录 → 人话错误', async () => {
  64. const { root, cleanup } = await tmpWorkdir()
  65. try {
  66. const r = await registerBook(root, { 书名: '', 目录: 'x' })
  67. assert.equal(r.ok, false)
  68. assert.ok(r.error.includes('书名'))
  69. } finally {
  70. await cleanup()
  71. }
  72. })
  73. test('setCurrentBook:按书名或目录命中,单一当前', async () => {
  74. const { root, cleanup } = await tmpWorkdir()
  75. try {
  76. await writeRegistry(root, [
  77. JSON.stringify({ 书名: '星海', 目录: 'xinghai', 当前: true }),
  78. JSON.stringify({ 书名: '剑起青云', 目录: 'jian', 当前: false }),
  79. ])
  80. const r = await setCurrentBook(root, '剑起青云')
  81. assert.equal(r.ok, true)
  82. assert.equal(r.book.目录, 'jian')
  83. const reg = await readBooksRegistry(root)
  84. assert.equal(reg.books.filter((b) => b.当前).length, 1)
  85. assert.equal(reg.books.find((b) => b.当前).书名, '剑起青云')
  86. // 按目录名也可命中
  87. const r2 = await setCurrentBook(root, 'xinghai')
  88. assert.equal(r2.ok, true)
  89. assert.equal(r2.book.书名, '星海')
  90. } finally {
  91. await cleanup()
  92. }
  93. })
  94. test('setCurrentBook:未命中 → 列候选', async () => {
  95. const { root, cleanup } = await tmpWorkdir()
  96. try {
  97. await writeRegistry(root, [JSON.stringify({ 书名: '星海', 目录: '星海', 当前: true })])
  98. const r = await setCurrentBook(root, '不存在的书')
  99. assert.equal(r.ok, false)
  100. assert.ok(r.error.includes('星海'), `候选应包含书名:${r.error}`)
  101. } finally {
  102. await cleanup()
  103. }
  104. })
  105. test('touchLastOpened:刷新最后打开,不改其他字段', async () => {
  106. const { root, cleanup } = await tmpWorkdir()
  107. try {
  108. await writeRegistry(root, [
  109. JSON.stringify({ 书名: '星海', 目录: '星海', 当前: true, 最后打开: '2000-01-01' }),
  110. ])
  111. await touchLastOpened(root, '星海')
  112. const reg = await readBooksRegistry(root)
  113. assert.notEqual(reg.books[0].最后打开, '2000-01-01')
  114. assert.match(reg.books[0].最后打开, DATE_RE)
  115. assert.equal(reg.books[0].当前, true)
  116. } finally {
  117. await cleanup()
  118. }
  119. })
  120. test('loadBooks:坏行丢弃回写自愈(与 assembleSessionContext 同源)', async () => {
  121. const { root, cleanup } = await tmpWorkdir()
  122. try {
  123. await writeRegistry(root, [
  124. JSON.stringify({ 书名: '星海', 目录: '星海', 当前: true }),
  125. '{坏的 json',
  126. ])
  127. const r = await loadBooks(root)
  128. assert.equal(r.books.length, 1)
  129. const raw = await fs.readFile(path.join(root, '.webnovel', 'books.jsonl'), 'utf8')
  130. assert.ok(!raw.includes('坏的'), '坏行应被自愈回写清除')
  131. } finally {
  132. await cleanup()
  133. }
  134. })