"""Tests for app.utils.log_sanitizer.""" from __future__ import annotations import pytest from app.utils.log_sanitizer import sanitize_for_logging class TestSanitizeForLogging: """Tests for sanitize_for_logging().""" def test_passthrough_clean_text(self) -> None: """No sensitive patterns: text unchanged.""" text = "Server started on port 8000" assert sanitize_for_logging(text) == text def test_password_eq(self) -> None: """password=X replaced.""" assert sanitize_for_logging("password=Secret123") == "password=***" def test_password_colon(self) -> None: """password:X replaced.""" assert sanitize_for_logging("password:Secret123") == "password=***" def test_password_case_insensitive(self) -> None: """Password matching is case-insensitive.""" assert sanitize_for_logging("PASSWORD=Secret123") == "password=***" assert sanitize_for_logging("Password:Secret123") == "password=***" def test_api_key_underscore(self) -> None: """api_key=X replaced.""" assert sanitize_for_logging("api_key=my-secret-key") == "api_key=***" def test_api_key_dash(self) -> None: """api-key=X replaced.""" assert sanitize_for_logging("api-key=my-secret-key") == "api_key=***" def test_api_key_no_separator(self) -> None: """api_keyXYZ (no separator) is NOT matched by the api_key pattern. The pattern requires =, :, _, or - after 'key', so plain 'api_keyXYZ' passes through unsanitized. This is intentional — the risk of false-positives (normal words like 'api_keyboard') outweighs coverage. """ result = sanitize_for_logging("api_keyXYZ") # No separator → not matched → unchanged assert result == "api_keyXYZ" def test_token_eq(self) -> None: """token=X replaced.""" assert sanitize_for_logging("token=eyJhbGciOiJIUzI1NiJ9") == "token=***" def test_token_case_insensitive(self) -> None: """Token matching is case-insensitive.""" assert sanitize_for_logging("TOKEN=eyJhbGciOiJIUzI1NiJ9") == "token=***" def test_authorization_bearer(self) -> None: """Authorization: Bearer replaced.""" result = sanitize_for_logging("Authorization: Bearer eyJhbGciOiJIUzI1NiJ9") assert "***" in result assert "Bearer" not in result def test_authorization_bearer_only(self) -> None: """Authorization: Bearer (no token) replaced.""" result = sanitize_for_logging("Authorization: Bearer") assert "***" in result def test_authorization_basic(self) -> None: """Authorization: Basic replaced.""" result = sanitize_for_logging("Authorization: Basic dXNlcjpwYXNz") assert "***" in result def test_secret_eq(self) -> None: """secret=X replaced.""" assert sanitize_for_logging("secret=my_secret_value") == "secret=***" def test_secret_key_eq(self) -> None: """secret_key=X replaced.""" assert sanitize_for_logging("secret_key=my_secret_value") == "secret_key=***" def test_rsa_private_key(self) -> None: """RSA private key header redacted.""" text = "Some text -----BEGIN RSA PRIVATE KEY-----\nMIIBogAAAAAAA=" result = sanitize_for_logging(text) assert "*** PRIVATE KEY ***" in result assert "BEGIN RSA PRIVATE KEY" not in result def test_dsa_private_key(self) -> None: """DSA private key header redacted.""" text = "Some text -----BEGIN DSA PRIVATE KEY-----\nMIIBogAAAAAAA=" result = sanitize_for_logging(text) assert "*** PRIVATE KEY ***" in result assert "BEGIN DSA PRIVATE KEY" not in result def test_ec_private_key(self) -> None: """EC private key header redacted.""" text = "Some text -----BEGIN EC PRIVATE KEY-----\nMIIBogAAAAAAA=" result = sanitize_for_logging(text) assert "*** PRIVATE KEY ***" in result assert "BEGIN EC PRIVATE KEY" not in result def test_openssh_private_key(self) -> None: """OPENSSH private key header redacted.""" text = "Some text -----BEGIN OPENSSH PRIVATE KEY-----\nMIIBogAAAAAAA=" result = sanitize_for_logging(text) assert "*** PRIVATE KEY ***" in result assert "BEGIN OPENSSH PRIVATE KEY" not in result def test_aws_access_key(self) -> None: """AKIA... AWS access key redacted.""" text = "Access key: AKIAIOSFODNN7EXAMPLE" result = sanitize_for_logging(text) assert "AKIA***" in result assert "EXAMPLE" not in result def test_bearer_jwt_token(self) -> None: """Standalone JWT token after 'Bearer' is redacted.""" text = "Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U" result = sanitize_for_logging(text) assert result == "Bearer ***" def test_multiple_sensitive_values(self) -> None: """Multiple sensitive patterns on one line all redacted.""" text = "password=pass1 token=tok1 api_key=key1" result = sanitize_for_logging(text) assert result.count("***") == 3 def test_empty_string(self) -> None: """Empty string returns empty string.""" assert sanitize_for_logging("") == "" def test_only_sensitive_pattern(self) -> None: """Text that is only a sensitive pattern returns only marker.""" assert sanitize_for_logging("password=secret") == "password=***" def test_multiline_text(self) -> None: """Sensitive data redacted across multiple lines.""" text = "Line 1: password=secret\nLine 2: token=tok123\nLine 3: clean" result = sanitize_for_logging(text) assert "***" in result assert "secret" not in result assert "tok123" not in result assert "clean" in result def test_jwt_structure_preserved(self) -> None: """JWT dot-separated structure not matched as token=X.""" text = "header.payload.signature" result = sanitize_for_logging(text) assert result == text