feat(m7-amend2): implicit lifecycle — writes drive state, no workflow UI
User: «Enlève également le workflow d'un test, quand on saisit des
informations côtés redteam cela signifie qu'il a été exécuté et donc
en attente d'une review blueteam.»
Backend (update_mission_test_fields)
- At the end of every PUT, inspect the touched-field set:
- any red write on state in {pending, skipped, blocked} → state=executed
+ auto-stamp executed_at=now() if absent
- any blue write on state=executed → state=reviewed_by_blue
- /transition endpoint kept for back-fill/admin use, not called from UI.
Frontend MissionTestPage
- Removed the transition-buttons header block and the `transition`
mutation. State pill stays as a passive indicator.
- New labels: "Not started" / "Awaiting review" / "Reviewed" describe
the implicit lifecycle, no longer exposing the state-machine concept.
E2E
- The SPA test that clicked `transition-executed` now verifies the
implicit promotion: typing red fields and saving flips the pill from
"Not started" → "Awaiting review", no button click required.
Spec
- §4 reword: "Cycle de vie implicite, piloté par les écritures" replaces
the old "Workflow par test instance" bullet.
Tests
- 3 new pytest: red_command-alone implicit execute + auto-stamp,
blue write promotes executed→reviewed, blue write on pending no-op.
- 142 pytest + 49 Playwright green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -618,6 +618,82 @@ def test_red_setting_executed_at_on_pending_auto_transitions_to_executed(
|
||||
assert forbidden.status_code == 403
|
||||
|
||||
|
||||
def test_red_writing_any_red_field_implicitly_executes_and_stamps(
|
||||
client, admin_token, catalogue, red_user
|
||||
):
|
||||
"""Post-amendement 2026-05-15 bis: the explicit state-machine workflow
|
||||
is gone. Writing ANY red field on a non-executed test promotes the
|
||||
state to `executed`; if the operator didn't supply an `executed_at`,
|
||||
the service auto-stamps `now()` so the persisted row is internally
|
||||
consistent."""
|
||||
mission = _make_mission(
|
||||
client, admin_token, name="m7-implicit-execute",
|
||||
scenario_id=catalogue["scenario"]["id"], red_id=red_user["id"],
|
||||
)
|
||||
tid = _first_test_id(mission)
|
||||
r = client.put(
|
||||
f"/api/v1/missions/{mission['id']}/tests/{tid}",
|
||||
headers=_bearer(red_user["token"]),
|
||||
json={"red_command": "whoami /priv"}, # no executed_at!
|
||||
)
|
||||
assert r.status_code == 200, r.get_data(as_text=True)
|
||||
body = r.get_json()
|
||||
assert body["state"] == "executed"
|
||||
assert body["red_command"] == "whoami /priv"
|
||||
assert body["executed_at"] is not None # auto-stamped
|
||||
assert body["executed_at_overridden"] is False
|
||||
|
||||
|
||||
def test_blue_writing_any_blue_field_promotes_executed_to_reviewed(
|
||||
client, admin_token, catalogue, red_user, blue_user
|
||||
):
|
||||
"""A blue write on an executed test implicitly promotes the state to
|
||||
`reviewed_by_blue` — no explicit transition required from the UI."""
|
||||
mission = _make_mission(
|
||||
client, admin_token, name="m7-implicit-review",
|
||||
scenario_id=catalogue["scenario"]["id"],
|
||||
red_id=red_user["id"], blue_id=blue_user["id"],
|
||||
)
|
||||
tid = _first_test_id(mission)
|
||||
# Red executes (also implicit).
|
||||
client.put(
|
||||
f"/api/v1/missions/{mission['id']}/tests/{tid}",
|
||||
headers=_bearer(red_user["token"]),
|
||||
json={"red_command": "powershell -enc Y2FsYw=="},
|
||||
)
|
||||
# Blue reviews by writing a comment — state should auto-promote.
|
||||
r = client.put(
|
||||
f"/api/v1/missions/{mission['id']}/tests/{tid}",
|
||||
headers=_bearer(blue_user["token"]),
|
||||
json={"blue_comment_md": "alert raised on EDR"},
|
||||
)
|
||||
assert r.status_code == 200, r.get_data(as_text=True)
|
||||
body = r.get_json()
|
||||
assert body["state"] == "reviewed_by_blue"
|
||||
|
||||
|
||||
def test_blue_write_on_pending_does_not_auto_execute(
|
||||
client, admin_token, catalogue, blue_user
|
||||
):
|
||||
"""A blue write on a still-pending test does NOT auto-execute — only
|
||||
red's writes imply execution (per user feedback: 'quand on saisit des
|
||||
informations côtés redteam cela signifie qu'il a été exécuté')."""
|
||||
mission = _make_mission(
|
||||
client, admin_token, name="m7-blue-on-pending",
|
||||
scenario_id=catalogue["scenario"]["id"], blue_id=blue_user["id"],
|
||||
)
|
||||
tid = _first_test_id(mission)
|
||||
r = client.put(
|
||||
f"/api/v1/missions/{mission['id']}/tests/{tid}",
|
||||
headers=_bearer(blue_user["token"]),
|
||||
json={"blue_comment_md": "pre-emptive note"},
|
||||
)
|
||||
assert r.status_code == 200
|
||||
body = r.get_json()
|
||||
assert body["state"] == "pending" # unchanged
|
||||
assert body["executed_at"] is None # not stamped
|
||||
|
||||
|
||||
def test_red_setting_executed_at_from_skipped_state_auto_transitions(
|
||||
client, admin_token, catalogue, red_user
|
||||
):
|
||||
|
||||
Reference in New Issue
Block a user