113 lines
3.7 KiB
TypeScript
113 lines
3.7 KiB
TypeScript
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||
|
|
import {
|
||
|
|
bootstrapUngatedGlobal,
|
||
|
|
UNGATED_GLOBAL_PROJECT_NAME,
|
||
|
|
SETTING_UNGATED_GLOBAL_SEEDED,
|
||
|
|
} from '../src/bootstrap/ungated-global.js';
|
||
|
|
import { SYSTEM_SETTINGS_SECRET } from '../src/bootstrap/system-settings.js';
|
||
|
|
import type { PrismaClient } from '@prisma/client';
|
||
|
|
|
||
|
|
function mockPrisma(opts: { settingsData?: Record<string, unknown> | null } = {}): PrismaClient {
|
||
|
|
const secretRow = opts.settingsData
|
||
|
|
? { id: 'secret-1', name: SYSTEM_SETTINGS_SECRET, data: opts.settingsData, version: 3 }
|
||
|
|
: null;
|
||
|
|
|
||
|
|
return {
|
||
|
|
secret: {
|
||
|
|
findUnique: vi.fn(async () => secretRow),
|
||
|
|
update: vi.fn(async () => ({})),
|
||
|
|
create: vi.fn(async () => ({})),
|
||
|
|
},
|
||
|
|
user: {
|
||
|
|
upsert: vi.fn(async () => ({ id: 'sys-user-id', email: 'system@mcpctl.local' })),
|
||
|
|
},
|
||
|
|
project: {
|
||
|
|
upsert: vi.fn(async (args: { where: { name: string } }) => ({ id: 'proj-ug', name: args.where.name })),
|
||
|
|
},
|
||
|
|
mcpServer: {
|
||
|
|
upsert: vi.fn(async (args: { where: { name: string } }) => ({ id: 'srv-calc', name: args.where.name })),
|
||
|
|
},
|
||
|
|
projectServer: {
|
||
|
|
upsert: vi.fn(async () => ({})),
|
||
|
|
},
|
||
|
|
} as unknown as PrismaClient;
|
||
|
|
}
|
||
|
|
|
||
|
|
describe('bootstrapUngatedGlobal', () => {
|
||
|
|
let prisma: PrismaClient;
|
||
|
|
|
||
|
|
beforeEach(() => {
|
||
|
|
prisma = mockPrisma();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('seeds the ungated-global project with the calcmcp server and attaches it', async () => {
|
||
|
|
await bootstrapUngatedGlobal(prisma);
|
||
|
|
|
||
|
|
expect(prisma.project.upsert).toHaveBeenCalledWith(
|
||
|
|
expect.objectContaining({
|
||
|
|
where: { name: UNGATED_GLOBAL_PROJECT_NAME },
|
||
|
|
create: expect.objectContaining({ gated: false, prompt: expect.stringContaining('calcmcp') }),
|
||
|
|
}),
|
||
|
|
);
|
||
|
|
expect(prisma.mcpServer.upsert).toHaveBeenCalledWith(
|
||
|
|
expect.objectContaining({
|
||
|
|
where: { name: 'calcmcp' },
|
||
|
|
create: expect.objectContaining({
|
||
|
|
dockerImage: expect.stringContaining('calcmcp'),
|
||
|
|
transport: 'STDIO',
|
||
|
|
}),
|
||
|
|
}),
|
||
|
|
);
|
||
|
|
expect(prisma.projectServer.upsert).toHaveBeenCalledWith(
|
||
|
|
expect.objectContaining({
|
||
|
|
where: { projectId_serverId: { projectId: 'proj-ug', serverId: 'srv-calc' } },
|
||
|
|
}),
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('marks the seed as done in the system settings secret', async () => {
|
||
|
|
await bootstrapUngatedGlobal(prisma);
|
||
|
|
|
||
|
|
// Settings secret didn't exist → created with the marker key
|
||
|
|
expect(prisma.secret.create).toHaveBeenCalledWith(
|
||
|
|
expect.objectContaining({
|
||
|
|
data: expect.objectContaining({
|
||
|
|
name: SYSTEM_SETTINGS_SECRET,
|
||
|
|
data: expect.objectContaining({ [SETTING_UNGATED_GLOBAL_SEEDED]: true }),
|
||
|
|
}),
|
||
|
|
}),
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('does NOT re-seed when the marker is set (deletions stay deleted)', async () => {
|
||
|
|
prisma = mockPrisma({ settingsData: { [SETTING_UNGATED_GLOBAL_SEEDED]: true } });
|
||
|
|
|
||
|
|
await bootstrapUngatedGlobal(prisma);
|
||
|
|
|
||
|
|
expect(prisma.project.upsert).not.toHaveBeenCalled();
|
||
|
|
expect(prisma.mcpServer.upsert).not.toHaveBeenCalled();
|
||
|
|
expect(prisma.projectServer.upsert).not.toHaveBeenCalled();
|
||
|
|
expect(prisma.secret.update).not.toHaveBeenCalled();
|
||
|
|
expect(prisma.secret.create).not.toHaveBeenCalled();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('preserves existing settings keys when writing the marker', async () => {
|
||
|
|
prisma = mockPrisma({ settingsData: { allowSelfPasswordChange: false } });
|
||
|
|
|
||
|
|
await bootstrapUngatedGlobal(prisma);
|
||
|
|
|
||
|
|
expect(prisma.secret.update).toHaveBeenCalledWith(
|
||
|
|
expect.objectContaining({
|
||
|
|
where: { name: SYSTEM_SETTINGS_SECRET },
|
||
|
|
data: expect.objectContaining({
|
||
|
|
data: {
|
||
|
|
allowSelfPasswordChange: false,
|
||
|
|
[SETTING_UNGATED_GLOBAL_SEEDED]: true,
|
||
|
|
},
|
||
|
|
version: 4,
|
||
|
|
}),
|
||
|
|
}),
|
||
|
|
);
|
||
|
|
});
|
||
|
|
});
|