mirror of
https://github.com/agentrhq/authsome.git
synced 2026-09-19 01:34:19 +08:00
6ad51760a8
- Set requires-python to >=3.13, target-version to py313, line-length to 120 - Add LICENSE file (MIT) and license-files field - Update author name/email to Manoj Bajaj <manojbajaj95@gmail.com> - Add py.typed marker, ruff config, and coverage config - Fix test_flows.py: use LocalFileCryptoBackend instead of KeyringCryptoBackend - Fix test_registry.py: assert "github" instead of non-existent "anthropic" bundled provider Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
153 lines
5.4 KiB
Python
153 lines
5.4 KiB
Python
"""Tests for authentication flows."""
|
|
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
from authsome.crypto.local_file_crypto import LocalFileCryptoBackend
|
|
from authsome.errors import AuthenticationFailedError, CredentialMissingError
|
|
from authsome.flows.api_key import ApiKeyEnvFlow, ApiKeyPromptFlow
|
|
from authsome.models.enums import AuthType, ConnectionStatus, FlowType
|
|
from authsome.models.provider import ApiKeyConfig, ProviderDefinition
|
|
|
|
|
|
def _make_api_key_provider() -> ProviderDefinition:
|
|
return ProviderDefinition(
|
|
name="testapi",
|
|
display_name="Test API",
|
|
auth_type=AuthType.API_KEY,
|
|
flow=FlowType.API_KEY_PROMPT,
|
|
api_key=ApiKeyConfig(
|
|
header_name="Authorization",
|
|
header_prefix="Bearer",
|
|
env_var="TEST_API_KEY",
|
|
),
|
|
)
|
|
|
|
|
|
class TestApiKeyPromptFlow:
|
|
"""API key prompt flow tests."""
|
|
|
|
@pytest.fixture
|
|
def crypto(self, tmp_path: Path) -> LocalFileCryptoBackend:
|
|
return LocalFileCryptoBackend(tmp_path)
|
|
|
|
def test_successful_login(self, crypto: LocalFileCryptoBackend) -> None:
|
|
flow = ApiKeyPromptFlow()
|
|
provider = _make_api_key_provider()
|
|
|
|
with patch("authsome.flows.api_key.getpass.getpass", return_value="sk-test-key-123"):
|
|
record = flow.authenticate(
|
|
provider=provider,
|
|
crypto=crypto,
|
|
profile="default",
|
|
connection_name="default",
|
|
)
|
|
|
|
assert record.provider == "testapi"
|
|
assert record.profile == "default"
|
|
assert record.connection_name == "default"
|
|
assert record.auth_type == AuthType.API_KEY
|
|
assert record.status == ConnectionStatus.CONNECTED
|
|
assert record.api_key is not None
|
|
# Verify the encrypted key can be decrypted
|
|
decrypted = crypto.decrypt(record.api_key)
|
|
assert decrypted == "sk-test-key-123"
|
|
|
|
def test_empty_key_rejected(self, crypto: LocalFileCryptoBackend) -> None:
|
|
flow = ApiKeyPromptFlow()
|
|
provider = _make_api_key_provider()
|
|
|
|
with patch("authsome.flows.api_key.getpass.getpass", return_value=""):
|
|
with pytest.raises(AuthenticationFailedError, match="cannot be empty"):
|
|
flow.authenticate(
|
|
provider=provider,
|
|
crypto=crypto,
|
|
profile="default",
|
|
connection_name="default",
|
|
)
|
|
|
|
def test_whitespace_only_rejected(self, crypto: LocalFileCryptoBackend) -> None:
|
|
flow = ApiKeyPromptFlow()
|
|
provider = _make_api_key_provider()
|
|
|
|
with patch("authsome.flows.api_key.getpass.getpass", return_value=" "):
|
|
with pytest.raises(AuthenticationFailedError, match="cannot be empty"):
|
|
flow.authenticate(
|
|
provider=provider,
|
|
crypto=crypto,
|
|
profile="default",
|
|
connection_name="default",
|
|
)
|
|
|
|
def test_missing_api_key_config(self, crypto: LocalFileCryptoBackend) -> None:
|
|
flow = ApiKeyPromptFlow()
|
|
provider = ProviderDefinition(
|
|
name="noconfig",
|
|
display_name="No Config",
|
|
auth_type=AuthType.API_KEY,
|
|
flow=FlowType.API_KEY_PROMPT,
|
|
)
|
|
with pytest.raises(AuthenticationFailedError, match="missing 'api_key'"):
|
|
flow.authenticate(
|
|
provider=provider,
|
|
crypto=crypto,
|
|
profile="default",
|
|
connection_name="default",
|
|
)
|
|
|
|
|
|
class TestApiKeyEnvFlow:
|
|
"""API key env import flow tests."""
|
|
|
|
@pytest.fixture
|
|
def crypto(self, tmp_path: Path) -> LocalFileCryptoBackend:
|
|
return LocalFileCryptoBackend(tmp_path)
|
|
|
|
def test_successful_env_import(self, crypto: LocalFileCryptoBackend, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.setenv("TEST_API_KEY", "env-key-value")
|
|
flow = ApiKeyEnvFlow()
|
|
provider = _make_api_key_provider()
|
|
|
|
record = flow.authenticate(
|
|
provider=provider,
|
|
crypto=crypto,
|
|
profile="default",
|
|
connection_name="default",
|
|
)
|
|
|
|
assert record.status == ConnectionStatus.CONNECTED
|
|
assert record.api_key is not None
|
|
assert crypto.decrypt(record.api_key) == "env-key-value"
|
|
|
|
def test_missing_env_var(self, crypto: LocalFileCryptoBackend, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.delenv("TEST_API_KEY", raising=False)
|
|
flow = ApiKeyEnvFlow()
|
|
provider = _make_api_key_provider()
|
|
|
|
with pytest.raises(CredentialMissingError, match="not set or empty"):
|
|
flow.authenticate(
|
|
provider=provider,
|
|
crypto=crypto,
|
|
profile="default",
|
|
connection_name="default",
|
|
)
|
|
|
|
def test_no_env_var_defined(self, crypto: LocalFileCryptoBackend) -> None:
|
|
flow = ApiKeyEnvFlow()
|
|
provider = ProviderDefinition(
|
|
name="noenv",
|
|
display_name="No Env",
|
|
auth_type=AuthType.API_KEY,
|
|
flow=FlowType.API_KEY_ENV,
|
|
api_key=ApiKeyConfig(env_var=None),
|
|
)
|
|
with pytest.raises(AuthenticationFailedError, match="does not define an env_var"):
|
|
flow.authenticate(
|
|
provider=provider,
|
|
crypto=crypto,
|
|
profile="default",
|
|
connection_name="default",
|
|
)
|