""" Tests for auth_authorization module. """ from unittest.mock import patch import pytest from src.auth_authorization import ( load_allowed_users, is_user_authorized, _parse_csv_content, ) # Mock CSV data for testing MOCK_CSV_CONTENT = """EMAIL,QA_ACCESS,PROD_ACCESS admin@example.com,true,true qa_only@example.com,true,false prod_only@example.com,false,true no_access@example.com,false,false """ class TestParseCSVContent: """Tests for _parse_csv_content function.""" def test_parse_valid_csv(self): """Test parsing valid CSV content.""" users = _parse_csv_content(MOCK_CSV_CONTENT) assert isinstance(users, dict) assert len(users) == 4 # Check admin user (full access) assert "admin@example.com" in users assert users["admin@example.com"]["qa_access"] is True assert users["admin@example.com"]["prod_access"] is True # Check qa_only user assert "qa_only@example.com" in users assert users["qa_only@example.com"]["qa_access"] is True assert users["qa_only@example.com"]["prod_access"] is False def test_email_normalization(self): """Test that emails are normalized to lowercase.""" csv_content = """EMAIL,QA_ACCESS,PROD_ACCESS Admin@Example.com,true,false """ users = _parse_csv_content(csv_content) # Should be lowercase assert "admin@example.com" in users # Mixed case should not exist assert "Admin@Example.com" not in users def test_invalid_csv_columns(self): """Test handling of CSV with missing required columns.""" csv_content = "WRONG_COLUMN,ANOTHER_COLUMN\nvalue1,value2\n" with pytest.raises(ValueError) as exc_info: _parse_csv_content(csv_content) assert "CSV must contain columns" in str(exc_info.value) def test_whitespace_in_csv(self): """Test that whitespace in CSV values is trimmed properly.""" csv_content = """EMAIL,QA_ACCESS,PROD_ACCESS Admin@Example.com , TRUE , FALSE """ users = _parse_csv_content(csv_content) # Email should be normalized and trimmed assert "admin@example.com" in users assert users["admin@example.com"]["qa_access"] is True assert users["admin@example.com"]["prod_access"] is False def test_empty_csv_file(self): """Test handling of empty CSV file (only headers).""" csv_content = "EMAIL,QA_ACCESS,PROD_ACCESS\n" users = _parse_csv_content(csv_content) assert isinstance(users, dict) assert len(users) == 0 def test_csv_with_empty_email(self): """Test that rows with empty emails are skipped.""" csv_content = """EMAIL,QA_ACCESS,PROD_ACCESS ,true,false """ users = _parse_csv_content(csv_content) assert isinstance(users, dict) assert len(users) == 0 # Empty email row should be skipped class TestLoadAllowedUsers: """Tests for load_allowed_users function.""" @patch("src.auth_authorization._read_csv_file", return_value=MOCK_CSV_CONTENT) def test_load_valid_csv(self, mock_read): """Test loading a valid CSV file.""" users = load_allowed_users() assert isinstance(users, dict) assert len(users) == 4 # Verify _read_csv_file was called mock_read.assert_called_once() @patch( "src.auth_authorization._read_csv_file", side_effect=FileNotFoundError("File not found"), ) def test_file_not_found(self, mock_read): """Test handling of missing CSV file.""" with pytest.raises(FileNotFoundError): load_allowed_users() class TestIsUserAuthorized: """Tests for is_user_authorized function.""" @patch("src.auth_authorization._read_csv_file", return_value=MOCK_CSV_CONTENT) def test_full_access_user_in_prod(self, mock_read): """Test user with full access can access production.""" authorized, reason = is_user_authorized("admin@example.com", "prod") assert authorized is True assert "production" in reason.lower() @patch("src.auth_authorization._read_csv_file", return_value=MOCK_CSV_CONTENT) def test_full_access_user_in_dev(self, mock_read): """Test user with full access can access dev environment.""" authorized, reason = is_user_authorized("admin@example.com", "dev") assert authorized is True assert "dev" in reason.lower() @patch("src.auth_authorization._read_csv_file", return_value=MOCK_CSV_CONTENT) def test_full_access_user_in_qa(self, mock_read): """Test user with full access can access qa environment.""" authorized, reason = is_user_authorized("admin@example.com", "qa") assert authorized is True assert "qa" in reason.lower() @patch("src.auth_authorization._read_csv_file", return_value=MOCK_CSV_CONTENT) def test_qa_only_user_in_prod(self, mock_read): """Test QA-only user cannot access production.""" authorized, reason = is_user_authorized("qa_only@example.com", "prod") assert authorized is False assert "production access" in reason.lower() @patch("src.auth_authorization._read_csv_file", return_value=MOCK_CSV_CONTENT) def test_qa_only_user_in_dev(self, mock_read): """Test QA-only user can access dev environment.""" authorized, reason = is_user_authorized("qa_only@example.com", "dev") assert authorized is True assert "dev" in reason.lower() @patch("src.auth_authorization._read_csv_file", return_value=MOCK_CSV_CONTENT) def test_qa_only_user_in_qa(self, mock_read): """Test QA-only user can access qa environment.""" authorized, reason = is_user_authorized("qa_only@example.com", "qa") assert authorized is True assert "qa" in reason.lower() @patch("src.auth_authorization._read_csv_file", return_value=MOCK_CSV_CONTENT) def test_prod_only_user_in_prod(self, mock_read): """Test prod-only user can access production.""" authorized, reason = is_user_authorized("prod_only@example.com", "prod") assert authorized is True assert "production" in reason.lower() @patch("src.auth_authorization._read_csv_file", return_value=MOCK_CSV_CONTENT) def test_prod_only_user_in_dev(self, mock_read): """Test prod-only user cannot access dev environment.""" authorized, reason = is_user_authorized("prod_only@example.com", "dev") assert authorized is False assert "non-production access" in reason.lower() @patch("src.auth_authorization._read_csv_file", return_value=MOCK_CSV_CONTENT) def test_no_access_user_in_prod(self, mock_read): """Test user with no access cannot access production.""" authorized, reason = is_user_authorized("no_access@example.com", "prod") assert authorized is False assert "production access" in reason.lower() @patch("src.auth_authorization._read_csv_file", return_value=MOCK_CSV_CONTENT) def test_no_access_user_in_dev(self, mock_read): """Test user with no access cannot access dev environment.""" authorized, reason = is_user_authorized("no_access@example.com", "dev") assert authorized is False assert "non-production access" in reason.lower() @patch("src.auth_authorization._read_csv_file", return_value=MOCK_CSV_CONTENT) def test_unknown_user_in_prod(self, mock_read): """Test unknown user cannot access production.""" authorized, reason = is_user_authorized("unknown@example.com", "prod") assert authorized is False assert "not found" in reason.lower() @patch("src.auth_authorization._read_csv_file", return_value=MOCK_CSV_CONTENT) def test_unknown_user_in_dev(self, mock_read): """Test unknown user cannot access dev environment.""" authorized, reason = is_user_authorized("unknown@example.com", "dev") assert authorized is False assert "not found" in reason.lower() @patch("src.auth_authorization._read_csv_file", return_value=MOCK_CSV_CONTENT) def test_email_case_insensitive(self, mock_read): """Test that email comparison is case-insensitive.""" # Uppercase should work authorized, reason = is_user_authorized("ADMIN@EXAMPLE.COM", "prod") assert authorized is True # Mixed case should work authorized, reason = is_user_authorized("Admin@Example.com", "dev") assert authorized is True @patch("src.auth_authorization._read_csv_file", return_value=MOCK_CSV_CONTENT) def test_environment_case_insensitive(self, mock_read): """Test that environment comparison is case-insensitive.""" # Uppercase environment authorized, reason = is_user_authorized("admin@example.com", "PROD") assert authorized is True # Mixed case environment authorized, reason = is_user_authorized("admin@example.com", "Dev") assert authorized is True @patch("src.auth_authorization._read_csv_file", return_value=MOCK_CSV_CONTENT) def test_whitespace_handling(self, mock_read): """Test that whitespace in inputs is handled properly.""" # Email with whitespace authorized, reason = is_user_authorized(" admin@example.com ", "prod") assert authorized is True # Environment with whitespace authorized, reason = is_user_authorized("admin@example.com", " dev ") assert authorized is True @patch("src.auth_authorization._read_csv_file", return_value=MOCK_CSV_CONTENT) def test_custom_environment_names(self, mock_read): """Test that custom environment names are handled correctly.""" # Any non-prod environment should check QA_ACCESS authorized, reason = is_user_authorized("admin@example.com", "staging") assert authorized is True authorized, reason = is_user_authorized("qa_only@example.com", "staging") assert authorized is True # Prod-only user should not have access to staging authorized, reason = is_user_authorized("prod_only@example.com", "staging") assert authorized is False # Unknown user should still fail authorized, reason = is_user_authorized("unknown@example.com", "staging") assert authorized is False class TestEdgeCases: """Tests for edge cases and error handling.""" @patch("src.auth_authorization._read_csv_file", return_value=MOCK_CSV_CONTENT) def test_empty_email(self, mock_read): """Test handling of empty email.""" authorized, reason = is_user_authorized("", "prod") assert authorized is False assert "not found" in reason.lower() @patch("src.auth_authorization._read_csv_file", return_value=MOCK_CSV_CONTENT) def test_empty_environment(self, mock_read): """Test handling of empty environment (should treat as non-prod).""" authorized, reason = is_user_authorized("admin@example.com", "") # Empty string != "prod", so should check QA_ACCESS assert authorized is True @patch( "src.auth_authorization._read_csv_file", side_effect=FileNotFoundError("File not found"), ) def test_missing_csv_file_in_authorization(self, mock_read): """Test authorization when CSV file is missing.""" authorized, reason = is_user_authorized("admin@example.com", "prod") assert authorized is False assert "authorization system error" in reason.lower() @patch( "src.auth_authorization._read_csv_file", side_effect=ValueError("Invalid CSV"), ) def test_invalid_csv_in_authorization(self, mock_read): """Test authorization when CSV is invalid.""" authorized, reason = is_user_authorized("admin@example.com", "prod") assert authorized is False assert "authorization system error" in reason.lower()