feat(w2): add PackService for .novel and .novel-all export/import

Introduce pack-bootstrap chunk, ZipArchive-based PackService with progress IPC,
book registry import helpers, and IT-PACK-01 / IT-ALL-02 integration tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-08 15:36:47 +08:00
parent 9f2b1ae3e0
commit a4412793f4
12 changed files with 1563 additions and 73 deletions
+67
View File
@@ -0,0 +1,67 @@
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { mkdtempSync, rmSync, existsSync } from 'fs'
import { join } from 'path'
import { tmpdir } from 'os'
import { BookRegistryService } from '../../src/main/services/book-registry'
import { GlobalSettingsService } from '../../src/main/services/global-settings'
import { PackService } from '../../src/main/services/pack.service'
import { closeAllBookDbs } from '../../src/main/db/connection'
describe('PackService', () => {
let userData: string
let registry: BookRegistryService
let settings: GlobalSettingsService
let pack: PackService
beforeEach(() => {
userData = mkdtempSync(join(tmpdir(), 'bilin-pack-'))
registry = new BookRegistryService(userData)
settings = new GlobalSettingsService(userData)
pack = new PackService(registry, settings, userData)
})
afterEach(() => {
closeAllBookDbs()
rmSync(userData, { recursive: true, force: true })
})
it('IT-PACK-01: export and import single .novel preserves chapters', async () => {
const book = registry.create({ name: '打包测试', category: '玄幻', createSampleChapter: true })
registry.open(book.id)
const chapterCount = registry.getChapterRepo(book.id).list().length
expect(chapterCount).toBeGreaterThan(0)
const novelPath = join(userData, 'export.novel')
await pack.exportBook(book.id, novelPath)
expect(existsSync(novelPath)).toBe(true)
registry.delete(book.id)
const importedId = await pack.importBook(novelPath)
const imported = registry.open(importedId)
expect(imported.chapters.length).toBe(chapterCount)
expect(imported.meta.name).toContain('打包测试')
})
it('IT-ALL-02: export all three books and restore after wipe', async () => {
const ids: string[] = []
for (let i = 0; i < 3; i++) {
const b = registry.create({
name: `${i + 1}`,
category: '玄幻',
createSampleChapter: true
})
ids.push(b.id)
}
const archivePath = join(userData, 'all.novel-all')
await pack.exportAll(archivePath)
expect(existsSync(archivePath)).toBe(true)
for (const id of ids) registry.delete(id)
expect(registry.list()).toHaveLength(0)
const report = await pack.importAll(archivePath, 'new')
expect(report.imported).toBe(3)
expect(registry.list().length).toBe(3)
})
})