From 635498864058b85ca47b7354613c2e527e6d0395 Mon Sep 17 00:00:00 2001 From: Minh Vu Date: Sun, 4 Oct 2026 15:58:13 +0200 Subject: [PATCH] fix: Keep analytics events with zero context cache capacity --- ldclient/impl/lru_cache.py | 2 ++ .../impl/events/test_async_event_processor.py | 25 +++++++++++++++++++ .../impl/events/test_event_processor.py | 24 ++++++++++++++++++ ldclient/testing/impl/test_lru_cache.py | 13 ++++++++++ 4 files changed, 64 insertions(+) diff --git a/ldclient/impl/lru_cache.py b/ldclient/impl/lru_cache.py index 76ee329a..7e77a07a 100644 --- a/ldclient/impl/lru_cache.py +++ b/ldclient/impl/lru_cache.py @@ -19,6 +19,8 @@ def get(self, key): ''' def put(self, key, value): + if self.capacity <= 0: + return False found = key in self.cache if found: self.cache.move_to_end(key) diff --git a/ldclient/testing/impl/events/test_async_event_processor.py b/ldclient/testing/impl/events/test_async_event_processor.py index f6d67632..36847a91 100644 --- a/ldclient/testing/impl/events/test_async_event_processor.py +++ b/ldclient/testing/impl/events/test_async_event_processor.py @@ -272,6 +272,31 @@ async def test_two_events_for_same_context_only_produce_one_index_event(): assert output[3]['kind'] == 'summary' +@pytest.mark.parametrize( + "event,expected_kinds", + [ + pytest.param(EventInputIdentify(timestamp, context), ['identify', 'identify'], id='identify'), + pytest.param(EventInputCustom(timestamp, context, 'eventkey', {'thing': 'stuff'}, 1.5), ['index', 'custom', 'index', 'custom'], id='custom'), + pytest.param( + EventInputEvaluation(timestamp, context, flag.key, flag, 1, 'value', None, 'default', None, True), + ['index', 'feature', 'index', 'feature', 'summary'], + id='feature', + ), + ], +) +async def test_events_are_delivered_with_zero_context_keys_capacity(event, expected_kinds): + mock_http = MockAioHttp() + async with make_processor(mock_http, context_keys_capacity=0) as ep: + ep.send_event(event) + ep.send_event(event) + + output = await flush_and_get_events(ep, mock_http) + assert [item['kind'] for item in output] == expected_kinds + for item in output: + if item['kind'] != 'summary': + assert item['context'] == context.to_dict() + + async def test_nontracked_events_are_summarized(): mock_http = MockAioHttp() async with make_processor(mock_http) as ep: diff --git a/ldclient/testing/impl/events/test_event_processor.py b/ldclient/testing/impl/events/test_event_processor.py index 2c2f462b..850f9ce7 100644 --- a/ldclient/testing/impl/events/test_event_processor.py +++ b/ldclient/testing/impl/events/test_event_processor.py @@ -384,6 +384,30 @@ def test_two_events_for_same_context_only_produce_one_index_event(): check_summary_event(output[3]) +@pytest.mark.parametrize( + "event,expected_kinds", + [ + pytest.param(EventInputIdentify(timestamp, context), ['identify', 'identify'], id='identify'), + pytest.param(EventInputCustom(timestamp, context, 'eventkey', {'thing': 'stuff'}, 1.5), ['index', 'custom', 'index', 'custom'], id='custom'), + pytest.param( + EventInputEvaluation(timestamp, context, flag.key, flag, 1, 'value', None, 'default', None, True), + ['index', 'feature', 'index', 'feature', 'summary'], + id='feature', + ), + ], +) +def test_events_are_delivered_with_zero_context_keys_capacity(event, expected_kinds): + with DefaultTestProcessor(context_keys_capacity=0) as ep: + ep.send_event(event) + ep.send_event(event) + + output = flush_and_get_events(ep) + assert [item['kind'] for item in output] == expected_kinds + for item in output: + if item['kind'] != 'summary': + assert item['context'] == context.to_dict() + + def test_new_index_event_is_added_if_context_cache_has_been_cleared(): with DefaultTestProcessor(context_keys_flush_interval=0.1) as ep: e0 = EventInputEvaluation(timestamp, context, flag.key, flag, 1, 'value1', None, 'default', None, True) diff --git a/ldclient/testing/impl/test_lru_cache.py b/ldclient/testing/impl/test_lru_cache.py index f423b9af..ad730c5c 100644 --- a/ldclient/testing/impl/test_lru_cache.py +++ b/ldclient/testing/impl/test_lru_cache.py @@ -1,6 +1,19 @@ +import pytest + from ldclient.impl.lru_cache import SimpleLRUCache +@pytest.mark.parametrize("capacity", [0, -1]) +def test_non_positive_capacity_does_not_retain_values(capacity): + lru = SimpleLRUCache(capacity) + assert lru.put("a", True) is False + assert lru.get("a") is None + assert lru.put("a", True) is False + assert lru.put("b", True) is False + assert lru.get("a") is None + assert lru.get("b") is None + + def test_retains_values_up_to_capacity(): lru = SimpleLRUCache(3) assert lru.put("a", True) is False