"""SimulationTemplate CRUD: list, create, get, patch, delete + RBAC + dedup.""" from __future__ import annotations from flask.testing import FlaskClient from backend.app.extensions import db from backend.app.models import User from backend.app.models.simulation_template import SimulationTemplate from backend.tests.conftest import auth_headers as _h # noqa: E402 # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- def _make_template(client: FlaskClient, token: str, **kw) -> dict: payload = {"name": "Template Alpha", **kw} resp = client.post("/api/templates", headers=_h(token), json=payload) assert resp.status_code == 201, resp.get_json() return resp.get_json() # --------------------------------------------------------------------------- # List # --------------------------------------------------------------------------- def test_list_templates_empty(client: FlaskClient, admin_token: str) -> None: resp = client.get("/api/templates", headers=_h(admin_token)) assert resp.status_code == 200 assert resp.get_json() == [] def test_list_templates_soc_forbidden(client: FlaskClient, soc_token: str) -> None: resp = client.get("/api/templates", headers=_h(soc_token)) assert resp.status_code == 403 def test_list_templates_unauthenticated(client: FlaskClient) -> None: resp = client.get("/api/templates") assert resp.status_code == 401 # --------------------------------------------------------------------------- # Create # --------------------------------------------------------------------------- def test_create_template_as_admin( client: FlaskClient, admin_user: User, admin_token: str ) -> None: body = _make_template( client, admin_token, description="desc", commands="cmd", prerequisites="prereq", ) assert body["name"] == "Template Alpha" assert body["description"] == "desc" assert body["commands"] == "cmd" assert body["prerequisites"] == "prereq" assert body["techniques"] == [] assert body["tactics"] == [] assert body["created_by"] == {"id": admin_user.id, "username": "admin1"} assert body["id"] is not None def test_create_template_as_redteam( client: FlaskClient, redteam_user: User, redteam_token: str ) -> None: body = _make_template(client, redteam_token) assert body["created_by"]["username"] == "redteam1" def test_create_template_soc_forbidden(client: FlaskClient, soc_token: str) -> None: resp = client.post( "/api/templates", headers=_h(soc_token), json={"name": "T"} ) assert resp.status_code == 403 def test_create_template_missing_name(client: FlaskClient, admin_token: str) -> None: resp = client.post("/api/templates", headers=_h(admin_token), json={}) assert resp.status_code == 400 assert "name" in resp.get_json()["error"] def test_create_template_duplicate_name_409( client: FlaskClient, admin_token: str ) -> None: _make_template(client, admin_token) resp = client.post( "/api/templates", headers=_h(admin_token), json={"name": "Template Alpha"} ) assert resp.status_code == 409 assert "already exists" in resp.get_json()["error"] def test_create_template_unknown_technique_id_400( client: FlaskClient, admin_token: str ) -> None: resp = client.post( "/api/templates", headers=_h(admin_token), json={"name": "T", "technique_ids": ["T9999.999"]}, ) assert resp.status_code == 400 assert "unknown technique id" in resp.get_json()["error"] def test_create_template_unknown_tactic_id_400( client: FlaskClient, admin_token: str ) -> None: resp = client.post( "/api/templates", headers=_h(admin_token), json={"name": "T", "tactic_ids": ["TA9999"]}, ) assert resp.status_code == 400 assert "unknown tactic id" in resp.get_json()["error"] # --------------------------------------------------------------------------- # Get single # --------------------------------------------------------------------------- def test_get_template(client: FlaskClient, admin_token: str) -> None: created = _make_template(client, admin_token) resp = client.get(f"/api/templates/{created['id']}", headers=_h(admin_token)) assert resp.status_code == 200 assert resp.get_json()["id"] == created["id"] def test_get_template_not_found(client: FlaskClient, admin_token: str) -> None: resp = client.get("/api/templates/9999", headers=_h(admin_token)) assert resp.status_code == 404 def test_get_template_soc_forbidden( client: FlaskClient, admin_token: str, soc_token: str ) -> None: created = _make_template(client, admin_token) resp = client.get(f"/api/templates/{created['id']}", headers=_h(soc_token)) assert resp.status_code == 403 # --------------------------------------------------------------------------- # Patch # --------------------------------------------------------------------------- def test_patch_template_name(client: FlaskClient, admin_token: str) -> None: created = _make_template(client, admin_token) resp = client.patch( f"/api/templates/{created['id']}", headers=_h(admin_token), json={"name": "Renamed"}, ) assert resp.status_code == 200 assert resp.get_json()["name"] == "Renamed" assert resp.get_json()["updated_at"] is not None def test_patch_template_empty_name_rejected( client: FlaskClient, admin_token: str ) -> None: created = _make_template(client, admin_token) resp = client.patch( f"/api/templates/{created['id']}", headers=_h(admin_token), json={"name": ""}, ) assert resp.status_code == 400 def test_patch_template_unknown_field_rejected( client: FlaskClient, admin_token: str ) -> None: created = _make_template(client, admin_token) resp = client.patch( f"/api/templates/{created['id']}", headers=_h(admin_token), json={"bogus_field": "x"}, ) assert resp.status_code == 400 assert "unknown fields" in resp.get_json()["error"] def test_patch_template_duplicate_name_409( client: FlaskClient, admin_token: str ) -> None: _make_template(client, admin_token, name="T1") t2 = _make_template(client, admin_token, name="T2") resp = client.patch( f"/api/templates/{t2['id']}", headers=_h(admin_token), json={"name": "T1"}, ) assert resp.status_code == 409 def test_patch_template_soc_forbidden( client: FlaskClient, admin_token: str, soc_token: str ) -> None: created = _make_template(client, admin_token) resp = client.patch( f"/api/templates/{created['id']}", headers=_h(soc_token), json={"name": "X"}, ) assert resp.status_code == 403 def test_patch_template_not_found(client: FlaskClient, admin_token: str) -> None: resp = client.patch( "/api/templates/9999", headers=_h(admin_token), json={"name": "X"} ) assert resp.status_code == 404 def test_patch_template_unknown_technique_id_400( client: FlaskClient, admin_token: str ) -> None: created = _make_template(client, admin_token) resp = client.patch( f"/api/templates/{created['id']}", headers=_h(admin_token), json={"technique_ids": ["T9999.999"]}, ) assert resp.status_code == 400 assert "unknown technique id" in resp.get_json()["error"] def test_patch_template_unknown_tactic_id_400( client: FlaskClient, admin_token: str ) -> None: created = _make_template(client, admin_token) resp = client.patch( f"/api/templates/{created['id']}", headers=_h(admin_token), json={"tactic_ids": ["TA9999"]}, ) assert resp.status_code == 400 assert "unknown tactic id" in resp.get_json()["error"] # --------------------------------------------------------------------------- # Delete # --------------------------------------------------------------------------- def test_delete_template( client: FlaskClient, app, admin_token: str ) -> None: created = _make_template(client, admin_token) resp = client.delete(f"/api/templates/{created['id']}", headers=_h(admin_token)) assert resp.status_code == 204 with app.app_context(): assert db.session.get(SimulationTemplate, created["id"]) is None def test_delete_template_not_found(client: FlaskClient, admin_token: str) -> None: resp = client.delete("/api/templates/9999", headers=_h(admin_token)) assert resp.status_code == 404 def test_delete_template_soc_forbidden( client: FlaskClient, admin_token: str, soc_token: str ) -> None: created = _make_template(client, admin_token) resp = client.delete(f"/api/templates/{created['id']}", headers=_h(soc_token)) assert resp.status_code == 403 # --------------------------------------------------------------------------- # List returns ordered by name # --------------------------------------------------------------------------- def test_list_templates_ordered_by_name( client: FlaskClient, admin_token: str ) -> None: for name in ("Zebra", "Alpha", "Midpoint"): _make_template(client, admin_token, name=name) body = client.get("/api/templates", headers=_h(admin_token)).get_json() names = [t["name"] for t in body] assert names == sorted(names)