From a30d0a14271727e5b9255d04cf1ab2e680eca386 Mon Sep 17 00:00:00 2001 From: ismeth Date: Thu, 26 Feb 2026 12:48:04 +0100 Subject: [PATCH] test: add tests for council config schema, prompt content, and config injection --- .../athena/athena-config-injection.test.ts | 26 +++++ .../athena/council-member-agent.test.ts | 24 ++++ .../council-member-agents.test.ts | 16 +++ src/config/schema/athena.test.ts | 103 +++++++++++++++++- 4 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 src/agents/athena/athena-config-injection.test.ts create mode 100644 src/agents/athena/council-member-agent.test.ts diff --git a/src/agents/athena/athena-config-injection.test.ts b/src/agents/athena/athena-config-injection.test.ts new file mode 100644 index 000000000..fe13a2e5d --- /dev/null +++ b/src/agents/athena/athena-config-injection.test.ts @@ -0,0 +1,26 @@ +import { describe, expect, it } from "bun:test" +import { createAthenaAgent } from "./agent" + +describe("Athena prompt config injection placeholders", () => { + const athenaConfig = createAthenaAgent("anthropic/claude-opus-4-6") + + describe("#given the Athena agent prompt", () => { + describe("#when checking for runtime injection placeholders", () => { + it("#then contains RETRY_ON_FAIL placeholder", () => { + expect(athenaConfig.prompt).toContain("{RETRY_ON_FAIL}") + }) + + it("#then contains STUCK_THRESHOLD_SECONDS placeholder", () => { + expect(athenaConfig.prompt).toContain("{STUCK_THRESHOLD_SECONDS}") + }) + + it("#then contains quorum reference", () => { + expect(athenaConfig.prompt).toContain("quorum") + }) + + it("#then contains timeout reference with 30000", () => { + expect(athenaConfig.prompt).toContain("30000") + }) + }) + }) +}) diff --git a/src/agents/athena/council-member-agent.test.ts b/src/agents/athena/council-member-agent.test.ts new file mode 100644 index 000000000..a541ac6f1 --- /dev/null +++ b/src/agents/athena/council-member-agent.test.ts @@ -0,0 +1,24 @@ +import { describe, expect, it } from "bun:test" +import { COUNCIL_MEMBER_PROMPT } from "./council-member-agent" + +describe("COUNCIL_MEMBER_PROMPT", () => { + describe("#given the prompt constant", () => { + describe("#when checking for required tag instructions", () => { + it("#then contains COUNCIL_MEMBER_RESPONSE tag name", () => { + expect(COUNCIL_MEMBER_PROMPT).toContain("COUNCIL_MEMBER_RESPONSE") + }) + + it("#then contains Response Format section header", () => { + expect(COUNCIL_MEMBER_PROMPT).toContain("Response Format") + }) + + it("#then contains opening tag example", () => { + expect(COUNCIL_MEMBER_PROMPT).toContain("") + }) + + it("#then contains closing tag example", () => { + expect(COUNCIL_MEMBER_PROMPT).toContain("") + }) + }) + }) +}) diff --git a/src/agents/builtin-agents/council-member-agents.test.ts b/src/agents/builtin-agents/council-member-agents.test.ts index 5d49f6189..7ee985d7d 100644 --- a/src/agents/builtin-agents/council-member-agents.test.ts +++ b/src/agents/builtin-agents/council-member-agents.test.ts @@ -9,6 +9,10 @@ describe("council-member-agents", () => { { model: "openai/gpt-5.3-codex", name: "GPT" }, { model: "anthropic/claude-opus-4-6", name: "gpt" }, ], + retry_on_fail: 0, + retry_failed_if_others_finished: false, + cancel_retrying_on_quorum: true, + stuck_threshold_seconds: 120, } //#when const result = registerCouncilMemberAgents(config) @@ -24,6 +28,10 @@ describe("council-member-agents", () => { { model: "openai/gpt-5.3-codex", name: "GPT" }, { model: "anthropic/claude-opus-4-6", name: "Claude" }, ], + retry_on_fail: 0, + retry_failed_if_others_finished: false, + cancel_retrying_on_quorum: true, + stuck_threshold_seconds: 120, } //#when const result = registerCouncilMemberAgents(config) @@ -40,6 +48,10 @@ describe("council-member-agents", () => { { model: "openai/gpt-5.3-codex", name: "GPT Codex" }, { model: "openai/gpt-5.3-codex", name: "Codex GPT" }, ], + retry_on_fail: 0, + retry_failed_if_others_finished: false, + cancel_retrying_on_quorum: true, + stuck_threshold_seconds: 120, } //#when const result = registerCouncilMemberAgents(config) @@ -56,6 +68,10 @@ describe("council-member-agents", () => { { model: "openai/gpt-5.3-codex", name: "GPT" }, { model: "invalid-no-slash", name: "Invalid" }, ], + retry_on_fail: 0, + retry_failed_if_others_finished: false, + cancel_retrying_on_quorum: true, + stuck_threshold_seconds: 120, } //#when const result = registerCouncilMemberAgents(config) diff --git a/src/config/schema/athena.test.ts b/src/config/schema/athena.test.ts index ed0edac09..1e12f5670 100644 --- a/src/config/schema/athena.test.ts +++ b/src/config/schema/athena.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from "bun:test" +import { describe, expect, it, test } from "bun:test" import { z } from "zod" import { AthenaConfigSchema, CouncilConfigSchema, CouncilMemberSchema } from "./athena" @@ -429,3 +429,104 @@ describe("AthenaConfigSchema", () => { expect(result.success).toBe(false) }) }) + +describe("CouncilConfigSchema — resilience fields", () => { + const validMembers = [ + { model: "openai/gpt-5.3-codex", name: "member-a" }, + { model: "anthropic/claude-opus-4-6", name: "member-b" }, + ] + + describe("#given minimal config with only members", () => { + describe("#when parsed", () => { + it("#then applies default retry_on_fail of 0", () => { + const result = CouncilConfigSchema.safeParse({ members: validMembers }) + expect(result.success).toBe(true) + if (result.success) { + expect(result.data.retry_on_fail).toBe(0) + } + }) + + it("#then applies default retry_failed_if_others_finished of false", () => { + const result = CouncilConfigSchema.safeParse({ members: validMembers }) + expect(result.success).toBe(true) + if (result.success) { + expect(result.data.retry_failed_if_others_finished).toBe(false) + } + }) + + it("#then applies default cancel_retrying_on_quorum of true", () => { + const result = CouncilConfigSchema.safeParse({ members: validMembers }) + expect(result.success).toBe(true) + if (result.success) { + expect(result.data.cancel_retrying_on_quorum).toBe(true) + } + }) + + it("#then applies default stuck_threshold_seconds of 120", () => { + const result = CouncilConfigSchema.safeParse({ members: validMembers }) + expect(result.success).toBe(true) + if (result.success) { + expect(result.data.stuck_threshold_seconds).toBe(120) + } + }) + }) + }) + + describe("#given config with all resilience fields set", () => { + describe("#when parsed", () => { + it("#then uses provided values instead of defaults", () => { + const config = { + members: validMembers, + retry_on_fail: 3, + retry_failed_if_others_finished: true, + cancel_retrying_on_quorum: false, + stuck_threshold_seconds: 60, + } + const result = CouncilConfigSchema.safeParse(config) + expect(result.success).toBe(true) + if (result.success) { + expect(result.data.retry_on_fail).toBe(3) + expect(result.data.retry_failed_if_others_finished).toBe(true) + expect(result.data.cancel_retrying_on_quorum).toBe(false) + expect(result.data.stuck_threshold_seconds).toBe(60) + } + }) + }) + }) + + describe("#given retry_on_fail below minimum", () => { + describe("#when parsed with -1", () => { + it("#then fails validation", () => { + const result = CouncilConfigSchema.safeParse({ members: validMembers, retry_on_fail: -1 }) + expect(result.success).toBe(false) + }) + }) + }) + + describe("#given retry_on_fail above maximum", () => { + describe("#when parsed with 6", () => { + it("#then fails validation", () => { + const result = CouncilConfigSchema.safeParse({ members: validMembers, retry_on_fail: 6 }) + expect(result.success).toBe(false) + }) + }) + }) + + describe("#given stuck_threshold_seconds below minimum", () => { + describe("#when parsed with 10", () => { + it("#then fails validation", () => { + const result = CouncilConfigSchema.safeParse({ members: validMembers, stuck_threshold_seconds: 10 }) + expect(result.success).toBe(false) + }) + }) + }) + + describe("#given backward-compatible config with only members", () => { + describe("#when parsed", () => { + it("#then succeeds without errors — new fields are optional", () => { + const result = CouncilConfigSchema.safeParse({ members: validMembers }) + expect(result.success).toBe(true) + }) + }) + }) +})