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) }) })