Files
GoClaw/server/seed.test.ts

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();
});
});