mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
110 lines
3.7 KiB
Python
110 lines
3.7 KiB
Python
"""Settings routes (/api/settings) are gated behind the panel token.
|
|||
|
|
|
||
|
|
Under cloud_auth the panel reaches these routes with a CEO session cookie; the
|
||
|
|
gate (``require_panel_token``) accepts the cookie OR a valid CEO HMAC token.
|
||
|
|
In dev mode a missing token is a no-op (existing behavior).
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from typing import TYPE_CHECKING
|
||
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
import pytest_asyncio
|
||
|
|
from fastapi import FastAPI
|
||
|
|
from httpx import ASGITransport, AsyncClient
|
||
|
|
from roboco.agents_config import issue_panel_token
|
||
|
|
from roboco.api import deps as _deps
|
||
|
|
from roboco.api.auth.backend import SESSION_COOKIE_NAME
|
||
|
|
from roboco.api.routes.settings import router as settings_router
|
||
|
|
from roboco.db.base import get_db
|
||
|
|
|
||
|
|
if TYPE_CHECKING:
|
||
|
|
from collections.abc import AsyncIterator
|
||
|
|
|
||
|
|
_SECRET = "test-secret-for-settings-routes"
|
||
|
|
_HTTP_200 = 200
|
||
|
|
_HTTP_401 = 401
|
||
|
|
|
||
|
|
|
||
|
|
@pytest_asyncio.fixture
|
||
|
|
async def settings_client(
|
||
|
|
monkeypatch: pytest.MonkeyPatch,
|
||
|
|
) -> AsyncIterator[AsyncClient]:
|
||
|
|
monkeypatch.setenv("ROBOCO_AGENT_AUTH_SECRET", _SECRET)
|
||
|
|
fake_service = MagicMock()
|
||
|
|
fake_service.all = AsyncMock(return_value={})
|
||
|
|
monkeypatch.setattr(
|
||
|
|
"roboco.api.routes.settings.get_settings_service",
|
||
|
|
lambda _db: fake_service,
|
||
|
|
)
|
||
|
|
|
||
|
|
async def _fake_db() -> AsyncIterator[object]:
|
||
|
|
yield object()
|
||
|
|
|
||
|
|
app = FastAPI()
|
||
|
|
app.include_router(settings_router, prefix="/api/settings")
|
||
|
|
app.dependency_overrides[get_db] = _fake_db
|
||
|
|
async with AsyncClient(
|
||
|
|
transport=ASGITransport(app=app), base_url="http://test"
|
||
|
|
) as client:
|
||
|
|
yield client
|
||
|
|
app.dependency_overrides.clear()
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_settings_rejects_no_credential_under_cloud_auth(
|
||
|
|
settings_client: AsyncClient, monkeypatch: pytest.MonkeyPatch
|
||
|
|
) -> None:
|
||
|
|
monkeypatch.setattr(_deps.settings, "cloud_auth_enabled", True)
|
||
|
|
monkeypatch.delenv("ROBOCO_AGENT_AUTH_REQUIRED", raising=False)
|
||
|
|
r = await settings_client.get("/api/settings")
|
||
|
|
assert r.status_code == _HTTP_401
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_settings_accepts_valid_token_under_cloud_auth(
|
||
|
|
settings_client: AsyncClient, monkeypatch: pytest.MonkeyPatch
|
||
|
|
) -> None:
|
||
|
|
monkeypatch.setattr(_deps.settings, "cloud_auth_enabled", True)
|
||
|
|
r = await settings_client.get(
|
||
|
|
"/api/settings", headers={"X-Agent-Token": issue_panel_token()}
|
||
|
|
)
|
||
|
|
assert r.status_code == _HTTP_200
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_settings_accepts_valid_cookie_under_cloud_auth(
|
||
|
|
settings_client: AsyncClient, monkeypatch: pytest.MonkeyPatch
|
||
|
|
) -> None:
|
||
|
|
monkeypatch.setattr(_deps.settings, "cloud_auth_enabled", True)
|
||
|
|
with patch(
|
||
|
|
"roboco.api.deps.resolve_session_user",
|
||
|
|
new=AsyncMock(return_value=MagicMock()),
|
||
|
|
):
|
||
|
|
r = await settings_client.get(
|
||
|
|
"/api/settings", cookies={SESSION_COOKIE_NAME: "valid"}
|
||
|
|
)
|
||
|
|
assert r.status_code == _HTTP_200
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_settings_dev_mode_no_token_passes(
|
||
|
|
settings_client: AsyncClient, monkeypatch: pytest.MonkeyPatch
|
||
|
|
) -> None:
|
||
|
|
monkeypatch.setattr(_deps.settings, "cloud_auth_enabled", False)
|
||
|
|
monkeypatch.delenv("ROBOCO_AGENT_AUTH_REQUIRED", raising=False)
|
||
|
|
r = await settings_client.get("/api/settings")
|
||
|
|
assert r.status_code == _HTTP_200
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_settings_put_rejects_no_credential_under_cloud_auth(
|
||
|
|
settings_client: AsyncClient, monkeypatch: pytest.MonkeyPatch
|
||
|
|
) -> None:
|
||
|
|
monkeypatch.setattr(_deps.settings, "cloud_auth_enabled", True)
|
||
|
|
monkeypatch.delenv("ROBOCO_AGENT_AUTH_REQUIRED", raising=False)
|
||
|
|
r = await settings_client.put("/api/settings/some.key", json={"value": "x"})
|
||
|
|
assert r.status_code == _HTTP_401
|