Files
mcp-synology-container/tests/test_auth.py
T
marcus 5b14af8ea1 chore: ruff autofix — import sorting, remove unused imports
Mechanical cleanup via `ruff check --fix` + `ruff format`:
- cli.py, test_auth.py: import sorting (isort convention)
- cli.py: remove unused AuthenticationError import in _run_setup
- config.py: remove unused `field` import
- test_auth.py: remove unused MagicMock import
- test_config.py: remove unused Path import

No functional change. All 131 tests remain green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 09:51:12 +02:00

169 lines
4.8 KiB
Python

"""Tests for auth.py."""
from unittest.mock import AsyncMock, patch
import pytest
from mcp_synology_container.auth import AuthenticationError, AuthManager
from mcp_synology_container.config import AppConfig, ConnectionConfig
def make_config(host: str = "nas.local") -> AppConfig:
return AppConfig(
schema_version=1,
connection=ConnectionConfig(host=host, port=443, https=True, verify_ssl=True),
)
def test_resolve_credentials_from_env(monkeypatch):
monkeypatch.setenv("SYNOLOGY_USERNAME", "admin")
monkeypatch.setenv("SYNOLOGY_PASSWORD", "secret")
config = make_config()
auth = AuthManager(config)
username, password, device_token = auth.resolve_credentials()
assert username == "admin"
assert password == "secret"
assert device_token is None
def test_resolve_credentials_no_credentials(monkeypatch):
monkeypatch.delenv("SYNOLOGY_USERNAME", raising=False)
monkeypatch.delenv("SYNOLOGY_PASSWORD", raising=False)
config = make_config()
auth = AuthManager(config)
with patch("keyring.get_password", return_value=None):
with pytest.raises(AuthenticationError, match="No credentials found"):
auth.resolve_credentials()
def test_resolve_credentials_from_keyring(monkeypatch):
monkeypatch.delenv("SYNOLOGY_USERNAME", raising=False)
monkeypatch.delenv("SYNOLOGY_PASSWORD", raising=False)
def mock_get_password(service, key):
data = {"username": "keyring_user", "password": "keyring_pass", "device_token": "tok123"}
return data.get(key)
config = make_config()
auth = AuthManager(config)
with patch("keyring.get_password", side_effect=mock_get_password):
username, password, device_token = auth.resolve_credentials()
assert username == "keyring_user"
assert password == "keyring_pass"
assert device_token == "tok123"
def test_store_credentials_success():
config = make_config()
auth = AuthManager(config)
with patch("keyring.set_password") as mock_set:
result = auth.store_credentials("user", "pass")
assert result is True
assert mock_set.call_count == 2
def test_store_credentials_keyring_unavailable():
config = make_config()
auth = AuthManager(config)
with patch("keyring.set_password", side_effect=Exception("no keyring")):
result = auth.store_credentials("user", "pass")
assert result is False
@pytest.mark.asyncio
async def test_login_success():
config = make_config()
auth = AuthManager(config)
mock_client = AsyncMock()
mock_client.request.return_value = {"sid": "test_session_id"}
with patch.object(auth, "resolve_credentials", return_value=("user", "pass", None)):
sid = await auth.login(mock_client)
assert sid == "test_session_id"
mock_client.request.assert_called_once()
call_kwargs = mock_client.request.call_args
assert call_kwargs[0][0] == "SYNO.API.Auth"
assert call_kwargs[0][1] == "login"
@pytest.mark.asyncio
async def test_login_with_device_token():
config = make_config()
auth = AuthManager(config)
mock_client = AsyncMock()
mock_client.request.return_value = {"sid": "test_sid"}
with patch.object(auth, "resolve_credentials", return_value=("user", "pass", "dev_token")):
sid = await auth.login(mock_client)
assert sid == "test_sid"
params = mock_client.request.call_args[1]["params"]
assert params["device_id"] == "dev_token"
@pytest.mark.asyncio
async def test_login_2fa_required():
from mcp_synology_container.dsm_client import SynologyError
config = make_config()
auth = AuthManager(config)
mock_client = AsyncMock()
mock_client.request.side_effect = SynologyError("2FA required", code=403)
with patch.object(auth, "resolve_credentials", return_value=("user", "pass", None)):
with pytest.raises(AuthenticationError, match="2FA is required"):
await auth.login(mock_client)
@pytest.mark.asyncio
async def test_login_no_sid_returned():
config = make_config()
auth = AuthManager(config)
mock_client = AsyncMock()
mock_client.request.return_value = {} # No 'sid' key
with patch.object(auth, "resolve_credentials", return_value=("user", "pass", None)):
with pytest.raises(AuthenticationError, match="no session ID"):
await auth.login(mock_client)
@pytest.mark.asyncio
async def test_logout():
config = make_config()
auth = AuthManager(config)
mock_client = AsyncMock()
mock_client.sid = "active_sid"
await auth.logout(mock_client)
mock_client.request.assert_called_once()
assert mock_client.sid is None
@pytest.mark.asyncio
async def test_logout_no_session():
config = make_config()
auth = AuthManager(config)
mock_client = AsyncMock()
mock_client.sid = None
await auth.logout(mock_client)
mock_client.request.assert_not_called()