feat(tests): let managers escalate and directly resolve disputed tests
Aegis CI / lint-and-test (push) Has been cancelled
Snyk Security Scan / Python vulnerabilities (backend) (push) Has been cancelled
Snyk Security Scan / npm vulnerabilities (frontend) (push) Has been cancelled
Snyk Security Scan / Docker image vulnerabilities (backend) (push) Has been cancelled
Aegis CI / lint-and-test (push) Has been cancelled
Snyk Security Scan / Python vulnerabilities (backend) (push) Has been cancelled
Snyk Security Scan / npm vulnerabilities (frontend) (push) Has been cancelled
Snyk Security Scan / Docker image vulnerabilities (backend) (push) Has been cancelled
Request Discussion only ever nudged the peer lead, and the one-time
manager notification when a dispute starts had no real follow-up
action attached to it — a manager could be told a dispute existed but
had no way to actually do anything about it. Adds:
- POST /tests/{id}/escalate-to-manager — either lead can explicitly
ask managers to step in, distinct from the passive initial notice.
- POST /tests/{id}/manager-resolve-dispute — a manager decides the
final outcome (validated/rejected) directly, bypassing both leads'
votes entirely, reusing the same dual-validation event dispatch so
Jira/notifications behave like any other resolution.
Both leads are notified of the manager's final call, win or lose.
This commit is contained in:
@@ -112,3 +112,96 @@ def test_resolve_dispute_routes_to_red_queue(
|
||||
assert body["state"] == "red_executing"
|
||||
assert body["red_validation_status"] is None
|
||||
assert body["blue_validation_status"] is None
|
||||
|
||||
|
||||
def test_escalate_to_manager_notifies_managers(
|
||||
client, db, api, auth_headers, red_tech_headers, red_lead_headers,
|
||||
blue_tech_headers, blue_lead_headers, manager_headers, manager_user, technique,
|
||||
):
|
||||
test_id = _reach_disputed(client, db, api, auth_headers, red_tech_headers, red_lead_headers,
|
||||
blue_tech_headers, blue_lead_headers, technique)
|
||||
|
||||
resp = api("post", f"/api/v1/tests/{test_id}/escalate-to-manager", red_lead_headers)
|
||||
assert resp.status_code == 200, resp.text
|
||||
assert resp.json()["status"] == "escalated"
|
||||
|
||||
notifications = api("get", "/api/v1/notifications", manager_headers).json()
|
||||
assert any("escalated" in (n.get("title") or "").lower() for n in notifications)
|
||||
|
||||
|
||||
def test_escalate_to_manager_requires_disputed_state(
|
||||
client, db, api, auth_headers, red_tech_headers, red_lead_headers, technique,
|
||||
):
|
||||
resp = api(
|
||||
"post", "/api/v1/tests", red_lead_headers,
|
||||
json={"technique_id": technique, "name": "Not disputed"},
|
||||
)
|
||||
test_id = resp.json()["id"]
|
||||
|
||||
resp = api("post", f"/api/v1/tests/{test_id}/escalate-to-manager", red_lead_headers)
|
||||
assert resp.status_code == 400
|
||||
|
||||
|
||||
def test_manager_resolve_dispute_as_validated(
|
||||
client, db, api, auth_headers, red_tech_headers, red_lead_headers,
|
||||
blue_tech_headers, blue_lead_headers, manager_headers, technique,
|
||||
):
|
||||
test_id = _reach_disputed(client, db, api, auth_headers, red_tech_headers, red_lead_headers,
|
||||
blue_tech_headers, blue_lead_headers, technique)
|
||||
|
||||
resp = api(
|
||||
"post", f"/api/v1/tests/{test_id}/manager-resolve-dispute", manager_headers,
|
||||
json={"outcome": "validated", "notes": "Detection was sufficient after all"},
|
||||
)
|
||||
assert resp.status_code == 200, resp.text
|
||||
assert resp.json()["state"] == "validated"
|
||||
|
||||
red_notifs = api("get", "/api/v1/notifications", red_lead_headers).json()
|
||||
assert any(n["type"] == "manager_dispute_resolution" for n in red_notifs)
|
||||
blue_notifs = api("get", "/api/v1/notifications", blue_lead_headers).json()
|
||||
assert any(n["type"] == "manager_dispute_resolution" for n in blue_notifs)
|
||||
|
||||
|
||||
def test_manager_resolve_dispute_as_rejected(
|
||||
client, db, api, auth_headers, red_tech_headers, red_lead_headers,
|
||||
blue_tech_headers, blue_lead_headers, manager_headers, technique,
|
||||
):
|
||||
test_id = _reach_disputed(client, db, api, auth_headers, red_tech_headers, red_lead_headers,
|
||||
blue_tech_headers, blue_lead_headers, technique)
|
||||
|
||||
resp = api(
|
||||
"post", f"/api/v1/tests/{test_id}/manager-resolve-dispute", manager_headers,
|
||||
json={"outcome": "rejected"},
|
||||
)
|
||||
assert resp.status_code == 200, resp.text
|
||||
assert resp.json()["state"] == "rejected"
|
||||
|
||||
|
||||
def test_manager_resolve_dispute_forbidden_for_lead(
|
||||
client, db, api, auth_headers, red_tech_headers, red_lead_headers,
|
||||
blue_tech_headers, blue_lead_headers, technique,
|
||||
):
|
||||
test_id = _reach_disputed(client, db, api, auth_headers, red_tech_headers, red_lead_headers,
|
||||
blue_tech_headers, blue_lead_headers, technique)
|
||||
|
||||
resp = api(
|
||||
"post", f"/api/v1/tests/{test_id}/manager-resolve-dispute", red_lead_headers,
|
||||
json={"outcome": "validated"},
|
||||
)
|
||||
assert resp.status_code == 403
|
||||
|
||||
|
||||
def test_manager_resolve_dispute_requires_disputed_state(
|
||||
client, db, api, red_lead_headers, manager_headers, technique,
|
||||
):
|
||||
resp = api(
|
||||
"post", "/api/v1/tests", red_lead_headers,
|
||||
json={"technique_id": technique, "name": "Not disputed either"},
|
||||
)
|
||||
test_id = resp.json()["id"]
|
||||
|
||||
resp = api(
|
||||
"post", f"/api/v1/tests/{test_id}/manager-resolve-dispute", manager_headers,
|
||||
json={"outcome": "validated"},
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
|
||||
Reference in New Issue
Block a user