4adafa1936
Add P7 Cytoscape relationship graph with setting CRUD, P9 cockpit analytics driven by writing_sessions, chapter POV selection, and full test coverage. Co-authored-by: Cursor <cursoragent@cursor.com>
74 lines
2.8 KiB
TypeScript
74 lines
2.8 KiB
TypeScript
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
import { mkdtempSync, rmSync } from 'fs'
|
|
import { join } from 'path'
|
|
import { tmpdir } from 'os'
|
|
import { randomUUID } from 'crypto'
|
|
import { BookRegistryService } from '../../src/main/services/book-registry'
|
|
import { CharacterGraphService } from '../../src/main/services/character-graph.service'
|
|
import { SettingRepository } from '../../src/main/db/repositories/setting.repo'
|
|
import { closeAllBookDbs } from '../../src/main/db/connection'
|
|
|
|
describe('CharacterGraphService', () => {
|
|
let dir: string
|
|
let registry: BookRegistryService
|
|
let bookId: string
|
|
let charA: string
|
|
let charB: string
|
|
let charC: string
|
|
|
|
beforeEach(() => {
|
|
dir = mkdtempSync(join(tmpdir(), 'bilin-graph-'))
|
|
registry = new BookRegistryService(dir)
|
|
const meta = registry.create({ name: '图谱书', category: '测试', createSampleChapter: false })
|
|
bookId = meta.id
|
|
const repo = new SettingRepository(registry.getDb(bookId))
|
|
charA = repo.create('character', '角色A').id
|
|
charB = repo.create('character', '角色B').id
|
|
charC = repo.create('character', '角色C').id
|
|
})
|
|
|
|
afterEach(() => {
|
|
closeAllBookDbs()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
it('UT-GRAPH-01: getData returns nodes and edges', () => {
|
|
const svc = new CharacterGraphService(registry)
|
|
svc.upsertRelationship(bookId, charA, { targetId: charB, label: '朋友', intimacy: 3 })
|
|
const data = svc.getData(bookId)
|
|
expect(data.nodes.length).toBe(3)
|
|
expect(data.edges).toHaveLength(1)
|
|
expect(data.edges[0].label).toBe('朋友')
|
|
})
|
|
|
|
it('UT-GRAPH-02: bidirectional edges dedupe to max intimacy', () => {
|
|
const svc = new CharacterGraphService(registry)
|
|
svc.upsertRelationship(bookId, charA, { targetId: charB, label: '同事', intimacy: 2 })
|
|
svc.upsertRelationship(bookId, charB, { targetId: charA, label: '挚友', intimacy: 4 })
|
|
const data = svc.getData(bookId)
|
|
expect(data.edges).toHaveLength(1)
|
|
expect(data.edges[0].intimacy).toBe(4)
|
|
})
|
|
|
|
it('UT-GRAPH-03: connected filter hides isolated nodes', () => {
|
|
const svc = new CharacterGraphService(registry)
|
|
svc.upsertRelationship(bookId, charA, { targetId: charB, label: '师徒', intimacy: 5 })
|
|
const data = svc.getData(bookId, 'connected')
|
|
expect(data.nodes).toHaveLength(2)
|
|
expect(data.nodes.some((n) => n.id === charC)).toBe(false)
|
|
})
|
|
|
|
it('IT-GRAPH-01: upsert and delete relationship', () => {
|
|
const svc = new CharacterGraphService(registry)
|
|
const rel = svc.upsertRelationship(bookId, charA, {
|
|
id: randomUUID(),
|
|
targetId: charB,
|
|
label: '恋人',
|
|
intimacy: 5
|
|
})
|
|
expect(svc.getData(bookId).edges).toHaveLength(1)
|
|
svc.deleteRelationship(bookId, charA, rel.id)
|
|
expect(svc.getData(bookId).edges).toHaveLength(0)
|
|
})
|
|
})
|