from types import SimpleNamespace from unittest.mock import MagicMock import cherrypy import jwt import pytest from repeater.web.auth.api_tokens import APITokenManager from repeater.web.auth.cherrypy_tool import check_auth from repeater.web.auth.jwt_handler import JWTHandler def test_jwt_handler_create_and_verify_and_invalid_cases(): secret = "test-secret-key-minimum-32-bytes!!" h = JWTHandler(secret, expiry_minutes=15) token = h.create_jwt("admin", "client-1") payload = h.verify_jwt(token) assert payload is not None assert payload["sub"] == "admin" assert payload["client_id"] == "client-1" expired = jwt.encode( {"sub": "admin", "client_id": "c", "iat": 1, "exp": 1}, secret, algorithm="HS256" ) assert h.verify_jwt(expired) is None assert h.verify_jwt("not-a-token") is None def test_api_token_manager_happy_paths_and_revoke_false(): db = SimpleNamespace( create_api_token=MagicMock(return_value=10), verify_api_token=MagicMock(return_value={"id": 10, "name": "n1"}), revoke_api_token=MagicMock(side_effect=[True, False]), list_api_tokens=MagicMock(return_value=[{"id": 10, "name": "n1"}]), ) mgr = APITokenManager(sqlite_handler=db, secret_key="k") token_id, plaintext = mgr.create_token("n1") assert token_id == 10 assert isinstance(plaintext, str) assert len(plaintext) == 64 verified = mgr.verify_token(plaintext) assert verified["id"] == 10 assert mgr.revoke_token(10) is True assert mgr.revoke_token(11) is False assert mgr.list_tokens()[0]["name"] == "n1" def _set_cp(monkeypatch, method="GET", path="/api/private", headers=None, params=None, cfg=None): req = SimpleNamespace( method=method, path_info=path, headers=headers or {}, params=params or {}, user=None, ) resp = SimpleNamespace(status=200, headers={}) monkeypatch.setattr(cherrypy, "request", req, raising=False) monkeypatch.setattr(cherrypy, "response", resp, raising=False) monkeypatch.setattr(cherrypy, "config", cfg or {}, raising=False) return req, resp def test_check_auth_skips_options_and_login(monkeypatch): _set_cp(monkeypatch, method="OPTIONS") assert check_auth() is None _set_cp(monkeypatch, method="GET", path="/auth/login") assert check_auth() is None def test_check_auth_missing_handlers_raises_http_500(monkeypatch): _set_cp(monkeypatch, cfg={}) with pytest.raises(cherrypy.HTTPError) as exc_info: check_auth() assert exc_info.value.status == 500 def test_check_auth_accepts_bearer_token(monkeypatch): jwt_handler = SimpleNamespace(verify_jwt=lambda _t: {"sub": "admin", "client_id": "c1"}) token_manager = SimpleNamespace(verify_token=lambda _k: None) req, _resp = _set_cp( monkeypatch, headers={"Authorization": "Bearer abc"}, cfg={"jwt_handler": jwt_handler, "token_manager": token_manager}, ) assert check_auth() is None assert req.user["auth_type"] == "jwt" def test_check_auth_accepts_query_token_and_removes_it(monkeypatch): jwt_handler = SimpleNamespace(verify_jwt=lambda _t: {"sub": "admin", "client_id": "c2"}) token_manager = SimpleNamespace(verify_token=lambda _k: None) req, _resp = _set_cp( monkeypatch, params={"token": "xyz", "x": "1"}, cfg={"jwt_handler": jwt_handler, "token_manager": token_manager}, ) assert check_auth() is None assert req.user["auth_type"] == "jwt_query" assert "token" not in req.params def test_check_auth_accepts_api_key(monkeypatch): jwt_handler = SimpleNamespace(verify_jwt=lambda _t: None) token_manager = SimpleNamespace(verify_token=lambda _k: {"id": 3, "name": "svc"}) req, _resp = _set_cp( monkeypatch, headers={"X-API-Key": "k"}, cfg={"jwt_handler": jwt_handler, "token_manager": token_manager}, ) assert check_auth() is None assert req.user["auth_type"] == "api_token" def test_check_auth_unauthorized_raises_http_error(monkeypatch): jwt_handler = SimpleNamespace(verify_jwt=lambda _t: None) token_manager = SimpleNamespace(verify_token=lambda _k: None) _set_cp(monkeypatch, cfg={"jwt_handler": jwt_handler, "token_manager": token_manager}) with pytest.raises(cherrypy.HTTPError): check_auth()