1-
2-
3-
4-
5- #! What do we want to test here?
6-
7- #! The processing of the environment variables, partiuclarly the protobuf, the random one, and also what happens when you try to initialise it without the right environment
8-
9- # this should test _convert_str_to_handlertype with throttle, protobufstream, and random
10- # throttle should return handlertype.throttle, none , protobufstream should return handlertype.protobufstream, and a valid protobufconf with the same values as expected
11-
12- # See if its possible to test _make_ers_handler_conf
13- # This should just take a default string comma and then return the correct features
14-
15- # Definitely test the creation of the full thing, with the four environments
16-
17-
18- #! Also test the from string in HandlerTypes
19- # Should get this testesd for the enums but also the error itself
20-
21-
22- #! We need to test the filters..
23- # Figure out how to test logging filters..
24-
25-
26- # Need tests for the throttling for sure
27-
28- # Need a test for the base handlerfilter:
29- # using one handlertype
30- # using multiple handler types
31-
32-
33- ###########
34-
35-
361"""Comprehensive tests for the logging filters in handlers.py.
372
383Tests cover:
4712import logging
4813import time
4914from threading import Thread
50- from unittest .mock import MagicMock , patch
15+ from unittest .mock import MagicMock
5116
5217import pytest
5318
@@ -79,9 +44,9 @@ def clean_logger():
7944
8045
8146@pytest .fixture
82- def log_record ():
47+ def log_record () -> logging . LogRecord :
8348 """Provide a basic log record for testing."""
84- record = logging .LogRecord (
49+ return logging .LogRecord (
8550 name = "test.module" ,
8651 level = logging .ERROR ,
8752 pathname = "/path/to/test.py" ,
@@ -90,7 +55,6 @@ def log_record():
9055 args = (),
9156 exc_info = None ,
9257 )
93- return record
9458
9559
9660@pytest .fixture
@@ -130,7 +94,9 @@ def mock_ers_handlers():
13094class TestBaseHandlerFilter :
13195 """Tests for BaseHandlerFilter.get_allowed() logic."""
13296
133- def test_non_ers_uses_record_handlers_attribute (self , log_record ):
97+ def test_non_ers_uses_record_handlers_attribute (
98+ self , log_record : logging .LogRecord
99+ ):
134100 """Test get_allowed() uses 'handlers' attribute from record for non-ERS."""
135101 log_record .handlers = [HandlerType .Rich , HandlerType .File ]
136102 filter_obj = BaseHandlerFilter ()
@@ -139,7 +105,9 @@ def test_non_ers_uses_record_handlers_attribute(self, log_record):
139105
140106 assert allowed == [HandlerType .Rich , HandlerType .File ]
141107
142- def test_non_ers_defaults_to_base_handlers (self , log_record ):
108+ def test_non_ers_defaults_to_base_handlers (
109+ self , log_record : logging .LogRecord
110+ ):
143111 """Test get_allowed() falls back to default handlers when attribute missing."""
144112 # log_record has no 'handlers' attribute
145113 filter_obj = BaseHandlerFilter ()
@@ -151,7 +119,9 @@ def test_non_ers_defaults_to_base_handlers(self, log_record):
151119 expected_handlers = {HandlerType .Stream , HandlerType .Rich , HandlerType .File }
152120 assert expected_handlers .issubset (set (allowed ))
153121
154- def test_ers_path_valid_configuration (self , ers_log_record , mock_ers_handlers ):
122+ def test_ers_path_valid_configuration (
123+ self , ers_log_record : logging .LogRecord , mock_ers_handlers : dict
124+ ):
155125 """Test get_allowed() extracts ERS handlers correctly with valid config."""
156126 ers_log_record .ers_handlers = mock_ers_handlers
157127 filter_obj = BaseHandlerFilter ()
@@ -160,7 +130,9 @@ def test_ers_path_valid_configuration(self, ers_log_record, mock_ers_handlers):
160130
161131 assert allowed == [HandlerType .Throttle , HandlerType .Protobufstream ]
162132
163- def test_ers_path_no_matching_level_variable (self , ers_log_record , mock_ers_handlers ):
133+ def test_ers_path_no_matching_level_variable (
134+ self , ers_log_record : logging .LogRecord , mock_ers_handlers : dict
135+ ):
164136 """Test get_allowed() returns None when log level has no ERS mapping."""
165137 # Set a log level that does not have an ERS equivalent
166138 ers_log_record .levelno = 25 # Between INFO and WARNING
@@ -194,7 +166,9 @@ def test_list_handler_ids_converted_to_set(self):
194166 assert isinstance (filter_obj .handler_ids , set )
195167 assert filter_obj .handler_ids == {HandlerType .Rich , HandlerType .File }
196168
197- def test_filter_returns_true_when_handler_in_allowed (self , log_record ):
169+ def test_filter_returns_true_when_handler_in_allowed (
170+ self , log_record : logging .LogRecord
171+ ):
198172 """Test filter() returns True when handler_id is in allowed list."""
199173 log_record .handlers = [HandlerType .Rich , HandlerType .File , HandlerType .Stream ]
200174 filter_obj = HandleIDFilter (HandlerType .Rich )
@@ -203,7 +177,9 @@ def test_filter_returns_true_when_handler_in_allowed(self, log_record):
203177
204178 assert result is True
205179
206- def test_filter_returns_false_when_handler_not_in_allowed (self , log_record ):
180+ def test_filter_returns_false_when_handler_not_in_allowed (
181+ self , log_record : logging .LogRecord
182+ ):
207183 """Test filter() returns False when handler_id not in allowed."""
208184 log_record .handlers = [HandlerType .File , HandlerType .Stream ]
209185 filter_obj = HandleIDFilter (HandlerType .Rich )
@@ -212,7 +188,9 @@ def test_filter_returns_false_when_handler_not_in_allowed(self, log_record):
212188
213189 assert result is False
214190
215- def test_filter_returns_false_when_get_allowed_returns_none (self , log_record ):
191+ def test_filter_returns_false_when_get_allowed_returns_none (
192+ self , log_record : logging .LogRecord
193+ ):
216194 """Test filter() returns False when get_allowed() returns None."""
217195 filter_obj = HandleIDFilter (HandlerType .Rich )
218196 filter_obj .get_allowed = MagicMock (return_value = None )
@@ -221,7 +199,9 @@ def test_filter_returns_false_when_get_allowed_returns_none(self, log_record):
221199
222200 assert result is False
223201
224- def test_filter_with_multiple_handler_ids (self , log_record ):
202+ def test_filter_with_multiple_handler_ids (
203+ self , log_record : logging .LogRecord
204+ ):
225205 """Test filter() with multiple handler_ids checks intersection."""
226206 log_record .handlers = [HandlerType .Rich , HandlerType .File ]
227207 filter_obj = HandleIDFilter ([HandlerType .Rich , HandlerType .Stream ])
@@ -231,7 +211,9 @@ def test_filter_with_multiple_handler_ids(self, log_record):
231211 # Should return True because Rich is in both sets
232212 assert result is True
233213
234- def test_filter_no_intersection_with_multiple_ids (self , log_record ):
214+ def test_filter_no_intersection_with_multiple_ids (
215+ self , log_record : logging .LogRecord
216+ ):
235217 """Test filter() returns False when no intersection with multiple ids."""
236218 log_record .handlers = [HandlerType .File ]
237219 filter_obj = HandleIDFilter ([HandlerType .Rich , HandlerType .Stream ])
@@ -249,7 +231,9 @@ def test_filter_no_intersection_with_multiple_ids(self, log_record):
249231class TestThrottleFilter :
250232 """Tests for ThrottleFilter throttling and suppression logic."""
251233
252- def test_initial_phase_lets_through_first_n_messages (self , log_record ):
234+ def test_initial_phase_lets_through_first_n_messages (
235+ self , log_record : logging .LogRecord
236+ ):
253237 """Test that first N messages pass through without suppression."""
254238 log_record .handlers = [HandlerType .Throttle ]
255239 filter_obj = ThrottleFilter (initial_threshold = 3 , time_limit = 10 )
@@ -259,7 +243,9 @@ def test_initial_phase_lets_through_first_n_messages(self, log_record):
259243 assert filter_obj .filter (log_record ) is True
260244 assert filter_obj .filter (log_record ) is True
261245
262- def test_after_initial_threshold_suppresses (self , log_record ):
246+ def test_after_initial_threshold_suppresses (
247+ self , log_record : logging .LogRecord
248+ ):
263249 """Test that messages are suppressed after initial_threshold."""
264250 log_record .handlers = [HandlerType .Throttle ]
265251 filter_obj = ThrottleFilter (initial_threshold = 2 , time_limit = 10 )
@@ -271,7 +257,9 @@ def test_after_initial_threshold_suppresses(self, log_record):
271257 # 3rd should be suppressed
272258 assert filter_obj .filter (log_record ) is False
273259
274- def test_escalating_threshold_doubles_on_report (self , log_record ):
260+ def test_escalating_threshold_doubles_on_report (
261+ self , log_record : logging .LogRecord
262+ ):
275263 """Test that threshold escalates (10->100->1000) when reporting."""
276264 log_record .handlers = [HandlerType .Throttle ]
277265 filter_obj = ThrottleFilter (initial_threshold = 1 , time_limit = 100 )
@@ -288,7 +276,9 @@ def test_escalating_threshold_doubles_on_report(self, log_record):
288276
289277 assert issue_record .threshold == 100 # Escalated from 10
290278
291- def test_time_window_reset_resets_counters (self , log_record , monkeypatch ):
279+ def test_time_window_reset_resets_counters (
280+ self , log_record : logging .LogRecord , monkeypatch : pytest .MonkeyPatch
281+ ):
292282 """Test that state resets after time_limit expires."""
293283 log_record .handlers = [HandlerType .Throttle ]
294284 filter_obj = ThrottleFilter (initial_threshold = 1 , time_limit = 1 )
@@ -302,7 +292,9 @@ def test_time_window_reset_resets_counters(self, log_record, monkeypatch):
302292 # Time advances beyond time_limit with no suppression, reset should allow pass
303293 assert filter_obj .filter (log_record ) is True
304294
305- def test_suppressed_counter_increments (self , log_record ):
295+ def test_suppressed_counter_increments (
296+ self , log_record : logging .LogRecord
297+ ):
306298 """Test that suppressed_counter increments for each suppressed message."""
307299 log_record .handlers = [HandlerType .Throttle ]
308300 filter_obj = ThrottleFilter (initial_threshold = 0 , time_limit = 100 )
@@ -317,7 +309,9 @@ def test_suppressed_counter_increments(self, log_record):
317309 if i > 0 :
318310 assert issue_record .suppressed_counter >= 0
319311
320- def test_throttle_suppression_flag_bypasses_filter (self , log_record ):
312+ def test_throttle_suppression_flag_bypasses_filter (
313+ self , log_record : logging .LogRecord
314+ ):
321315 """Test that _throttle_suppression flag allows suppression messages through."""
322316 log_record .handlers = [HandlerType .Throttle ]
323317 filter_obj = ThrottleFilter (initial_threshold = 0 , time_limit = 100 )
@@ -329,7 +323,9 @@ def test_throttle_suppression_flag_bypasses_filter(self, log_record):
329323 log_record ._throttle_suppression = True
330324 assert filter_obj .filter (log_record ) is True
331325
332- def test_get_allowed_returns_none_skips_throttle (self , log_record ):
326+ def test_get_allowed_returns_none_skips_throttle (
327+ self , log_record : logging .LogRecord
328+ ):
333329 """Test filter() returns True if get_allowed() returns None."""
334330 filter_obj = ThrottleFilter ()
335331 filter_obj .get_allowed = MagicMock (return_value = None )
@@ -338,7 +334,9 @@ def test_get_allowed_returns_none_skips_throttle(self, log_record):
338334 result = filter_obj .filter (log_record )
339335 assert result is False
340336
341- def test_throttle_not_in_allowed_returns_true (self , log_record ):
337+ def test_throttle_not_in_allowed_returns_true (
338+ self , log_record : logging .LogRecord
339+ ):
342340 """Test filter() returns True if Throttle not in allowed handlers."""
343341 log_record .handlers = [HandlerType .Rich , HandlerType .File ]
344342 filter_obj = ThrottleFilter (initial_threshold = 0 , time_limit = 10 )
@@ -358,7 +356,9 @@ def test_timestamp_formatting(self):
358356 assert formatted .count ("-" ) == 2 # Two dashes for date
359357 assert formatted .count (":" ) == 2 # Two colons for time
360358
361- def test_different_issues_tracked_separately (self , log_record ):
359+ def test_different_issues_tracked_separately (
360+ self , log_record : logging .LogRecord
361+ ):
362362 """Test that different file:line combinations track state separately."""
363363 filter_obj = ThrottleFilter (initial_threshold = 2 , time_limit = 10 )
364364
@@ -390,13 +390,15 @@ def test_different_issues_tracked_separately(self, log_record):
390390 # Issue 2: suppressed (independent)
391391 assert filter_obj .filter (record2 ) is False
392392
393- def test_thread_safety_concurrent_issues (self , log_record ):
393+ def test_thread_safety_concurrent_issues (
394+ self , log_record : logging .LogRecord
395+ ):
394396 """Test ThrottleFilter is thread-safe with concurrent logging."""
395397 filter_obj = ThrottleFilter (initial_threshold = 5 , time_limit = 10 )
396398 log_record .handlers = [HandlerType .Throttle ]
397399 results = []
398400
399- def log_messages (record , num_messages ) :
401+ def log_messages (record : logging . LogRecord , num_messages : int ) -> None :
400402 """Log from a thread."""
401403 for _ in range (num_messages ):
402404 result = filter_obj .filter (record )
@@ -465,7 +467,7 @@ def test_reset_clears_all_state(self):
465467class TestFiltersIntegration :
466468 """Integration tests with real logger setup."""
467469
468- def test_logger_with_handle_id_filter (self , clean_logger ):
470+ def test_logger_with_handle_id_filter (self , clean_logger : logging . Logger ):
469471 """Test logger with HandleIDFilter allows only specific handlers."""
470472 stream = io .StringIO ()
471473 handler = logging .StreamHandler (stream )
@@ -490,7 +492,7 @@ def test_logger_with_handle_id_filter(self, clean_logger):
490492 # Message should appear because Stream is in allowed
491493 assert "Test message" in stream .getvalue ()
492494
493- def test_logger_with_throttle_filter (self , clean_logger ):
495+ def test_logger_with_throttle_filter (self , clean_logger : logging . Logger ):
494496 """Test logger correctly suppresses messages with ThrottleFilter."""
495497 stream = io .StringIO ()
496498 handler = logging .StreamHandler (stream )
@@ -521,7 +523,7 @@ def test_logger_with_throttle_filter(self, clean_logger):
521523 # First 2 should appear, then suppression message
522524 assert output .count ("Repeated message" ) >= 2
523525
524- def test_chained_filters (self , clean_logger ):
526+ def test_chained_filters (self , clean_logger : logging . Logger ):
525527 """Test stacking HandleIDFilter and ThrottleFilter."""
526528 stream = io .StringIO ()
527529 handler = logging .StreamHandler (stream )
@@ -561,7 +563,7 @@ def test_chained_filters(self, clean_logger):
561563class TestEdgeCases :
562564 """Tests for edge cases and boundary conditions."""
563565
564- def test_empty_handlers_list (self , log_record ):
566+ def test_empty_handlers_list (self , log_record : logging . LogRecord ):
565567 """Test filter behavior with empty handlers list."""
566568 log_record .handlers = []
567569 filter_obj = HandleIDFilter (HandlerType .Rich )
@@ -570,7 +572,7 @@ def test_empty_handlers_list(self, log_record):
570572
571573 assert result is False
572574
573- def test_none_handlers_attribute (self , log_record ):
575+ def test_none_handlers_attribute (self , log_record : logging . LogRecord ):
574576 """Test filter when record.handlers is None."""
575577 log_record .handlers = None
576578 filter_obj = HandleIDFilter (HandlerType .Rich )
@@ -579,15 +581,17 @@ def test_none_handlers_attribute(self, log_record):
579581 result = filter_obj .filter (log_record )
580582 assert result is False
581583
582- def test_throttle_with_zero_initial_threshold (self , log_record ):
584+ def test_throttle_with_zero_initial_threshold (
585+ self , log_record : logging .LogRecord
586+ ):
583587 """Test ThrottleFilter with initial_threshold=0."""
584588 log_record .handlers = [HandlerType .Throttle ]
585589 filter_obj = ThrottleFilter (initial_threshold = 0 , time_limit = 10 )
586590
587591 # All messages should be suppressed after first
588592 assert filter_obj .filter (log_record ) is False
589593
590- def test_issue_record_key_format (self , log_record ):
594+ def test_issue_record_key_format (self , log_record : logging . LogRecord ):
591595 """Test that issue_record key is formatted correctly."""
592596 filter_obj = ThrottleFilter ()
593597
@@ -596,7 +600,9 @@ def test_issue_record_key_format(self, log_record):
596600
597601 assert isinstance (record , IssueRecord )
598602
599- def test_multiple_handler_types_intersection (self , log_record ):
603+ def test_multiple_handler_types_intersection (
604+ self , log_record : logging .LogRecord
605+ ):
600606 """Test set intersection with multiple handler types."""
601607 log_record .handlers = [
602608 HandlerType .Rich ,
@@ -609,7 +615,9 @@ def test_multiple_handler_types_intersection(self, log_record):
609615 result = filter_obj .filter (log_record )
610616 assert result is True
611617
612- def test_protobuf_conf_in_ers_handlers (self , ers_log_record , mock_ers_handlers ):
618+ def test_protobuf_conf_in_ers_handlers (
619+ self , ers_log_record : logging .LogRecord , mock_ers_handlers : dict
620+ ):
613621 """Test that ProtobufConf is properly included in ERS configuration."""
614622 ers_log_record .ers_handlers = mock_ers_handlers
615623 filter_obj = BaseHandlerFilter ()
@@ -618,7 +626,9 @@ def test_protobuf_conf_in_ers_handlers(self, ers_log_record, mock_ers_handlers):
618626
619627 assert HandlerType .Protobufstream in allowed
620628
621- def test_suppression_message_includes_count (self , log_record , clean_logger ):
629+ def test_suppression_message_includes_count (
630+ self , clean_logger : logging .Logger
631+ ):
622632 """Test that suppression message includes suppressed count."""
623633 stream = io .StringIO ()
624634 handler = logging .StreamHandler (stream )
0 commit comments