2026-02-12 12:01:57 +01:00
|
|
|
import { describe, expect, test } from "bun:test"
|
|
|
|
|
import { z } from "zod"
|
|
|
|
|
import { AthenaConfigSchema, CouncilConfigSchema, CouncilMemberSchema } from "./athena"
|
|
|
|
|
|
|
|
|
|
describe("CouncilMemberSchema", () => {
|
2026-02-20 13:15:23 +01:00
|
|
|
test("accepts member config with model and name", () => {
|
2026-02-12 12:01:57 +01:00
|
|
|
//#given
|
2026-02-20 13:15:23 +01:00
|
|
|
const config = { model: "anthropic/claude-opus-4-6", name: "member-a" }
|
2026-02-12 12:01:57 +01:00
|
|
|
|
|
|
|
|
//#when
|
|
|
|
|
const result = CouncilMemberSchema.safeParse(config)
|
|
|
|
|
|
|
|
|
|
//#then
|
|
|
|
|
expect(result.success).toBe(true)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test("accepts member config with all optional fields", () => {
|
|
|
|
|
//#given
|
|
|
|
|
const config = {
|
|
|
|
|
model: "openai/gpt-5.3-codex",
|
|
|
|
|
variant: "high",
|
|
|
|
|
name: "analyst-a",
|
2026-02-19 13:53:04 +01:00
|
|
|
temperature: 0.3,
|
2026-02-12 12:01:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//#when
|
|
|
|
|
const result = CouncilMemberSchema.safeParse(config)
|
|
|
|
|
|
|
|
|
|
//#then
|
|
|
|
|
expect(result.success).toBe(true)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test("rejects member config missing model", () => {
|
|
|
|
|
//#given
|
2026-02-14 15:38:34 +01:00
|
|
|
const config = { name: "no-model" }
|
2026-02-12 12:01:57 +01:00
|
|
|
|
|
|
|
|
//#when
|
|
|
|
|
const result = CouncilMemberSchema.safeParse(config)
|
|
|
|
|
|
|
|
|
|
//#then
|
|
|
|
|
expect(result.success).toBe(false)
|
|
|
|
|
})
|
|
|
|
|
|
2026-02-13 16:00:04 +01:00
|
|
|
test("rejects model string without provider/model separator", () => {
|
|
|
|
|
//#given
|
|
|
|
|
const config = { model: "invalid-model" }
|
|
|
|
|
|
|
|
|
|
//#when
|
|
|
|
|
const result = CouncilMemberSchema.safeParse(config)
|
|
|
|
|
|
|
|
|
|
//#then
|
|
|
|
|
expect(result.success).toBe(false)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test("rejects model string with empty provider", () => {
|
|
|
|
|
//#given
|
|
|
|
|
const config = { model: "/gpt-5.3-codex" }
|
|
|
|
|
|
|
|
|
|
//#when
|
|
|
|
|
const result = CouncilMemberSchema.safeParse(config)
|
|
|
|
|
|
|
|
|
|
//#then
|
|
|
|
|
expect(result.success).toBe(false)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test("rejects model string with empty model ID", () => {
|
|
|
|
|
//#given
|
|
|
|
|
const config = { model: "openai/" }
|
|
|
|
|
|
|
|
|
|
//#when
|
|
|
|
|
const result = CouncilMemberSchema.safeParse(config)
|
|
|
|
|
|
|
|
|
|
//#then
|
|
|
|
|
expect(result.success).toBe(false)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test("rejects empty model string", () => {
|
|
|
|
|
//#given
|
|
|
|
|
const config = { model: "" }
|
|
|
|
|
|
|
|
|
|
//#when
|
|
|
|
|
const result = CouncilMemberSchema.safeParse(config)
|
|
|
|
|
|
|
|
|
|
//#then
|
|
|
|
|
expect(result.success).toBe(false)
|
|
|
|
|
})
|
|
|
|
|
|
2026-02-12 12:01:57 +01:00
|
|
|
test("z.infer produces expected type shape", () => {
|
|
|
|
|
//#given
|
|
|
|
|
type InferredCouncilMember = z.infer<typeof CouncilMemberSchema>
|
|
|
|
|
const member: InferredCouncilMember = {
|
|
|
|
|
model: "anthropic/claude-opus-4-6",
|
|
|
|
|
variant: "medium",
|
|
|
|
|
name: "oracle",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//#when
|
|
|
|
|
const model = member.model
|
|
|
|
|
|
|
|
|
|
//#then
|
|
|
|
|
expect(model).toBe("anthropic/claude-opus-4-6")
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test("optional fields are optional without runtime defaults", () => {
|
|
|
|
|
//#given
|
2026-02-20 13:15:23 +01:00
|
|
|
const config = { model: "xai/grok-code-fast-1", name: "member-x" }
|
2026-02-12 12:01:57 +01:00
|
|
|
|
|
|
|
|
//#when
|
|
|
|
|
const parsed = CouncilMemberSchema.parse(config)
|
|
|
|
|
|
|
|
|
|
//#then
|
|
|
|
|
expect(parsed.variant).toBeUndefined()
|
2026-02-19 13:53:04 +01:00
|
|
|
expect(parsed.temperature).toBeUndefined()
|
|
|
|
|
})
|
|
|
|
|
|
2026-02-20 13:15:23 +01:00
|
|
|
test("rejects member config missing name", () => {
|
|
|
|
|
//#given
|
|
|
|
|
const config = { model: "anthropic/claude-opus-4-6" }
|
|
|
|
|
|
|
|
|
|
//#when
|
|
|
|
|
const result = CouncilMemberSchema.safeParse(config)
|
|
|
|
|
|
|
|
|
|
//#then
|
|
|
|
|
expect(result.success).toBe(false)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test("rejects member config with empty name", () => {
|
|
|
|
|
//#given
|
|
|
|
|
const config = { model: "anthropic/claude-opus-4-6", name: "" }
|
|
|
|
|
|
|
|
|
|
//#when
|
|
|
|
|
const result = CouncilMemberSchema.safeParse(config)
|
|
|
|
|
|
|
|
|
|
//#then
|
|
|
|
|
expect(result.success).toBe(false)
|
|
|
|
|
})
|
|
|
|
|
|
2026-02-19 13:53:04 +01:00
|
|
|
test("accepts member config with temperature", () => {
|
|
|
|
|
//#given
|
2026-02-20 13:15:23 +01:00
|
|
|
const config = { model: "openai/gpt-5.3-codex", name: "member-a", temperature: 0.5 }
|
2026-02-19 13:53:04 +01:00
|
|
|
|
|
|
|
|
//#when
|
|
|
|
|
const result = CouncilMemberSchema.safeParse(config)
|
|
|
|
|
|
|
|
|
|
//#then
|
|
|
|
|
expect(result.success).toBe(true)
|
|
|
|
|
if (result.success) {
|
|
|
|
|
expect(result.data.temperature).toBe(0.5)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test("rejects temperature below 0", () => {
|
|
|
|
|
//#given
|
|
|
|
|
const config = { model: "openai/gpt-5.3-codex", temperature: -0.1 }
|
|
|
|
|
|
|
|
|
|
//#when
|
|
|
|
|
const result = CouncilMemberSchema.safeParse(config)
|
|
|
|
|
|
|
|
|
|
//#then
|
|
|
|
|
expect(result.success).toBe(false)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test("rejects temperature above 2", () => {
|
|
|
|
|
//#given
|
|
|
|
|
const config = { model: "openai/gpt-5.3-codex", temperature: 2.1 }
|
|
|
|
|
|
|
|
|
|
//#when
|
|
|
|
|
const result = CouncilMemberSchema.safeParse(config)
|
|
|
|
|
|
|
|
|
|
//#then
|
|
|
|
|
expect(result.success).toBe(false)
|
2026-02-12 12:01:57 +01:00
|
|
|
})
|
2026-02-18 20:55:05 +01:00
|
|
|
|
|
|
|
|
test("rejects member config with unknown fields", () => {
|
|
|
|
|
//#given
|
2026-02-19 13:53:04 +01:00
|
|
|
const config = { model: "openai/gpt-5.3-codex", unknownField: true }
|
2026-02-18 20:55:05 +01:00
|
|
|
|
|
|
|
|
//#when
|
|
|
|
|
const result = CouncilMemberSchema.safeParse(config)
|
|
|
|
|
|
|
|
|
|
//#then
|
|
|
|
|
expect(result.success).toBe(false)
|
|
|
|
|
})
|
2026-02-12 12:01:57 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe("CouncilConfigSchema", () => {
|
|
|
|
|
test("accepts council with 2 members", () => {
|
|
|
|
|
//#given
|
|
|
|
|
const config = {
|
2026-02-20 13:15:23 +01:00
|
|
|
members: [
|
|
|
|
|
{ model: "anthropic/claude-opus-4-6", name: "member-a" },
|
|
|
|
|
{ model: "openai/gpt-5.3-codex", name: "member-b" },
|
|
|
|
|
],
|
2026-02-12 12:01:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//#when
|
|
|
|
|
const result = CouncilConfigSchema.safeParse(config)
|
|
|
|
|
|
|
|
|
|
//#then
|
|
|
|
|
expect(result.success).toBe(true)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test("accepts council with 3 members and optional fields", () => {
|
|
|
|
|
//#given
|
|
|
|
|
const config = {
|
|
|
|
|
members: [
|
2026-02-14 15:38:34 +01:00
|
|
|
{ model: "anthropic/claude-opus-4-6", name: "a" },
|
2026-02-12 12:01:57 +01:00
|
|
|
{ model: "openai/gpt-5.3-codex", name: "b", variant: "high" },
|
2026-02-14 15:38:34 +01:00
|
|
|
{ model: "xai/grok-code-fast-1", name: "c", variant: "low" },
|
2026-02-12 12:01:57 +01:00
|
|
|
],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//#when
|
|
|
|
|
const result = CouncilConfigSchema.safeParse(config)
|
|
|
|
|
|
|
|
|
|
//#then
|
|
|
|
|
expect(result.success).toBe(true)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test("rejects council with 0 members", () => {
|
|
|
|
|
//#given
|
|
|
|
|
const config = { members: [] }
|
|
|
|
|
|
|
|
|
|
//#when
|
|
|
|
|
const result = CouncilConfigSchema.safeParse(config)
|
|
|
|
|
|
|
|
|
|
//#then
|
|
|
|
|
expect(result.success).toBe(false)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test("rejects council with 1 member", () => {
|
|
|
|
|
//#given
|
2026-02-20 13:15:23 +01:00
|
|
|
const config = { members: [{ model: "anthropic/claude-opus-4-6", name: "member-a" }] }
|
2026-02-12 12:01:57 +01:00
|
|
|
|
|
|
|
|
//#when
|
|
|
|
|
const result = CouncilConfigSchema.safeParse(config)
|
|
|
|
|
|
|
|
|
|
//#then
|
|
|
|
|
expect(result.success).toBe(false)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test("rejects council missing members field", () => {
|
|
|
|
|
//#given
|
|
|
|
|
const config = {}
|
|
|
|
|
|
|
|
|
|
//#when
|
|
|
|
|
const result = CouncilConfigSchema.safeParse(config)
|
|
|
|
|
|
|
|
|
|
//#then
|
|
|
|
|
expect(result.success).toBe(false)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe("AthenaConfigSchema", () => {
|
|
|
|
|
test("accepts Athena config with model and council", () => {
|
|
|
|
|
//#given
|
|
|
|
|
const config = {
|
|
|
|
|
model: "anthropic/claude-opus-4-6",
|
|
|
|
|
council: {
|
2026-02-20 13:15:23 +01:00
|
|
|
members: [
|
|
|
|
|
{ model: "openai/gpt-5.3-codex", name: "member-a" },
|
|
|
|
|
{ model: "xai/grok-code-fast-1", name: "member-b" },
|
|
|
|
|
],
|
2026-02-12 12:01:57 +01:00
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//#when
|
|
|
|
|
const result = AthenaConfigSchema.safeParse(config)
|
|
|
|
|
|
|
|
|
|
//#then
|
|
|
|
|
expect(result.success).toBe(true)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test("rejects Athena config without council", () => {
|
|
|
|
|
//#given
|
|
|
|
|
const config = { model: "anthropic/claude-opus-4-6" }
|
|
|
|
|
|
|
|
|
|
//#when
|
|
|
|
|
const result = AthenaConfigSchema.safeParse(config)
|
|
|
|
|
|
|
|
|
|
//#then
|
|
|
|
|
expect(result.success).toBe(false)
|
|
|
|
|
})
|
|
|
|
|
})
|