Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions tests/server/routes/test_default_policies.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from collections.abc import AsyncIterator
from pathlib import Path
from unittest.mock import patch

import httpx
import pytest
Expand Down Expand Up @@ -301,3 +302,38 @@ async def test_list_no_config_policies_when_caps_empty(
data = resp.json()["data"]
config_entries = [p for p in data if p.get("source") == "config"]
assert config_entries == []


# ── Telemetry ─────────────────────────────────────────────────────────


async def test_create_default_policy_emits_telemetry(
policy_client: httpx.AsyncClient,
) -> None:
"""``POST /v1/policies`` emits a ``PolicyRegisteredEvent`` with scope='admin'."""
from omnigent.telemetry.events import PolicyRegisteredEvent

with patch("omnigent.server.routes.default_policies._tel_emit") as mock_emit:
resp = await policy_client.post("/v1/policies", json=_policy_payload())
assert resp.status_code == 200

mock_emit.assert_called_once()
event = mock_emit.call_args[0][0]
assert isinstance(event, PolicyRegisteredEvent)
assert event.scope == "admin"
assert event.session_id is None
assert event.handler == _REGISTERED_HANDLER
assert event.policy_type == "python"


async def test_create_default_policy_no_telemetry_on_error(
policy_client: httpx.AsyncClient,
) -> None:
"""``POST /v1/policies`` does not emit telemetry when the request fails."""
with patch("omnigent.server.routes.default_policies._tel_emit") as mock_emit:
# Duplicate name → 409 before the emit block is reached.
await policy_client.post("/v1/policies", json=_policy_payload(name="dup"))
mock_emit.reset_mock()
resp = await policy_client.post("/v1/policies", json=_policy_payload(name="dup"))
assert resp.status_code == 409
mock_emit.assert_not_called()
41 changes: 41 additions & 0 deletions tests/server/routes/test_session_policies_crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from collections.abc import AsyncIterator
from pathlib import Path
from unittest.mock import patch

import httpx
import pytest
Expand Down Expand Up @@ -255,3 +256,43 @@ async def test_delete_session_policy(client: httpx.AsyncClient, session_id: str)
# Verify it's gone from the session policies
get_resp = await client.get(f"/v1/sessions/{session_id}/policies/{pid}")
assert get_resp.status_code == 404


# ── Telemetry ─────────────────────────────────────────────────────────


async def test_create_session_policy_emits_telemetry(
client: httpx.AsyncClient, session_id: str
) -> None:
"""``POST /v1/sessions/{id}/policies`` emits a ``PolicyRegisteredEvent`` with scope='session'.""" # noqa: E501
from omnigent.telemetry.events import PolicyRegisteredEvent

with patch("omnigent.server.routes.session_policies._tel_emit") as mock_emit:
resp = await client.post(
f"/v1/sessions/{session_id}/policies",
json=_policy_payload(),
)
assert resp.status_code == 200

mock_emit.assert_called_once()
event = mock_emit.call_args[0][0]
assert isinstance(event, PolicyRegisteredEvent)
assert event.scope == "session"
assert event.session_id == session_id
assert event.handler == "https://example.com/policies/eval"
assert event.policy_type == "url"


async def test_create_session_policy_no_telemetry_on_error(
client: httpx.AsyncClient, session_id: str
) -> None:
"""``POST /v1/sessions/{id}/policies`` does not emit telemetry when the request fails."""
with patch("omnigent.server.routes.session_policies._tel_emit") as mock_emit:
# Duplicate name → 409 before the emit block is reached.
await client.post(f"/v1/sessions/{session_id}/policies", json=_policy_payload(name="dup"))
mock_emit.reset_mock()
resp = await client.post(
f"/v1/sessions/{session_id}/policies", json=_policy_payload(name="dup")
)
assert resp.status_code == 409
mock_emit.assert_not_called()
71 changes: 71 additions & 0 deletions tests/test_telemetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,77 @@ def test_host_registry_get_host_installation_id_registered() -> None:
assert registry.get_host_installation_id("host_abc") == "inst-xyz"


# ── PolicyRegisteredEvent ────────────────────────────────────────────────────


def test_build_record_policy_registered_event_admin_scope() -> None:
"""``_build_record`` serialises admin-scope ``PolicyRegisteredEvent`` correctly.

``installation_id``, ``session_id``, and ``anon_user_id`` are promoted to
top-level ``data`` fields; ``handler``, ``policy_type``, and ``scope`` go
into ``params``. Admin policies have ``session_id=None`` which becomes
an empty string in the wire format.
"""
import omnigent.telemetry.client as _mod
from omnigent.telemetry.events import PolicyRegisteredEvent

event = PolicyRegisteredEvent(
installation_id="inst-abc",
handler="omnigent.policies.builtins.safety.ask_on_os_tools",
policy_type="python",
scope="admin",
session_id=None,
anon_user_id="deadbeef01234567",
)
record = _mod._build_record(event)
data = record["data"]

assert data["event_name"] == "PolicyRegisteredEvent"
assert data["installation_id"] == "inst-abc"
assert data["anon_user_id"] == "deadbeef01234567"
# session_id=None → empty string sentinel in wire format
assert data["session_id"] == ""
# Event-specific fields live in params, not at top level.
params = json.loads(data["params"]) if data["params"] else {}
assert params["handler"] == "omnigent.policies.builtins.safety.ask_on_os_tools"
assert params["policy_type"] == "python"
assert params["scope"] == "admin"
# Promoted fields must not leak into params.
assert "installation_id" not in params
assert "session_id" not in params
assert "anon_user_id" not in params


def test_build_record_policy_registered_event_session_scope() -> None:
"""``_build_record`` serialises session-scope ``PolicyRegisteredEvent`` correctly.

``session_id`` is a real string for session-scoped policies and must appear
as the top-level ``session_id`` field, not inside ``params``.
"""
import omnigent.telemetry.client as _mod
from omnigent.telemetry.events import PolicyRegisteredEvent

event = PolicyRegisteredEvent(
installation_id="inst-xyz",
handler="https://example.com/policies/eval",
policy_type="url",
scope="session",
session_id="sess_abc123",
anon_user_id=None,
)
record = _mod._build_record(event)
data = record["data"]

assert data["event_name"] == "PolicyRegisteredEvent"
assert data["session_id"] == "sess_abc123"
assert data["anon_user_id"] is None
params = json.loads(data["params"]) if data["params"] else {}
assert params["scope"] == "session"
assert params["handler"] == "https://example.com/policies/eval"
assert params["policy_type"] == "url"
assert "session_id" not in params


def test_build_record_promotes_host_installation_id() -> None:
"""``_build_record`` lifts ``host_installation_id`` to top-level data."""
import omnigent.telemetry.client as _mod
Expand Down
Loading