| """
|
| Unit Tests for Logging Module.
|
|
|
| Tests logging configuration and logger classes.
|
| """
|
|
|
| import pytest
|
| import logging
|
| from unittest.mock import MagicMock, patch
|
|
|
| from app.utils.logger import (
|
| LOG_FORMAT,
|
| LOG_DATE_FORMAT,
|
| setup_logging,
|
| get_logger,
|
| RequestLogger,
|
| DetectionLogger,
|
| AuditLogger,
|
| request_logger,
|
| detection_logger,
|
| audit_logger,
|
| )
|
|
|
|
|
| class TestLoggingConstants:
|
| """Tests for logging constants."""
|
|
|
| def test_log_format_defined(self):
|
| """Test LOG_FORMAT is defined."""
|
| assert LOG_FORMAT is not None
|
| assert isinstance(LOG_FORMAT, str)
|
| assert len(LOG_FORMAT) > 0
|
|
|
| def test_log_format_contains_placeholders(self):
|
| """Test LOG_FORMAT contains standard placeholders."""
|
| assert "%(asctime)s" in LOG_FORMAT or "asctime" in LOG_FORMAT.lower()
|
| assert "%(levelname)" in LOG_FORMAT or "levelname" in LOG_FORMAT.lower()
|
|
|
| def test_date_format_defined(self):
|
| """Test LOG_DATE_FORMAT is defined."""
|
| assert LOG_DATE_FORMAT is not None
|
| assert isinstance(LOG_DATE_FORMAT, str)
|
|
|
|
|
| class TestSetupLogging:
|
| """Tests for setup_logging function."""
|
|
|
| def test_sets_default_level(self):
|
| """Test default log level is INFO."""
|
| setup_logging()
|
| logger = logging.getLogger()
|
|
|
|
|
| assert logger.level in [logging.INFO, logging.DEBUG, logging.NOTSET, logging.WARNING]
|
|
|
| def test_sets_custom_level(self):
|
| """Test custom log level can be set."""
|
| setup_logging(level="DEBUG")
|
|
|
|
|
| def test_accepts_custom_format(self):
|
| """Test custom format can be provided."""
|
| custom_format = "%(message)s"
|
| setup_logging(log_format=custom_format)
|
|
|
|
|
| def test_handles_invalid_level(self):
|
| """Test handles invalid log level gracefully."""
|
|
|
| setup_logging(level="INVALID")
|
|
|
|
|
|
|
| class TestGetLogger:
|
| """Tests for get_logger function."""
|
|
|
| def test_returns_logger(self):
|
| """Test returns Logger instance."""
|
| logger = get_logger("test")
|
|
|
| assert isinstance(logger, logging.Logger)
|
|
|
| def test_returns_named_logger(self):
|
| """Test returns logger with given name."""
|
| logger = get_logger("my.module.name")
|
|
|
| assert logger.name == "my.module.name"
|
|
|
| def test_same_name_same_logger(self):
|
| """Test same name returns same logger instance."""
|
| logger1 = get_logger("same.name")
|
| logger2 = get_logger("same.name")
|
|
|
| assert logger1 is logger2
|
|
|
|
|
| class TestRequestLogger:
|
| """Tests for RequestLogger class."""
|
|
|
| def test_initialization(self):
|
| """Test RequestLogger initializes correctly."""
|
| logger = RequestLogger()
|
|
|
| assert logger.logger is not None
|
| assert logger.logger.name == "scamshield.request"
|
|
|
| def test_log_request(self):
|
| """Test log_request method."""
|
| logger = RequestLogger()
|
|
|
| with patch.object(logger.logger, 'info') as mock_info:
|
| logger.log_request(
|
| method="POST",
|
| path="/api/v1/honeypot/engage",
|
| client_ip="127.0.0.1",
|
| request_id="req-123"
|
| )
|
|
|
| mock_info.assert_called_once()
|
| call_args = mock_info.call_args[0][0]
|
|
|
| assert "REQUEST" in call_args
|
| assert "POST" in call_args
|
| assert "/api/v1/honeypot/engage" in call_args
|
|
|
| def test_log_request_minimal(self):
|
| """Test log_request with minimal parameters."""
|
| logger = RequestLogger()
|
|
|
| with patch.object(logger.logger, 'info') as mock_info:
|
| logger.log_request(method="GET", path="/health")
|
|
|
| mock_info.assert_called_once()
|
|
|
| def test_log_response(self):
|
| """Test log_response method."""
|
| logger = RequestLogger()
|
|
|
| with patch.object(logger.logger, 'info') as mock_info:
|
| logger.log_response(
|
| status_code=200,
|
| duration_ms=150,
|
| request_id="req-123"
|
| )
|
|
|
| mock_info.assert_called_once()
|
| call_args = mock_info.call_args[0][0]
|
|
|
| assert "RESPONSE" in call_args
|
| assert "200" in call_args
|
| assert "150" in call_args
|
|
|
|
|
| class TestDetectionLogger:
|
| """Tests for DetectionLogger class."""
|
|
|
| def test_initialization(self):
|
| """Test DetectionLogger initializes correctly."""
|
| logger = DetectionLogger()
|
|
|
| assert logger.logger is not None
|
| assert logger.logger.name == "scamshield.detection"
|
|
|
| def test_log_detection_scam(self):
|
| """Test log_detection for scam detection."""
|
| logger = DetectionLogger()
|
|
|
| with patch.object(logger.logger, 'info') as mock_info:
|
| logger.log_detection(
|
| session_id="sess-123",
|
| scam_detected=True,
|
| confidence=0.95,
|
| language="en",
|
| indicators=["lottery", "urgent"]
|
| )
|
|
|
| mock_info.assert_called_once()
|
| call_args = mock_info.call_args[0][0]
|
|
|
| assert "DETECTION" in call_args
|
| assert "sess-123" in call_args
|
| assert "SCAM" in call_args
|
| assert "0.95" in call_args
|
|
|
| def test_log_detection_legitimate(self):
|
| """Test log_detection for legitimate message."""
|
| logger = DetectionLogger()
|
|
|
| with patch.object(logger.logger, 'info') as mock_info:
|
| logger.log_detection(
|
| session_id="sess-456",
|
| scam_detected=False,
|
| confidence=0.1,
|
| language="en"
|
| )
|
|
|
| mock_info.assert_called_once()
|
| call_args = mock_info.call_args[0][0]
|
|
|
| assert "LEGITIMATE" in call_args
|
|
|
| def test_log_extraction(self):
|
| """Test log_extraction method."""
|
| logger = DetectionLogger()
|
|
|
| with patch.object(logger.logger, 'info') as mock_info:
|
| logger.log_extraction(
|
| session_id="sess-789",
|
| intel_summary={"upi_ids": 2, "phone_numbers": 1},
|
| confidence=0.85
|
| )
|
|
|
| mock_info.assert_called_once()
|
| call_args = mock_info.call_args[0][0]
|
|
|
| assert "EXTRACTION" in call_args
|
| assert "sess-789" in call_args
|
|
|
|
|
| class TestAuditLogger:
|
| """Tests for AuditLogger class."""
|
|
|
| def test_initialization(self):
|
| """Test AuditLogger initializes correctly."""
|
| logger = AuditLogger()
|
|
|
| assert logger.logger is not None
|
| assert logger.logger.name == "scamshield.audit"
|
|
|
| def test_log_event(self):
|
| """Test log_event method."""
|
| logger = AuditLogger()
|
|
|
| with patch.object(logger.logger, 'info') as mock_info:
|
| logger.log_event(
|
| event_type="session_created",
|
| details={"message_count": 5},
|
| session_id="sess-audit-123"
|
| )
|
|
|
| mock_info.assert_called_once()
|
| call_args = mock_info.call_args[0][0]
|
|
|
| assert "AUDIT" in call_args
|
| assert "session_created" in call_args
|
| assert "sess-audit-123" in call_args
|
|
|
| def test_log_event_without_session(self):
|
| """Test log_event without session_id."""
|
| logger = AuditLogger()
|
|
|
| with patch.object(logger.logger, 'info') as mock_info:
|
| logger.log_event(
|
| event_type="system_startup",
|
| details={"version": "1.0.0"}
|
| )
|
|
|
| mock_info.assert_called_once()
|
|
|
|
|
| class TestModuleLevelLoggers:
|
| """Tests for module-level logger instances."""
|
|
|
| def test_request_logger_exists(self):
|
| """Test request_logger is defined."""
|
| assert request_logger is not None
|
| assert isinstance(request_logger, RequestLogger)
|
|
|
| def test_detection_logger_exists(self):
|
| """Test detection_logger is defined."""
|
| assert detection_logger is not None
|
| assert isinstance(detection_logger, DetectionLogger)
|
|
|
| def test_audit_logger_exists(self):
|
| """Test audit_logger is defined."""
|
| assert audit_logger is not None
|
| assert isinstance(audit_logger, AuditLogger)
|
|
|
|
|
| class TestLoggerIntegration:
|
| """Integration tests for logger module."""
|
|
|
| def test_full_request_lifecycle(self):
|
| """Test logging a full request lifecycle."""
|
| with patch.object(request_logger.logger, 'info'):
|
| request_logger.log_request(
|
| method="POST",
|
| path="/api/v1/honeypot/engage",
|
| client_ip="192.168.1.1",
|
| request_id="req-full-test"
|
| )
|
|
|
| request_logger.log_response(
|
| status_code=200,
|
| duration_ms=250,
|
| request_id="req-full-test"
|
| )
|
|
|
| def test_detection_and_extraction_flow(self):
|
| """Test detection and extraction logging flow."""
|
| with patch.object(detection_logger.logger, 'info'):
|
| detection_logger.log_detection(
|
| session_id="sess-flow-test",
|
| scam_detected=True,
|
| confidence=0.9,
|
| language="en",
|
| indicators=["prize", "otp"]
|
| )
|
|
|
| detection_logger.log_extraction(
|
| session_id="sess-flow-test",
|
| intel_summary={"upi_ids": 1, "phone_numbers": 2},
|
| confidence=0.85
|
| )
|
|
|