mindXtrain / tests /test_coach_governance_api.py
Gregory-L's picture
fork mindXtrain from GitHub (Professor-Codephreak/mindXtrain@661bd41) as the mindX-specific line
dfb775d verified
Raw History Blame Contribute Delete
4.35 kB
"""Coach governance endpoints: boardroom convene + dojo settle."""
from __future__ import annotations
from fastapi.testclient import TestClient
from mindxtrain.governance import panel as P
from mindxtrain.operator.app import app
client = TestClient(app)
def test_boardroom_presets():
r = client.get("/coach/api/boardroom/presets")
assert r.status_code == 200
presets = r.json()
assert "classic_triad" in presets
assert presets["classic_triad"] == ["advocate", "critic", "analyst"]
def test_convene_with_explicit_votes():
r = client.post("/coach/api/boardroom/convene", json={
"motion": "promote actor x",
"members": [{"id": "a", "role": "advocate"}, {"id": "b", "role": "critic"},
{"id": "c", "role": "analyst"}],
"votes": {"a": "approve", "b": "approve", "c": "reject"},
})
assert r.status_code == 200, r.text
d = r.json()["decision"]
assert d["outcome"] == "approved"
assert d["approvals"] == 2 and d["rejections"] == 1
def test_convene_tie_is_disputed():
r = client.post("/coach/api/boardroom/convene", json={
"motion": "x",
"members": [{"id": "a"}, {"id": "b"}],
"votes": {"a": "approve", "b": "reject"},
})
assert r.status_code == 200
assert r.json()["decision"]["disputed"] is True
def test_convene_with_models(monkeypatch):
def _chat(model, messages, **k):
return "VERDICT: APPROVE" if "advocate" in messages[0]["content"].lower() else "VERDICT: REJECT"
monkeypatch.setattr(P, "chat_once", _chat)
r = client.post("/coach/api/boardroom/convene", json={
"motion": "promote actor x",
"members": [{"id": "a", "role": "advocate", "model": "m"},
{"id": "b", "role": "critic", "model": "m"}],
"use_models": True,
})
assert r.status_code == 200, r.text
body = r.json()
assert len(body["deliberations"]) == 2
assert body["decision"]["approvals"] == 1 and body["decision"]["rejections"] == 1
def test_convene_requires_votes_or_models():
r = client.post("/coach/api/boardroom/convene", json={
"motion": "x", "members": [{"id": "a"}],
})
assert r.status_code == 422
def test_convene_rejects_bad_role():
r = client.post("/coach/api/boardroom/convene", json={
"motion": "x", "members": [{"id": "a", "role": "wizard"}],
"votes": {"a": "approve"},
})
assert r.status_code == 422
def test_dojo_settle_with_votes():
r = client.post("/coach/api/dojo/settle", json={
"motion": "promote actor x", "size": 3,
"votes": {"judge-0": "approve", "judge-1": "approve", "judge-2": "reject"},
})
assert r.status_code == 200, r.text
v = r.json()
assert v["settled"] is True
assert v["winner"] == "approve"
def test_dojo_settle_rounds_size_to_prime():
# size 4 → 5 judges; need all five votes.
r = client.post("/coach/api/dojo/settle", json={
"motion": "x", "size": 4,
"votes": {f"judge-{i}": ("approve" if i < 3 else "reject") for i in range(5)},
})
assert r.status_code == 200, r.text
v = r.json()
assert len(v["judges"]) == 5
assert v["winner"] == "approve"
def test_dojo_settle_missing_vote_422():
r = client.post("/coach/api/dojo/settle", json={
"motion": "x", "size": 3, "votes": {"judge-0": "approve"},
})
assert r.status_code == 422
def test_models_endpoint_shape():
# Returns 200 with the backend base_url + a (possibly empty) model list,
# whether or not a chat backend is reachable.
r = client.get("/coach/api/models")
assert r.status_code == 200
body = r.json()
assert "base_url" in body
assert isinstance(body["models"], list)
def test_coach_index_has_boardroom_card():
r = client.get("/coach/")
assert r.status_code == 200
assert 'id="step-boardroom"' in r.text
assert 'id="br-convene"' in r.text
assert 'id="br-settle"' in r.text
def test_coach_js_wires_boardroom():
r = client.get("/coach/static/coach.js")
assert r.status_code == 200
assert "wireBoardroom" in r.text
assert "/coach/api/boardroom/convene" in r.text
assert "/coach/api/dojo/settle" in r.text
# Model dropdown is populated from the live backend.
assert "loadBoardroomModels" in r.text
assert "/coach/api/models" in r.text