import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest'; import type { PrismaClient } from '@prisma/client'; import { setupTestDb, cleanupTestDb, clearAllTables } from './helpers.js'; describe('ResourceProposal schema', () => { let prisma: PrismaClient; beforeAll(async () => { prisma = await setupTestDb(); }, 30_000); afterAll(async () => { await cleanupTestDb(); }); beforeEach(async () => { await clearAllTables(prisma); }); async function createUser() { return prisma.user.create({ data: { email: `test-${Date.now()}-${Math.random()}@example.com`, name: 'Test', passwordHash: '!locked', role: 'USER', }, }); } async function createProject(name = `project-${Date.now()}-${Math.random()}`) { const user = await createUser(); return prisma.project.create({ data: { name, ownerId: user.id } }); } it('creates a pending prompt proposal with defaults', async () => { const project = await createProject(); const proposal = await prisma.resourceProposal.create({ data: { resourceType: 'prompt', name: 'my-proposal', body: { content: 'hello', priority: 5 }, projectId: project.id, }, }); expect(proposal.id).toBeDefined(); expect(proposal.status).toBe('pending'); expect(proposal.reviewerNote).toBe(''); expect(proposal.approvedRevisionId).toBeNull(); expect(proposal.version).toBe(1); expect(proposal.body).toEqual({ content: 'hello', priority: 5 }); }); it('creates a pending skill proposal', async () => { const project = await createProject(); const proposal = await prisma.resourceProposal.create({ data: { resourceType: 'skill', name: 'my-skill-proposal', body: { content: 'SKILL.md body', metadata: { postInstall: 'hooks/x.sh' } }, projectId: project.id, }, }); expect(proposal.resourceType).toBe('skill'); }); it('enforces unique (resourceType, name, projectId)', async () => { const project = await createProject(); await prisma.resourceProposal.create({ data: { resourceType: 'prompt', name: 'dup', body: { content: 'a' }, projectId: project.id, }, }); await expect( prisma.resourceProposal.create({ data: { resourceType: 'prompt', name: 'dup', body: { content: 'b' }, projectId: project.id, }, }), ).rejects.toThrow(); }); it('allows same name across different resource types in same project', async () => { const project = await createProject(); await prisma.resourceProposal.create({ data: { resourceType: 'prompt', name: 'shared', body: { content: 'a' }, projectId: project.id, }, }); const second = await prisma.resourceProposal.create({ data: { resourceType: 'skill', name: 'shared', body: { content: 'b' }, projectId: project.id, }, }); expect(second.id).toBeDefined(); }); it('allows status lifecycle: pending → approved', async () => { const project = await createProject(); const proposal = await prisma.resourceProposal.create({ data: { resourceType: 'prompt', name: 'flow', body: { content: 'hi' }, projectId: project.id, }, }); const approved = await prisma.resourceProposal.update({ where: { id: proposal.id }, data: { status: 'approved', reviewerNote: 'looks good', approvedRevisionId: 'rev-fake-123', }, }); expect(approved.status).toBe('approved'); expect(approved.reviewerNote).toBe('looks good'); expect(approved.approvedRevisionId).toBe('rev-fake-123'); }); it('cascades on project delete', async () => { const project = await createProject(); const proposal = await prisma.resourceProposal.create({ data: { resourceType: 'prompt', name: 'will-cascade', body: { content: 'hi' }, projectId: project.id, }, }); await prisma.project.delete({ where: { id: project.id } }); const found = await prisma.resourceProposal.findUnique({ where: { id: proposal.id } }); expect(found).toBeNull(); }); });