mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
94 lines
3.1 KiB
Python
94 lines
3.1 KiB
Python
from __future__ import annotations
|
|||
|
|
|
||
|
|
from typing import Any
|
||
|
|
from unittest.mock import AsyncMock, patch
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
from fastapi import HTTPException, status
|
||
|
|
from roboco.api import deps as d
|
||
|
|
from roboco.api.deps import get_current_agent_slug
|
||
|
|
|
||
|
|
|
||
|
|
async def _run(
|
||
|
|
dep: Any,
|
||
|
|
headers: dict[str, str],
|
||
|
|
settings_on: bool,
|
||
|
|
monkeypatch: pytest.MonkeyPatch,
|
||
|
|
) -> Any:
|
||
|
|
monkeypatch.setattr(d.settings, "cloud_auth_enabled", settings_on)
|
||
|
|
db = AsyncMock()
|
||
|
|
response = AsyncMock()
|
||
|
|
# _cloud_auth_agent_context is the gate; stub it to assert it's reached
|
||
|
|
with patch.object(d, "_cloud_auth_agent_context", new=AsyncMock()) as m:
|
||
|
|
m.return_value = type(
|
||
|
|
"Ctx",
|
||
|
|
(),
|
||
|
|
{"agent_id": "00000000-0000-0000-0000-000000000000", "slug": "be-dev-1"},
|
||
|
|
)()
|
||
|
|
return await dep(
|
||
|
|
db=db,
|
||
|
|
response=response,
|
||
|
|
x_agent_id=headers.get("X-Agent-ID"),
|
||
|
|
x_agent_role=headers.get("X-Agent-Role"),
|
||
|
|
x_agent_team=headers.get("X-Agent-Team"),
|
||
|
|
x_agent_token=headers.get("X-Agent-Token"),
|
||
|
|
roboco_session=headers.get("roboco_session"),
|
||
|
|
), m.called
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_cloud_auth_spoof_bare_agent_id_rejected(
|
||
|
|
monkeypatch: pytest.MonkeyPatch,
|
||
|
|
) -> None:
|
||
|
|
# A bare X-Agent-ID with no token/cookie must not reach the gate body.
|
||
|
|
monkeypatch.setattr(d.settings, "cloud_auth_enabled", True)
|
||
|
|
db = AsyncMock()
|
||
|
|
response = AsyncMock()
|
||
|
|
with patch.object(d, "_cloud_auth_agent_context", new=AsyncMock()) as m:
|
||
|
|
m.side_effect = HTTPException(
|
||
|
|
status.HTTP_401_UNAUTHORIZED,
|
||
|
|
"Cloud auth is enabled — agent requests require a valid token.",
|
||
|
|
)
|
||
|
|
with pytest.raises(HTTPException) as exc:
|
||
|
|
await d.get_current_agent_slug(
|
||
|
|
db=db,
|
||
|
|
response=response,
|
||
|
|
x_agent_id="be-dev-1",
|
||
|
|
x_agent_role=None,
|
||
|
|
x_agent_team=None,
|
||
|
|
x_agent_token=None,
|
||
|
|
roboco_session=None,
|
||
|
|
)
|
||
|
|
assert exc.value.status_code == status.HTTP_401_UNAUTHORIZED
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_cloud_auth_routes_through_dual_path(
|
||
|
|
monkeypatch: pytest.MonkeyPatch,
|
||
|
|
) -> None:
|
||
|
|
_, called = await _run(
|
||
|
|
get_current_agent_slug, {"X-Agent-ID": "be-dev-1"}, True, monkeypatch
|
||
|
|
)
|
||
|
|
assert called # cloud mode delegates to _cloud_auth_agent_context
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_dev_mode_unchanged_slug_returns_header(
|
||
|
|
monkeypatch: pytest.MonkeyPatch,
|
||
|
|
) -> None:
|
||
|
|
monkeypatch.setattr(d.settings, "cloud_auth_enabled", False)
|
||
|
|
db = AsyncMock()
|
||
|
|
response = AsyncMock()
|
||
|
|
with patch.object(d, "_cloud_auth_agent_context", new=AsyncMock()) as m:
|
||
|
|
slug = await d.get_current_agent_slug(
|
||
|
|
db=db,
|
||
|
|
response=response,
|
||
|
|
x_agent_id="be-dev-1",
|
||
|
|
x_agent_role=None,
|
||
|
|
x_agent_team=None,
|
||
|
|
x_agent_token=None,
|
||
|
|
roboco_session=None,
|
||
|
|
)
|
||
|
|
assert slug == "be-dev-1"
|
||
|
|
assert not m.called # dev path does not invoke the cloud gate
|