"""End-to-end smoke: login → create engagement → list engagement (sprint 1). Uses the testcontainers Postgres scaffold + Flask test client. Each test seeds a single RT-lead user and signs in over the session-cookie surface the frontend will consume. """ from __future__ import annotations from uuid import UUID import pytest from mimic.auth.password import hash_password from mimic.db.models import Group, User, UserGroup from mimic.db.types import UserType from mimic.rbac.matrix import GROUP_PERMISSIONS, GroupName, Permission pytestmark = pytest.mark.integration def _seed_rt_lead( db, email: str = "lead@example.org", password: str = "lead-secret-1", # noqa: S107 ) -> UUID: """Create an rt_lead user + the rt_lead group + the membership link.""" group = db.session.query(Group).filter_by(name=GroupName.RT_LEAD.value).first() if group is None: group = Group(name=GroupName.RT_LEAD.value, description="Red team lead") db.session.add(group) db.session.flush() user = User( email=email, display_name="Lead", type=UserType.RT_LEAD, local_password_hash=hash_password(password, rounds=4), ) db.session.add(user) db.session.flush() db.session.add(UserGroup(user_id=user.id, group_id=group.id, engagement_id=None)) db.session.commit() return user.id def test_login_then_create_and_list_engagement(app, client) -> None: from mimic.extensions import db # noqa: PLC0415 (must follow app fixture) with app.app_context(): _seed_rt_lead(db) # 1. /me before login → 401, uniform envelope. response = client.get("/api/v1/auth/me") assert response.status_code == 401 body = response.get_json() assert body == {"error": "not_authenticated", "message": "no active session"} # 2. login → 200, body shape matches CurrentUser, session cookie set. response = client.post( "/api/v1/auth/login", json={"username": "lead@example.org", "password": "lead-secret-1"}, ) assert response.status_code == 200 user_payload = response.get_json() assert user_payload["username"] == "lead@example.org" assert user_payload["role"] == "rt_lead" assert Permission.ENGAGEMENT_CREATE.value in user_payload["permissions"] assert sorted(user_payload["permissions"]) == sorted( p.value for p in GROUP_PERMISSIONS[GroupName.RT_LEAD] ) # 3. /me after login → 200, same shape. response = client.get("/api/v1/auth/me") assert response.status_code == 200 assert response.get_json()["username"] == "lead@example.org" # 4. POST /engagements → 201, created_by_id is current user. response = client.post( "/api/v1/engagements/", json={"client_name": "Acme Demo", "c2_type": "mythic"}, ) assert response.status_code == 201 engagement = response.get_json() assert engagement["client_name"] == "Acme Demo" # 5. GET /engagements lists it (RT lead sees everything). response = client.get("/api/v1/engagements/") assert response.status_code == 200 listing = response.get_json() assert any(e["id"] == engagement["id"] for e in listing) # 6. logout → 204; subsequent /me → 401. response = client.post("/api/v1/auth/logout") assert response.status_code == 204 response = client.get("/api/v1/auth/me") assert response.status_code == 401 def test_login_rejects_bad_credentials(app, client) -> None: from mimic.extensions import db # noqa: PLC0415 with app.app_context(): _seed_rt_lead(db, email="bob@example.org", password="hunter2") # Wrong password. response = client.post( "/api/v1/auth/login", json={"username": "bob@example.org", "password": "wrong"}, ) assert response.status_code == 401 assert response.get_json() == { "error": "invalid_credentials", "message": "invalid username or password", } # Unknown user — same uniform message (no enumeration leak). response = client.post( "/api/v1/auth/login", json={"username": "ghost@example.org", "password": "hunter2"}, ) assert response.status_code == 401 assert response.get_json() == { "error": "invalid_credentials", "message": "invalid username or password", } def test_logout_without_session_returns_401(app, client) -> None: response = client.post("/api/v1/auth/logout") assert response.status_code == 401 assert response.get_json() == {"error": "not_authenticated", "message": "no active session"}