- Updated router.ts middleware for Clerk authentication - Modified test contexts to use clerkUserId - Added team tables to test schema - Updated WaitlistForm and waitlist page - Created src/server/trpc/ parallel structure All 258 tests pass. Ready for Security Reviewer.
238 lines
6.6 KiB
TypeScript
238 lines
6.6 KiB
TypeScript
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<typeof appRouter.createCaller>;
|
|
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);
|
|
});
|
|
});
|
|
});
|