""" Unit tests for src/shared/auth_utils.py - Authentication utilities. Tests cover: - Token management and environment variable loading - Header generation with Bearer tokens - Request configuration creation - Security: no token exposure in logs/debug output """ import os import unittest from pathlib import Path from unittest.mock import patch, MagicMock # Add parent directory to path for imports sys_path = Path(__file__).parent.parent / "src" if str(sys_path) not in __import__('sys').path: __import__('sys').path.insert(0, str(sys_path)) class TestTokenLoading(unittest.TestCase): """Test token loading from environment variables.""" @patch.dict(os.environ, {}, clear=True) def test_no_token_when_env_not_set(self): """Token should be None when FLARES_API_TOKEN not set.""" from src.shared.auth_utils import AuthManager manager = AuthManager() self.assertIsNone(manager._api_token) self.assertFalse(manager.has_api_auth) @patch.dict(os.environ, {'FLARES_API_TOKEN': 'test-token-12345'}) def test_token_loaded_from_env(self): """Token should be loaded from FLARES_API_TOKEN env var.""" # Force reimport to pick up new env import importlib import src.shared.auth_utils as auth_module importlib.reload(auth_module) manager = auth_module.AuthManager() self.assertEqual(manager._api_token, 'test-token-12345') self.assertTrue(manager.has_api_auth) @patch.dict(os.environ, {'FLARES_API_TOKEN': ''}) def test_empty_string_treated_as_no_token(self): """Empty string token should be treated as no token.""" import importlib import src.shared.auth_utils as auth_module importlib.reload(auth_module) manager = auth_module.AuthManager() # Empty string is falsy but still stored self.assertEqual(manager._api_token, '') self.assertFalse(manager.has_api_auth) class TestHeaderGeneration(unittest.TestCase): """Test HTTP header generation with authentication.""" @patch.dict(os.environ, {'FLARES_API_TOKEN': 'my-secret-token'}) def test_headers_include_bearer_token(self): """Headers should include Authorization: Bearer when token set.""" import importlib import src.shared.auth_utils as auth_module importlib.reload(auth_module) manager = auth_module.AuthManager() headers = manager.get_headers() self.assertIn('Authorization', headers) self.assertEqual(headers['Authorization'], 'Bearer my-secret-token') self.assertIn('User-Agent', headers) self.assertIn('FLARES', headers['User-Agent']) @patch.dict(os.environ, {}, clear=True) def test_headers_without_token(self): """Headers should not include Authorization when no token.""" import importlib import src.shared.auth_utils as auth_module importlib.reload(auth_module) manager = auth_module.AuthManager() headers = manager.get_headers() self.assertNotIn('Authorization', headers) self.assertIn('User-Agent', headers) @patch.dict(os.environ, {'FLARES_API_TOKEN': 'Bearer existing-prefix'}) def test_does_not_double_bearer_prefix(self): """Should not add Bearer prefix if token already has it.""" import importlib import src.shared.auth_utils as auth_module importlib.reload(auth_module) manager = auth_module.AuthManager() headers = manager.get_headers() # Should be "Bearer Bearer existing-prefix" which is wrong, # but that's user error - we don't strip prefixes self.assertIn('Authorization', headers) class TestRequestConfig(unittest.TestCase): """Test request configuration creation.""" @patch.dict(os.environ, {'FLARES_API_TOKEN': 'test-token'}) def test_config_includes_headers(self): """Request config should include auth headers.""" import importlib import src.shared.auth_utils as auth_module importlib.reload(auth_module) manager = auth_module.AuthManager() config = manager.create_authenticated_request_config() self.assertIn('headers', config) self.assertIn('Authorization', config['headers']) @patch.dict(os.environ, {}, clear=True) def test_config_without_token(self): """Request config should work without token.""" import importlib import src.shared.auth_utils as auth_module importlib.reload(auth_module) manager = auth_module.AuthManager() config = manager.create_authenticated_request_config() self.assertIn('headers', config) # Should still have User-Agent even without auth self.assertIn('User-Agent', config['headers']) class TestTokenValidation(unittest.TestCase): """Test token format validation.""" def test_token_accepts_various_formats(self): """Token should accept various valid formats.""" from src.shared.auth_utils import AuthManager valid_tokens = [ 'simple-token', 'token-with-dashes-123', 'token_with_underscores', 'abc123XYZ', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.test', # JWT-like ] for token in valid_tokens: with patch.dict(os.environ, {'FLARES_API_TOKEN': token}): import importlib import src.shared.auth_utils as auth_module importlib.reload(auth_module) manager = auth_module.AuthManager() self.assertTrue(manager.has_api_auth, f"Token rejected: {token[:20]}...") class TestSecurity(unittest.TestCase): """Test security properties of auth module.""" @patch.dict(os.environ, {'FLARES_API_TOKEN': 'super-secret-token'}) def test_headers_dict_does_not_expose_raw_token_in_str(self): """String representation of headers should not expose token clearly.""" import importlib import src.shared.auth_utils as auth_module importlib.reload(auth_module) manager = auth_module.AuthManager() headers = manager.get_headers() # The headers dict itself will contain the token (necessary for requests) # But we verify it's in the expected format self.assertEqual(headers['Authorization'], 'Bearer super-secret-token') @patch.dict(os.environ, {'FLARES_API_TOKEN': 'test123'}) def test_default_manager_uses_env_token(self): """get_default_auth_manager() should use same token as AuthManager().""" import importlib import src.shared.auth_utils as auth_module importlib.reload(auth_module) default_mgr = auth_module.get_default_auth_manager() direct_mgr = auth_module.AuthManager() self.assertEqual( default_mgr._api_token, direct_mgr._api_token, "Default manager should use same token as direct instantiation" ) class TestIntegration(unittest.TestCase): """Integration tests for authentication workflow.""" @patch.dict(os.environ, {'FLARES_API_TOKEN': 'integration-test-token'}) def test_full_auth_workflow(self): """Test complete authentication setup workflow.""" import importlib import src.shared.auth_utils as auth_module importlib.reload(auth_module) # Step 1: Create manager manager = auth_module.AuthManager() self.assertTrue(manager.has_api_auth) # Step 2: Get headers headers = manager.get_headers() self.assertIn('Authorization', headers) self.assertEqual( headers['Authorization'], 'Bearer integration-test-token' ) # Step 3: Create request config config = manager.create_authenticated_request_config() self.assertEqual(config['headers'], headers) if __name__ == '__main__': unittest.main()