"""AP1.7 — Operational API and Actor Service Tokens.""" from __future__ import annotations import hashlib from auth import AUTH_HEADER from services import actors as actor_service from services.actor_service_tokens import ( TOKEN_PREFIX, _hash_token, create_service_token, validate_service_token, ) from tests.factories import provision_user_in_tenant from tests.test_initiatives_actions import _auth, _create_initiative, _login def test_token_hash_roundtrip(): raw = f"{TOKEN_PREFIX}abc123_secretpart" assert _hash_token(raw) == hashlib.sha256(raw.encode()).hexdigest() def test_validate_service_token_rejects_garbage(): assert validate_service_token("not-a-token") is None assert validate_service_token("") is None def test_operational_api_with_service_token(client): admin = provision_user_in_tenant(tenant_role="admin") token = _login(client, admin) agent = actor_service.create_actor( tenant_id=admin["tenant_id"], actor_type="agent", name="Cursor Agent", ) created = create_service_token( tenant_id=admin["tenant_id"], actor_id=agent["id"], label="Test Agent", created_by_user_id=admin["id"], ) actor_token = created["token"] me = client.get( "/api/operational/me", headers={"X-Actor-Token": actor_token}, ) assert me.status_code == 200 body = me.json() assert body["ok"] is True assert body["actor_id"] == agent["id"] assert body["data"]["auth_source"] == "service_token" initiative = _create_initiative(client, token, title="Agent Test Initiative") initiative_id = initiative.json()["id"] ctx = client.get( f"/api/operational/initiatives/{initiative_id}/context", headers={"X-Actor-Token": actor_token}, ) assert ctx.status_code == 200 assert ctx.json()["data"]["title"] == "Agent Test Initiative" next_action = client.get( f"/api/operational/next-action?initiative_id={initiative_id}&limit=3", headers={"X-Actor-Token": actor_token}, ) assert next_action.status_code == 200 assert isinstance(next_action.json()["data"], list) def test_operational_token_missing_capability(client): admin = provision_user_in_tenant(tenant_role="admin") agent = actor_service.create_actor( tenant_id=admin["tenant_id"], actor_type="agent", name="Limited Agent", ) created = create_service_token( tenant_id=admin["tenant_id"], actor_id=agent["id"], label="Read-only", created_by_user_id=admin["id"], capabilities=["kairo.action.read"], ) res = client.post( "/api/operational/evidence", json={ "initiative_id": "00000000-0000-0000-0000-000000000001", "title": "Should fail", }, headers={"X-Actor-Token": created["token"]}, ) assert res.status_code == 403 def test_create_service_token_via_api(client): admin = provision_user_in_tenant(tenant_role="admin") token = _login(client, admin) agent = actor_service.create_actor( tenant_id=admin["tenant_id"], actor_type="agent", name="API Token Agent", ) res = client.post( f"/api/actors/{agent['id']}/service-tokens", json={"label": "Cursor"}, headers=_auth(token), ) assert res.status_code == 201 body = res.json() assert body["token"].startswith(TOKEN_PREFIX) assert body["actor_id"] == agent["id"] listed = client.get( f"/api/actors/{agent['id']}/service-tokens", headers=_auth(token), ) assert listed.status_code == 200 assert len(listed.json()) >= 1