130 lines
4.7 KiB
TypeScript
130 lines
4.7 KiB
TypeScript
|
|
/**
|
||
|
|
* v4 schema-level tests for `Llm.poolName` and the dispatcher's
|
||
|
|
* `findByPoolName` query semantics. Lives in the db package because it
|
||
|
|
* exercises the actual Prisma column + index — the mcpd-side unit tests
|
||
|
|
* already cover the dispatcher's behavior with a mocked LlmService.
|
||
|
|
*/
|
||
|
|
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
|
||
|
|
import type { PrismaClient } from '@prisma/client';
|
||
|
|
import { setupTestDb, cleanupTestDb, clearAllTables } from './helpers.js';
|
||
|
|
|
||
|
|
/** Re-implementation of the LlmRepository query for direct schema verification. */
|
||
|
|
function findByPoolName(prisma: PrismaClient, poolName: string) {
|
||
|
|
return prisma.llm.findMany({
|
||
|
|
where: {
|
||
|
|
OR: [
|
||
|
|
{ poolName },
|
||
|
|
{ AND: [{ poolName: null }, { name: poolName }] },
|
||
|
|
],
|
||
|
|
},
|
||
|
|
orderBy: { name: 'asc' },
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
describe('Llm.poolName (v4)', () => {
|
||
|
|
let prisma: PrismaClient;
|
||
|
|
|
||
|
|
beforeAll(async () => {
|
||
|
|
prisma = await setupTestDb();
|
||
|
|
}, 30_000);
|
||
|
|
|
||
|
|
afterAll(async () => {
|
||
|
|
await cleanupTestDb();
|
||
|
|
});
|
||
|
|
|
||
|
|
beforeEach(async () => {
|
||
|
|
await clearAllTables(prisma);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('defaults poolName to NULL for freshly inserted rows', async () => {
|
||
|
|
const llm = await prisma.llm.create({
|
||
|
|
data: { name: 'plain', type: 'openai', model: 'gpt-4o' },
|
||
|
|
});
|
||
|
|
expect(llm.poolName).toBeNull();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('allows multiple rows to share a poolName (the v4 stacking behavior)', async () => {
|
||
|
|
await prisma.llm.create({
|
||
|
|
data: { name: 'qwen-prod-1', type: 'openai', model: 'qwen3-thinking', poolName: 'qwen-pool' },
|
||
|
|
});
|
||
|
|
await prisma.llm.create({
|
||
|
|
data: { name: 'qwen-prod-2', type: 'openai', model: 'qwen3-thinking', poolName: 'qwen-pool' },
|
||
|
|
});
|
||
|
|
await prisma.llm.create({
|
||
|
|
data: { name: 'qwen-prod-3', type: 'openai', model: 'qwen3-thinking', poolName: 'qwen-pool' },
|
||
|
|
});
|
||
|
|
|
||
|
|
const members = await findByPoolName(prisma, 'qwen-pool');
|
||
|
|
expect(members.map((m) => m.name).sort()).toEqual(['qwen-prod-1', 'qwen-prod-2', 'qwen-prod-3']);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('keeps `name` globally unique even when multiple rows share a poolName', async () => {
|
||
|
|
await prisma.llm.create({
|
||
|
|
data: { name: 'qwen-prod-1', type: 'openai', model: 'qwen3-thinking', poolName: 'qwen-pool' },
|
||
|
|
});
|
||
|
|
await expect(
|
||
|
|
prisma.llm.create({
|
||
|
|
data: { name: 'qwen-prod-1', type: 'openai', model: 'qwen3-thinking', poolName: 'qwen-pool' },
|
||
|
|
}),
|
||
|
|
).rejects.toThrow();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('falls back to `name` as the effective pool key for solo rows (poolName=NULL)', async () => {
|
||
|
|
// Solo row addressable via its own name as a "pool of 1".
|
||
|
|
await prisma.llm.create({
|
||
|
|
data: { name: 'gpt-4o', type: 'openai', model: 'gpt-4o' },
|
||
|
|
});
|
||
|
|
const members = await findByPoolName(prisma, 'gpt-4o');
|
||
|
|
expect(members.map((m) => m.name)).toEqual(['gpt-4o']);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('a solo row with name=X joins the same pool as explicit poolName=X members', async () => {
|
||
|
|
// Edge case: an existing solo Llm named "qwen-pool" pre-dates pool
|
||
|
|
// adoption, then a publisher registers with poolName=qwen-pool. Both
|
||
|
|
// should appear in the dispatcher's candidate list — the effective
|
||
|
|
// pool key (poolName ?? name) is "qwen-pool" for each.
|
||
|
|
await prisma.llm.create({
|
||
|
|
data: { name: 'qwen-pool', type: 'openai', model: 'qwen3-thinking' },
|
||
|
|
});
|
||
|
|
await prisma.llm.create({
|
||
|
|
data: { name: 'qwen-prod-2', type: 'openai', model: 'qwen3-thinking', poolName: 'qwen-pool' },
|
||
|
|
});
|
||
|
|
|
||
|
|
const members = await findByPoolName(prisma, 'qwen-pool');
|
||
|
|
expect(members.map((m) => m.name).sort()).toEqual(['qwen-pool', 'qwen-prod-2']);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('does not match a solo row by `name` when its poolName is set to something else', async () => {
|
||
|
|
// Solo with name=foo but poolName=bar should NOT match findByPoolName('foo')
|
||
|
|
// — the explicit poolName takes precedence over the name fallback.
|
||
|
|
await prisma.llm.create({
|
||
|
|
data: { name: 'foo', type: 'openai', model: 'm', poolName: 'bar' },
|
||
|
|
});
|
||
|
|
const members = await findByPoolName(prisma, 'foo');
|
||
|
|
expect(members.map((m) => m.name)).toEqual([]);
|
||
|
|
|
||
|
|
const inBar = await findByPoolName(prisma, 'bar');
|
||
|
|
expect(inBar.map((m) => m.name)).toEqual(['foo']);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('updates poolName via update() and round-trips correctly', async () => {
|
||
|
|
const llm = await prisma.llm.create({
|
||
|
|
data: { name: 'qwen-prod-1', type: 'openai', model: 'qwen3-thinking' },
|
||
|
|
});
|
||
|
|
expect(llm.poolName).toBeNull();
|
||
|
|
|
||
|
|
const updated = await prisma.llm.update({
|
||
|
|
where: { id: llm.id },
|
||
|
|
data: { poolName: 'qwen-pool' },
|
||
|
|
});
|
||
|
|
expect(updated.poolName).toBe('qwen-pool');
|
||
|
|
|
||
|
|
// Revert to solo (NULL).
|
||
|
|
const reverted = await prisma.llm.update({
|
||
|
|
where: { id: llm.id },
|
||
|
|
data: { poolName: null },
|
||
|
|
});
|
||
|
|
expect(reverted.poolName).toBeNull();
|
||
|
|
});
|
||
|
|
});
|