File size: 4,352 Bytes
dfb775d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
"""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