148 lines
4.2 KiB
TypeScript
148 lines
4.2 KiB
TypeScript
|
|
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();
|
||
|
|
});
|
||
|
|
});
|