- 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.
303 lines
8.7 KiB
TypeScript
303 lines
8.7 KiB
TypeScript
import { describe, it, expect, beforeEach } from 'vitest';
|
|
import { appRouter } from './index';
|
|
import { getTestDb, resetTestDb, globalSqlite } from './test-setup';
|
|
import type { TRPCContext } from './types';
|
|
|
|
describe('tRPC API Layer', () => {
|
|
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);
|
|
});
|
|
|
|
describe('Project CRUD', () => {
|
|
it('should create a project', async () => {
|
|
const project = await caller.project.createProject({
|
|
name: 'Test Project',
|
|
description: 'A test project',
|
|
});
|
|
|
|
expect(project).toMatchObject({
|
|
name: 'Test Project',
|
|
description: 'A test project',
|
|
ownerId: 1,
|
|
});
|
|
expect(project.id).toBeDefined();
|
|
expect(project.id).toBeGreaterThan(0);
|
|
expect(project.createdAt).toBeInstanceOf(Date);
|
|
expect(project.updatedAt).toBeInstanceOf(Date);
|
|
});
|
|
|
|
it('should list projects', async () => {
|
|
await caller.project.createProject({ name: 'Test Project' });
|
|
|
|
const projects = await caller.project.listProjects();
|
|
|
|
expect(Array.isArray(projects)).toBe(true);
|
|
expect(projects.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should get a specific project', async () => {
|
|
const created = await caller.project.createProject({ name: 'Get Test' });
|
|
|
|
const project = await caller.project.getProject({ id: created.id });
|
|
|
|
expect(project.id).toBe(created.id);
|
|
expect(project.name).toBe('Get Test');
|
|
});
|
|
|
|
it('should update a project', async () => {
|
|
const created = await caller.project.createProject({ name: 'Update Test' });
|
|
|
|
const updated = await caller.project.updateProject({
|
|
id: created.id,
|
|
name: 'Updated Test',
|
|
description: 'Updated description',
|
|
});
|
|
|
|
expect(updated.name).toBe('Updated Test');
|
|
expect(updated.description).toBe('Updated description');
|
|
});
|
|
|
|
it('should delete a project', async () => {
|
|
const created = await caller.project.createProject({ name: 'Delete Test' });
|
|
|
|
const result = await caller.project.deleteProject({ id: created.id });
|
|
|
|
expect(result).toEqual({ success: true });
|
|
});
|
|
});
|
|
|
|
describe('Character CRUD', () => {
|
|
beforeEach(async () => {
|
|
const project = await caller.project.createProject({
|
|
name: 'Character Test Project',
|
|
});
|
|
projectId = project.id;
|
|
});
|
|
|
|
it('should create a character', async () => {
|
|
const character = await caller.project.createCharacter({
|
|
name: 'John Doe',
|
|
description: 'Main character',
|
|
projectId,
|
|
});
|
|
|
|
expect(character).toMatchObject({
|
|
name: 'John Doe',
|
|
description: 'Main character',
|
|
projectId,
|
|
});
|
|
});
|
|
|
|
it('should list characters for a project', async () => {
|
|
await caller.project.createCharacter({ name: 'Char 1', projectId });
|
|
|
|
const characters = await caller.project.listCharacters({ projectId });
|
|
|
|
expect(characters.length).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
|
|
describe('Scene CRUD', () => {
|
|
beforeEach(async () => {
|
|
const project = await caller.project.createProject({
|
|
name: 'Scene Test Project',
|
|
});
|
|
projectId = project.id;
|
|
});
|
|
|
|
it('should create a scene', async () => {
|
|
const scene = await caller.project.createScene({
|
|
title: 'INT. OFFICE - DAY',
|
|
content: 'John sits at his desk.',
|
|
projectId,
|
|
order: 1,
|
|
});
|
|
|
|
expect(scene).toMatchObject({
|
|
title: 'INT. OFFICE - DAY',
|
|
content: 'John sits at his desk.',
|
|
projectId,
|
|
order: 1,
|
|
});
|
|
});
|
|
|
|
it('should list scenes for a project', async () => {
|
|
await caller.project.createScene({ title: 'Scene 1', projectId, order: 1 });
|
|
|
|
const scenes = await caller.project.listScenes({ projectId });
|
|
|
|
expect(scenes.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should update scene order', async () => {
|
|
const scene = await caller.project.createScene({
|
|
title: 'Reorder Scene',
|
|
projectId,
|
|
order: 1,
|
|
});
|
|
|
|
const updated = await caller.project.updateScene({ id: scene.id, order: 5 });
|
|
|
|
expect(updated.order).toBe(5);
|
|
});
|
|
});
|
|
|
|
describe('Error Handling', () => {
|
|
it('should throw error when getting non-existent project', async () => {
|
|
await expect(
|
|
caller.project.getProject({ id: 99999 })
|
|
).rejects.toThrow('not found');
|
|
});
|
|
|
|
it('should throw error when deleting non-existent project', async () => {
|
|
await expect(
|
|
caller.project.deleteProject({ id: 99999 })
|
|
).rejects.toThrow('not found');
|
|
});
|
|
});
|
|
|
|
describe('Project Sharing', () => {
|
|
let sharedProjectId: number;
|
|
|
|
beforeEach(async () => {
|
|
const project = await caller.project.createProject({
|
|
name: 'Shared Project',
|
|
});
|
|
sharedProjectId = project.id;
|
|
|
|
// Insert a second user
|
|
globalSqlite!.exec("INSERT INTO users (id, clerk_id, email, name) VALUES (2, 'user2_test', 'user2@test.com', 'User Two');");
|
|
});
|
|
|
|
it('should share a project with another user', async () => {
|
|
const member = await caller.project.shareProject({
|
|
projectId: sharedProjectId,
|
|
userId: 2,
|
|
role: 'editor',
|
|
});
|
|
|
|
expect(member).toMatchObject({
|
|
projectId: sharedProjectId,
|
|
userId: 2,
|
|
role: 'editor',
|
|
});
|
|
});
|
|
|
|
it('should list project members including owner', async () => {
|
|
await caller.project.shareProject({
|
|
projectId: sharedProjectId,
|
|
userId: 2,
|
|
role: 'viewer',
|
|
});
|
|
|
|
const members = await caller.project.listMembers({ projectId: sharedProjectId });
|
|
|
|
expect(members.length).toBeGreaterThanOrEqual(2);
|
|
const owner = members.find((m: any) => m.userId === 1 && m.role === 'owner');
|
|
const member = members.find((m: any) => m.userId === 2 && m.role === 'viewer');
|
|
expect(owner).toBeDefined();
|
|
expect(member).toBeDefined();
|
|
});
|
|
|
|
it('should update a member role', async () => {
|
|
await caller.project.shareProject({
|
|
projectId: sharedProjectId,
|
|
userId: 2,
|
|
role: 'viewer',
|
|
});
|
|
|
|
const updated = await caller.project.updateMemberRole({
|
|
projectId: sharedProjectId,
|
|
userId: 2,
|
|
role: 'admin',
|
|
});
|
|
|
|
expect(updated.role).toBe('admin');
|
|
});
|
|
|
|
it('should remove a member', async () => {
|
|
await caller.project.shareProject({
|
|
projectId: sharedProjectId,
|
|
userId: 2,
|
|
role: 'editor',
|
|
});
|
|
|
|
const result = await caller.project.removeMember({
|
|
projectId: sharedProjectId,
|
|
userId: 2,
|
|
});
|
|
|
|
expect(result).toEqual({ success: true });
|
|
|
|
const members = await caller.project.listMembers({ projectId: sharedProjectId });
|
|
const removed = members.find((m: any) => m.userId === 2);
|
|
expect(removed).toBeUndefined();
|
|
});
|
|
|
|
it('should throw error when sharing with yourself', async () => {
|
|
await expect(
|
|
caller.project.shareProject({
|
|
projectId: sharedProjectId,
|
|
userId: 1,
|
|
role: 'editor',
|
|
})
|
|
).rejects.toThrow('yourself');
|
|
});
|
|
|
|
it('should throw error when sharing duplicate user', async () => {
|
|
await caller.project.shareProject({
|
|
projectId: sharedProjectId,
|
|
userId: 2,
|
|
role: 'editor',
|
|
});
|
|
|
|
await expect(
|
|
caller.project.shareProject({
|
|
projectId: sharedProjectId,
|
|
userId: 2,
|
|
role: 'viewer',
|
|
})
|
|
).rejects.toThrow('already a member');
|
|
});
|
|
|
|
it('should allow shared members to access project', async () => {
|
|
await caller.project.shareProject({
|
|
projectId: sharedProjectId,
|
|
userId: 2,
|
|
role: 'editor',
|
|
});
|
|
|
|
// Create caller for user 2
|
|
const db = await getTestDb();
|
|
const ctx2: TRPCContext = { clerkUserId: 'user2_test', db };
|
|
const caller2 = appRouter.createCaller(ctx2);
|
|
|
|
const project = await caller2.project.getProject({ id: sharedProjectId });
|
|
expect(project.id).toBe(sharedProjectId);
|
|
});
|
|
|
|
it('should include shared projects in listProjects for member', async () => {
|
|
await caller.project.shareProject({
|
|
projectId: sharedProjectId,
|
|
userId: 2,
|
|
role: 'viewer',
|
|
});
|
|
|
|
const db = await getTestDb();
|
|
const ctx2: TRPCContext = { clerkUserId: 'user2_test', db };
|
|
const caller2 = appRouter.createCaller(ctx2);
|
|
|
|
const projects = await caller2.project.listProjects();
|
|
const found = projects.find((p: any) => p.id === sharedProjectId);
|
|
expect(found).toBeDefined();
|
|
});
|
|
});
|
|
});
|