import { describe, it, expect, beforeEach } from 'vitest'; import { appRouter } from './index'; import { getTestDb, resetTestDb } from './test-setup'; import type { TRPCContext } from './types'; describe('tRPC API Layer - Character System', () => { let ctx: TRPCContext; let caller: ReturnType; let projectId: number; beforeEach(async () => { await resetTestDb(); const db = await getTestDb(); ctx = { clerkUserId: 'user_test', db }; caller = appRouter.createCaller(ctx); const project = await caller.project.createProject({ name: 'Character System Test Project', }); projectId = project.id; }); describe('createCharacter', () => { it('should create a character with all profile fields', async () => { const character = await caller.project.createCharacter({ name: 'John Doe', bio: 'A brave hero', role: 'protagonist', arc: 'Grows from coward to leader', arcType: 'positive', age: 30, gender: 'male', voice: 'Deep, commanding', traits: 'Brave, loyal, stubborn', motivation: 'Protect his family', conflict: 'Internal fear of failure', secret: 'Afraid of heights', projectId, }); expect(character).toMatchObject({ name: 'John Doe', bio: 'A brave hero', role: 'protagonist', arcType: 'positive', age: 30, projectId, }); expect(character.slug).toBe('john-doe'); }); it('should default role to supporting when not provided', async () => { const character = await caller.project.createCharacter({ name: 'Jane Smith', projectId, }); expect(character.role).toBe('supporting'); }); }); describe('updateCharacter', () => { it('should update character profile fields', async () => { const created = await caller.project.createCharacter({ name: 'Original', projectId, }); const updated = await caller.project.updateCharacter({ id: created.id, name: 'Updated Name', bio: 'New bio', role: 'antagonist', }); expect(updated.name).toBe('Updated Name'); expect(updated.slug).toBe('updated-name'); expect(updated.bio).toBe('New bio'); expect(updated.role).toBe('antagonist'); }); }); describe('searchCharacters', () => { it('should filter characters by query', async () => { await caller.project.createCharacter({ name: 'Alice', bio: 'The hero', projectId, }); await caller.project.createCharacter({ name: 'Bob', bio: 'The villain', projectId, }); const results = await caller.project.searchCharacters({ projectId, query: 'hero', }); expect(results.length).toBe(1); expect(results[0].name).toBe('Alice'); }); it('should filter characters by role', async () => { await caller.project.createCharacter({ name: 'Protag', role: 'protagonist', projectId, }); await caller.project.createCharacter({ name: 'Antag', role: 'antagonist', projectId, }); const results = await caller.project.searchCharacters({ projectId, role: 'protagonist', }); expect(results.length).toBe(1); expect(results[0].name).toBe('Protag'); }); }); describe('createRelationship', () => { it('should create a relationship between two characters', async () => { const charA = await caller.project.createCharacter({ name: 'Character A', projectId, }); const charB = await caller.project.createCharacter({ name: 'Character B', projectId, }); const rel = await caller.project.createRelationship({ characterIdA: charA.id, characterIdB: charB.id, relationshipType: 'friendship', strength: 80, isAntagonistic: false, }); expect(rel.characterIdA).toBe(charA.id); expect(rel.characterIdB).toBe(charB.id); expect(rel.relationshipType).toBe('friendship'); expect(rel.strength).toBe(80); }); it('should prevent self-relationships', async () => { const charA = await caller.project.createCharacter({ name: 'Character A', projectId, }); await expect( caller.project.createRelationship({ characterIdA: charA.id, characterIdB: charA.id, relationshipType: 'friendship', }) ).rejects.toThrow('Cannot create a relationship with the same character'); }); it('should prevent duplicate relationships', async () => { const charA = await caller.project.createCharacter({ name: 'Character A', projectId, }); const charB = await caller.project.createCharacter({ name: 'Character B', projectId, }); await caller.project.createRelationship({ characterIdA: charA.id, characterIdB: charB.id, relationshipType: 'friendship', }); await expect( caller.project.createRelationship({ characterIdA: charA.id, characterIdB: charB.id, relationshipType: 'rivalry', }) ).rejects.toThrow('Relationship already exists between these characters'); }); }); describe('deleteCharacter', () => { it('should remove associated relationships when deleting a character', async () => { const charA = await caller.project.createCharacter({ name: 'Character A', projectId, }); const charB = await caller.project.createCharacter({ name: 'Character B', projectId, }); await caller.project.createRelationship({ characterIdA: charA.id, characterIdB: charB.id, relationshipType: 'friendship', }); await caller.project.deleteCharacter({ id: charA.id }); const rels = await caller.project.getRelationshipsForCharacter({ characterId: charB.id, }); expect(rels.length).toBe(0); }); }); describe('getCharacterStats', () => { it('should return stats for a character', async () => { const charA = await caller.project.createCharacter({ name: 'TestChar', projectId, }); const stats = await caller.project.getCharacterStats({ characterId: charA.id, }); expect(stats.characterId).toBe(charA.id); expect(stats.sceneCount).toBe(0); expect(stats.totalDialogueLines).toBe(0); expect(stats.relationshipCount).toBe(0); }); }); });