199 lines
6.8 KiB
TypeScript
199 lines
6.8 KiB
TypeScript
/**
|
|
* Tests for server/seed.ts
|
|
*
|
|
* Strategy: mock `./db` so getDb() returns a fake Drizzle-like object,
|
|
* then assert that seedDefaults() inserts exactly the right agents
|
|
* (or skips when agents already exist).
|
|
*/
|
|
|
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
import { DEFAULT_AGENTS, seedDefaults } from "./seed";
|
|
|
|
// ─── Mock getDb ───────────────────────────────────────────────────────────────
|
|
|
|
const mockInsert = vi.fn();
|
|
const mockSelect = vi.fn();
|
|
|
|
vi.mock("./db", () => ({
|
|
getDb: vi.fn(),
|
|
}));
|
|
|
|
import { getDb } from "./db";
|
|
|
|
// Helper: build a minimal fake db that tracks inserts and returns a given count
|
|
// Supports: select().from().where() chain (seed now queries WHERE isSystem=true)
|
|
function makeFakeDb(existingAgentCount: number) {
|
|
const insertValues = vi.fn().mockResolvedValue([{ insertId: 1 }]);
|
|
const insertInto = vi.fn().mockReturnValue({ values: insertValues });
|
|
|
|
// select().from().where() → [{ value: count }]
|
|
// .where() is optional — both from() and where() resolve to the count
|
|
const whereFn = vi.fn().mockResolvedValue([{ value: existingAgentCount }]);
|
|
const fromFn = vi.fn().mockReturnValue({ where: whereFn });
|
|
const selectFn = vi.fn().mockReturnValue({ from: fromFn });
|
|
|
|
return {
|
|
insert: insertInto,
|
|
select: selectFn,
|
|
_insertValues: insertValues,
|
|
_fromFn: fromFn,
|
|
_whereFn: whereFn,
|
|
};
|
|
}
|
|
|
|
// ─── Tests ────────────────────────────────────────────────────────────────────
|
|
|
|
describe("DEFAULT_AGENTS", () => {
|
|
it("should contain exactly 6 default agents", () => {
|
|
expect(DEFAULT_AGENTS).toHaveLength(6);
|
|
});
|
|
|
|
it("should have exactly one orchestrator agent", () => {
|
|
const orchestrators = DEFAULT_AGENTS.filter((a) => a.isOrchestrator);
|
|
expect(orchestrators).toHaveLength(1);
|
|
expect(orchestrators[0].name).toBe("GoClaw Orchestrator");
|
|
});
|
|
|
|
it("should mark all agents as system agents", () => {
|
|
const nonSystem = DEFAULT_AGENTS.filter((a) => !a.isSystem);
|
|
expect(nonSystem).toHaveLength(0);
|
|
});
|
|
|
|
it("should have unique roles", () => {
|
|
const roles = DEFAULT_AGENTS.map((a) => a.role);
|
|
const uniqueRoles = new Set(roles);
|
|
expect(uniqueRoles.size).toBe(roles.length);
|
|
});
|
|
|
|
it("each agent should have allowedTools array", () => {
|
|
for (const agent of DEFAULT_AGENTS) {
|
|
expect(Array.isArray(agent.allowedTools)).toBe(true);
|
|
expect(agent.allowedTools.length).toBeGreaterThan(0);
|
|
}
|
|
});
|
|
|
|
it("orchestrator should have the most tools", () => {
|
|
const orch = DEFAULT_AGENTS.find((a) => a.isOrchestrator)!;
|
|
const maxTools = Math.max(...DEFAULT_AGENTS.map((a) => a.allowedTools.length));
|
|
expect(orch.allowedTools.length).toBe(maxTools);
|
|
});
|
|
|
|
it("each agent should have a non-empty systemPrompt", () => {
|
|
for (const agent of DEFAULT_AGENTS) {
|
|
expect(agent.systemPrompt.trim().length).toBeGreaterThan(50);
|
|
}
|
|
});
|
|
|
|
it("each agent should have valid temperature (0-1 as string)", () => {
|
|
for (const agent of DEFAULT_AGENTS) {
|
|
const temp = parseFloat(agent.temperature);
|
|
expect(temp).toBeGreaterThanOrEqual(0);
|
|
expect(temp).toBeLessThanOrEqual(1);
|
|
}
|
|
});
|
|
|
|
it("each agent should have maxTokens > 0", () => {
|
|
for (const agent of DEFAULT_AGENTS) {
|
|
expect(agent.maxTokens).toBeGreaterThan(0);
|
|
}
|
|
});
|
|
|
|
it("orchestrator should have the highest maxTokens", () => {
|
|
const orch = DEFAULT_AGENTS.find((a) => a.isOrchestrator)!;
|
|
const maxTok = Math.max(...DEFAULT_AGENTS.map((a) => a.maxTokens));
|
|
expect(orch.maxTokens).toBe(maxTok);
|
|
});
|
|
|
|
it("each agent should have seeded metadata flag", () => {
|
|
for (const agent of DEFAULT_AGENTS) {
|
|
expect(agent.metadata.seeded).toBe(true);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("seedDefaults()", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it("should skip seeding when DB is unavailable", async () => {
|
|
vi.mocked(getDb).mockResolvedValue(null as any);
|
|
await expect(seedDefaults()).resolves.toBeUndefined();
|
|
// No inserts should happen
|
|
expect(mockInsert).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("should skip seeding when agents already exist", async () => {
|
|
const fakeDb = makeFakeDb(3); // 3 agents already exist
|
|
vi.mocked(getDb).mockResolvedValue(fakeDb as any);
|
|
|
|
await seedDefaults();
|
|
|
|
// select was called to check count
|
|
expect(fakeDb.select).toHaveBeenCalled();
|
|
// insert was NOT called
|
|
expect(fakeDb.insert).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("should insert all default agents when table is empty", async () => {
|
|
const fakeDb = makeFakeDb(0); // empty table
|
|
vi.mocked(getDb).mockResolvedValue(fakeDb as any);
|
|
|
|
await seedDefaults();
|
|
|
|
// insert should be called once per agent
|
|
expect(fakeDb.insert).toHaveBeenCalledTimes(DEFAULT_AGENTS.length);
|
|
expect(fakeDb._insertValues).toHaveBeenCalledTimes(DEFAULT_AGENTS.length);
|
|
});
|
|
|
|
it("should insert orchestrator agent with isOrchestrator=true", async () => {
|
|
const fakeDb = makeFakeDb(0);
|
|
vi.mocked(getDb).mockResolvedValue(fakeDb as any);
|
|
|
|
await seedDefaults();
|
|
|
|
// Find the call that inserted the orchestrator
|
|
const calls = fakeDb._insertValues.mock.calls;
|
|
const orchCall = calls.find((call: any[]) => call[0]?.isOrchestrator === true);
|
|
expect(orchCall).toBeDefined();
|
|
expect(orchCall![0].isSystem).toBe(true);
|
|
expect(orchCall![0].name).toBe("GoClaw Orchestrator");
|
|
});
|
|
|
|
it("should insert all agents with isSystem=true", async () => {
|
|
const fakeDb = makeFakeDb(0);
|
|
vi.mocked(getDb).mockResolvedValue(fakeDb as any);
|
|
|
|
await seedDefaults();
|
|
|
|
const calls = fakeDb._insertValues.mock.calls;
|
|
for (const call of calls) {
|
|
expect(call[0].isSystem).toBe(true);
|
|
}
|
|
});
|
|
|
|
it("should be idempotent — second call with existing data does nothing", async () => {
|
|
// First call: empty DB → seeds
|
|
const fakeDb1 = makeFakeDb(0);
|
|
vi.mocked(getDb).mockResolvedValue(fakeDb1 as any);
|
|
await seedDefaults();
|
|
expect(fakeDb1.insert).toHaveBeenCalledTimes(DEFAULT_AGENTS.length);
|
|
|
|
// Second call: DB now has agents → skips
|
|
const fakeDb2 = makeFakeDb(DEFAULT_AGENTS.length);
|
|
vi.mocked(getDb).mockResolvedValue(fakeDb2 as any);
|
|
await seedDefaults();
|
|
expect(fakeDb2.insert).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("should not throw when DB insert fails — just log error", async () => {
|
|
const fakeDb = makeFakeDb(0);
|
|
// Make insert throw
|
|
fakeDb._insertValues.mockRejectedValue(new Error("DB error"));
|
|
vi.mocked(getDb).mockResolvedValue(fakeDb as any);
|
|
|
|
// Should not throw
|
|
await expect(seedDefaults()).resolves.toBeUndefined();
|
|
});
|
|
});
|