"""Tests for Authentication System (core/auth.py).""" import pytest from core.auth import AuthManager @pytest.fixture def auth_with_key(monkeypatch): """Create AuthManager with a known master key.""" monkeypatch.setenv("MASTER_API_KEY", "test-master-key-12345") return AuthManager() @pytest.fixture def auth_without_key(monkeypatch): """Create AuthManager without env key (auto-generates).""" monkeypatch.delenv("MASTER_API_KEY", raising=False) return AuthManager() class TestMasterKeyInit: """Test master key initialization.""" def test_loads_key_from_env(self, auth_with_key): """Master key should be loaded from MASTER_API_KEY env var.""" assert auth_with_key.master_api_key == "test-master-key-12345" def test_generates_key_when_missing(self, auth_without_key): """Should auto-generate a key when env var is not set.""" assert auth_without_key.master_api_key is not None assert len(auth_without_key.master_api_key) > 20 def test_generated_keys_are_unique(self, monkeypatch): """Each init without env should produce a different key.""" monkeypatch.delenv("MASTER_API_KEY", raising=False) a = AuthManager() b = AuthManager() assert a.master_api_key != b.master_api_key class TestMasterKeyValidation: """Test master key validation.""" def test_valid_key_accepted(self, auth_with_key): """Correct master key should be accepted.""" assert auth_with_key.validate_master_key("test-master-key-12345") is True def test_invalid_key_rejected(self, auth_with_key): """Wrong master key should be rejected.""" assert auth_with_key.validate_master_key("wrong-key") is False def test_empty_key_rejected(self, auth_with_key): """Empty string should be rejected.""" assert auth_with_key.validate_master_key("") is False def test_timing_safe_comparison(self, auth_with_key): """Validation should use timing-safe comparison (secrets.compare_digest).""" # This is a behavioral test: both wrong keys should take similar time # We mainly verify the method works correctly for various inputs assert auth_with_key.validate_master_key("test-master-key-12345") is True assert auth_with_key.validate_master_key("test-master-key-12346") is False def test_get_master_key(self, auth_with_key): """get_master_key should return the current key.""" assert auth_with_key.get_master_key() == "test-master-key-12345" class TestProjectKeys: """Test project-specific key management.""" def test_add_project_key_custom(self, auth_with_key): """Should store a custom project key.""" auth_with_key.add_project_key("proj1", "my-project-key") assert auth_with_key.validate_project_key("proj1", "my-project-key") is True def test_add_project_key_generated(self, auth_with_key): """Should generate a key when none provided.""" key = auth_with_key.add_project_key("proj1") assert key is not None assert len(key) > 20 assert auth_with_key.validate_project_key("proj1", key) is True def test_project_key_wrong_project(self, auth_with_key): """Project key should not work for a different project.""" auth_with_key.add_project_key("proj1", "key-for-proj1") # proj2 has no key, so it falls back to master key assert auth_with_key.validate_project_key("proj2", "key-for-proj1") is False def test_fallback_to_master_key(self, auth_with_key): """Projects without a specific key should accept master key.""" assert auth_with_key.validate_project_key("no-key-project", "test-master-key-12345") is True def test_remove_project_key(self, auth_with_key): """Removing a project key should make it fall back to master.""" auth_with_key.add_project_key("proj1", "proj-key") auth_with_key.remove_project_key("proj1") # After removal, should fall back to master key assert auth_with_key.validate_project_key("proj1", "proj-key") is False assert auth_with_key.validate_project_key("proj1", "test-master-key-12345") is True def test_remove_nonexistent_key(self, auth_with_key): """Removing a key for a project that has none should not raise.""" auth_with_key.remove_project_key("nonexistent") # Should not raise def test_has_project_key(self, auth_with_key): """has_project_key should reflect current state.""" assert auth_with_key.has_project_key("proj1") is False auth_with_key.add_project_key("proj1", "key") assert auth_with_key.has_project_key("proj1") is True auth_with_key.remove_project_key("proj1") assert auth_with_key.has_project_key("proj1") is False