diff --git a/splitio/client/input_validator.py b/splitio/client/input_validator.py index b9201346..0b502244 100644 --- a/splitio/client/input_validator.py +++ b/splitio/client/input_validator.py @@ -564,7 +564,7 @@ def validate_factory_instantiation(sdk_key): return True -def valid_properties(properties): +def valid_properties(properties, source): """ Check if properties is a valid dict and returns the properties that will be sent to the track method, avoiding unexpected types. @@ -580,7 +580,7 @@ def valid_properties(properties): return True, None, size if not isinstance(properties, dict): - _LOGGER.error('track: properties must be of type dictionary.') + _LOGGER.error('%s: properties must be of type dictionary.', source) return False, None, 0 valid_properties = dict() @@ -597,7 +597,7 @@ def valid_properties(properties): if not isinstance(element, str) and not isinstance(element, Number) \ and not isinstance(element, bool): - _LOGGER.warning('Property %s is of invalid type. Setting value to None', element) + _LOGGER.warning('%s: Property %s is of invalid type. Setting value to None', source, element) element = None valid_properties[property] = element @@ -607,14 +607,13 @@ def valid_properties(properties): if size > MAX_PROPERTIES_LENGTH_BYTES: _LOGGER.error( - 'The maximum size allowed for the properties is 32768 bytes. ' + - 'Current one is ' + str(size) + ' bytes. Event not queued' - ) + '%s: The maximum size allowed for the properties is 32768 bytes. ' + + 'Current one is ' + str(size) + ' bytes. Event not queued', source) return False, None, size if len(valid_properties.keys()) > 300: - _LOGGER.warning('Event has more than 300 properties. Some of them will be trimmed' + - ' when processed') + _LOGGER.warning('%s: Event has more than 300 properties. Some of them will be trimmed' + + ' when processed', source) return True, valid_properties if len(valid_properties) else None, size def validate_pluggable_adapter(config): diff --git a/splitio/engine/impressions/strategies.py b/splitio/engine/impressions/strategies.py index 42b66011..c2b0c565 100644 --- a/splitio/engine/impressions/strategies.py +++ b/splitio/engine/impressions/strategies.py @@ -38,7 +38,14 @@ def process_impressions(self, impressions): :returns: Tuple of to be stored, observed and counted impressions, and unique keys tuple :rtype: list[tuple[splitio.models.impression.Impression, dict]], list[], list[], list[] """ - imps = [(self._observer.test_and_set(imp), attrs) for imp, attrs in impressions] + imps = [] + for imp, attrs in impressions: + if imp.properties is not None: + imps.append((imp, attrs)) + continue + + imps.append((self._observer.test_and_set(imp), attrs)) + return [i for i, _ in imps], imps, [], [] class StrategyNoneMode(BaseStrategy): @@ -85,7 +92,14 @@ def process_impressions(self, impressions): :returns: Tuple of to be stored, observed and counted impressions, and unique keys tuple :rtype: list[tuple[splitio.models.impression.Impression, dict]], list[splitio.models.impression.Impression], list[splitio.models.impression.Impression], list[] """ - imps = [(self._observer.test_and_set(imp), attrs) for imp, attrs in impressions] + imps = [] + for imp, attrs in impressions: + if imp.properties is not None: + imps.append((imp, attrs)) + continue + + imps.append((self._observer.test_and_set(imp), attrs)) + counter_imps = [imp for imp, _ in imps if imp.previous_time != None] this_hour = truncate_time(utctime_ms()) return [i for i, _ in imps if i.previous_time is None or i.previous_time < this_hour], imps, counter_imps, [] diff --git a/splitio/models/impressions.py b/splitio/models/impressions.py index 9224d15b..0c6d50f7 100644 --- a/splitio/models/impressions.py +++ b/splitio/models/impressions.py @@ -12,7 +12,8 @@ 'change_number', 'bucketing_key', 'time', - 'previous_time' + 'previous_time', + 'properties' ] ) diff --git a/tests/api/test_impressions_api.py b/tests/api/test_impressions_api.py index 7c8c1510..63193021 100644 --- a/tests/api/test_impressions_api.py +++ b/tests/api/test_impressions_api.py @@ -14,9 +14,9 @@ from splitio.storage.inmemmory import InMemoryTelemetryStorage, InMemoryTelemetryStorageAsync impressions_mock = [ - Impression('k1', 'f1', 'on', 'l1', 123456, 'b1', 321654), - Impression('k2', 'f2', 'off', 'l1', 123456, 'b1', 321654), - Impression('k3', 'f1', 'on', 'l1', 123456, 'b1', 321654) + Impression('k1', 'f1', 'on', 'l1', 123456, 'b1', 321654, {}), + Impression('k2', 'f2', 'off', 'l1', 123456, 'b1', 321654, {}), + Impression('k3', 'f1', 'on', 'l1', 123456, 'b1', 321654, {}) ] expectedImpressions = [{ 'f': 'f1', diff --git a/tests/client/test_input_validator.py b/tests/client/test_input_validator.py index 0659ee43..1ba6b610 100644 --- a/tests/client/test_input_validator.py +++ b/tests/client/test_input_validator.py @@ -499,17 +499,17 @@ def _configs(treatment): def test_valid_properties(self, mocker): """Test valid_properties() method.""" - assert input_validator.valid_properties(None) == (True, None, 1024) - assert input_validator.valid_properties([]) == (False, None, 0) - assert input_validator.valid_properties(True) == (False, None, 0) - assert input_validator.valid_properties(dict()) == (True, None, 1024) - assert input_validator.valid_properties({2: 123}) == (True, None, 1024) + assert input_validator.valid_properties(None, '') == (True, None, 1024) + assert input_validator.valid_properties([], '') == (False, None, 0) + assert input_validator.valid_properties(True, '') == (False, None, 0) + assert input_validator.valid_properties(dict(), '') == (True, None, 1024) + assert input_validator.valid_properties({2: 123}, '') == (True, None, 1024) class Test: pass assert input_validator.valid_properties({ "test": Test() - }) == (True, {"test": None}, 1028) + }, '') == (True, {"test": None}, 1028) props1 = { "test1": "test", @@ -519,7 +519,7 @@ class Test: "test5": [], 2: "t", } - r1, r2, r3 = input_validator.valid_properties(props1) + r1, r2, r3 = input_validator.valid_properties(props1, '') assert r1 is True assert len(r2.keys()) == 5 assert r2["test1"] == "test" @@ -532,12 +532,12 @@ class Test: props2 = dict() for i in range(301): props2[str(i)] = i - assert input_validator.valid_properties(props2) == (True, props2, 1817) + assert input_validator.valid_properties(props2, '') == (True, props2, 1817) props3 = dict() for i in range(100, 210): props3["prop" + str(i)] = "a" * 300 - r1, r2, r3 = input_validator.valid_properties(props3) + r1, r2, r3 = input_validator.valid_properties(props3, '') assert r1 is False assert r3 == 32952 diff --git a/tests/engine/test_impressions.py b/tests/engine/test_impressions.py index b9f6a607..715bfe1b 100644 --- a/tests/engine/test_impressions.py +++ b/tests/engine/test_impressions.py @@ -23,16 +23,16 @@ def test_changes_are_reflected(self): """Test that change in any field changes the resulting hash.""" total = set() hasher = Hasher() - total.add(hasher.process(Impression('key1', 'feature1', 'on', 'killed', 123, None, 456))) - total.add(hasher.process(Impression('key2', 'feature1', 'on', 'killed', 123, None, 456))) - total.add(hasher.process(Impression('key1', 'feature2', 'on', 'killed', 123, None, 456))) - total.add(hasher.process(Impression('key1', 'feature1', 'off', 'killed', 123, None, 456))) - total.add(hasher.process(Impression('key1', 'feature1', 'on', 'not killed', 123, None, 456))) - total.add(hasher.process(Impression('key1', 'feature1', 'on', 'killed', 321, None, 456))) + total.add(hasher.process(Impression('key1', 'feature1', 'on', 'killed', 123, None, 456, None, {}))) + total.add(hasher.process(Impression('key2', 'feature1', 'on', 'killed', 123, None, 456, None, {}))) + total.add(hasher.process(Impression('key1', 'feature2', 'on', 'killed', 123, None, 456, None, {}))) + total.add(hasher.process(Impression('key1', 'feature1', 'off', 'killed', 123, None, 456, None, {}))) + total.add(hasher.process(Impression('key1', 'feature1', 'on', 'not killed', 123, None, 456, None, {}))) + total.add(hasher.process(Impression('key1', 'feature1', 'on', 'killed', 321, None, 456, None, {}))) assert len(total) == 6 # Re-adding the first-one should not increase the number of different hashes - total.add(hasher.process(Impression('key1', 'feature1', 'on', 'killed', 123, None, 456))) + total.add(hasher.process(Impression('key1', 'feature1', 'on', 'killed', 123, None, 456, None, {}))) assert len(total) == 6 @@ -42,26 +42,26 @@ class ImpressionObserverTests(object): def test_previous_time_properly_calculated(self): """Test that the previous time is properly set.""" observer = Observer(5) - assert (observer.test_and_set(Impression('key1', 'f1', 'on', 'killed', 123, None, 456)) - == Impression('key1', 'f1', 'on', 'killed', 123, None, 456)) - assert (observer.test_and_set(Impression('key1', 'f1', 'on', 'killed', 123, None, 457)) - == Impression('key1', 'f1', 'on', 'killed', 123, None, 457, 456)) + assert (observer.test_and_set(Impression('key1', 'f1', 'on', 'killed', 123, None, 456, None, None)) + == Impression('key1', 'f1', 'on', 'killed', 123, None, 456, None, None)) + assert (observer.test_and_set(Impression('key1', 'f1', 'on', 'killed', 123, None, 457, None, None)) + == Impression('key1', 'f1', 'on', 'killed', 123, None, 457, 456, None)) # Add 5 new impressions to evict the first one and check that previous time is None again - assert (observer.test_and_set(Impression('key2', 'f1', 'on', 'killed', 123, None, 456)) - == Impression('key2', 'f1', 'on', 'killed', 123, None, 456)) - assert (observer.test_and_set(Impression('key3', 'f1', 'on', 'killed', 123, None, 456)) - == Impression('key3', 'f1', 'on', 'killed', 123, None, 456)) - assert (observer.test_and_set(Impression('key4', 'f1', 'on', 'killed', 123, None, 456)) - == Impression('key4', 'f1', 'on', 'killed', 123, None, 456)) - assert (observer.test_and_set(Impression('key5', 'f1', 'on', 'killed', 123, None, 456)) - == Impression('key5', 'f1', 'on', 'killed', 123, None, 456)) - assert (observer.test_and_set(Impression('key6', 'f1', 'on', 'killed', 123, None, 456)) - == Impression('key6', 'f1', 'on', 'killed', 123, None, 456)) + assert (observer.test_and_set(Impression('key2', 'f1', 'on', 'killed', 123, None, 456, None, None)) + == Impression('key2', 'f1', 'on', 'killed', 123, None, 456, None, None)) + assert (observer.test_and_set(Impression('key3', 'f1', 'on', 'killed', 123, None, 456, None, None)) + == Impression('key3', 'f1', 'on', 'killed', 123, None, 456, None, None)) + assert (observer.test_and_set(Impression('key4', 'f1', 'on', 'killed', 123, None, 456, None, None)) + == Impression('key4', 'f1', 'on', 'killed', 123, None, 456, None, None)) + assert (observer.test_and_set(Impression('key5', 'f1', 'on', 'killed', 123, None, 456, None, None)) + == Impression('key5', 'f1', 'on', 'killed', 123, None, 456, None, None)) + assert (observer.test_and_set(Impression('key6', 'f1', 'on', 'killed', 123, None, 456, None, None)) + == Impression('key6', 'f1', 'on', 'killed', 123, None, 456, None, None)) # Re-process the first-one - assert (observer.test_and_set(Impression('key1', 'f1', 'on', 'killed', 123, None, 456)) - == Impression('key1', 'f1', 'on', 'killed', 123, None, 456)) + assert (observer.test_and_set(Impression('key1', 'f1', 'on', 'killed', 123, None, 456, None, None)) + == Impression('key1', 'f1', 'on', 'killed', 123, None, 456, None, None)) class ImpressionCounterTests(object): @@ -72,15 +72,15 @@ def test_tracking_and_popping(self): counter = Counter() utc_now = utctime_ms_reimplement() utc_1_hour_after = utc_now + (3600 * 1000) - counter.track([Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now), - Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now), - Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now)]) + counter.track([Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now, None, None), + Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now, None, None), + Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now, None, None)]) - counter.track([Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now), - Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now)]) + counter.track([Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now, None, None), + Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now, None, None)]) - counter.track([Impression('k1', 'f1', 'on', 'l1', 123, None, utc_1_hour_after), - Impression('k1', 'f2', 'on', 'l1', 123, None, utc_1_hour_after)]) + counter.track([Impression('k1', 'f1', 'on', 'l1', 123, None, utc_1_hour_after, None, None), + Impression('k1', 'f2', 'on', 'l1', 123, None, utc_1_hour_after, None, None)]) assert set(counter.pop_all()) == set([ Counter.CountPerFeature('f1', truncate_time(utc_now), 3), @@ -112,18 +112,18 @@ def test_standalone_optimized(self, mocker): # An impression that hasn't happened in the last hour (pt = None) should be tracked imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3), False), None), - (ImpressionDecorated(Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3), False), None) + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3, None, None), False), None), + (ImpressionDecorated(Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3, None, None), False), None) ]) assert for_unique_keys_tracker == [] - assert imps == [Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3), - Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3)] + assert imps == [Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3, None, None), + Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3, None, None)] assert deduped == 0 # Tracking the same impression a ms later should be empty imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-2), False), None) + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-2, None, None), False), None) ]) assert imps == [] assert deduped == 1 @@ -131,9 +131,9 @@ def test_standalone_optimized(self, mocker): # Tracking an impression with a different key makes it to the queue imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (ImpressionDecorated(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1), False), None) + (ImpressionDecorated(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1, None, None), False), None) ]) - assert imps == [Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1)] + assert imps == [Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1, None, None)] assert deduped == 0 # Advance the perceived clock one hour @@ -144,30 +144,30 @@ def test_standalone_optimized(self, mocker): # Track the same impressions but "one hour later" imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1), False), None), - (ImpressionDecorated(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2), False), None) + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1, None, None), False), None), + (ImpressionDecorated(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2, None, None), False), None) ]) - assert imps == [Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1, old_utc-3), - Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2, old_utc-1)] + assert imps == [Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1, old_utc-3, None), + Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2, old_utc-1, None)] assert deduped == 0 assert for_unique_keys_tracker == [] assert len(manager._strategy._observer._cache._data) == 3 # distinct impressions seen - assert for_counter == [Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1, old_utc-3), - Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2, old_utc-1)] + assert for_counter == [Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1, old_utc-3, None), + Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2, old_utc-1, None)] # Test counting only from the second impression imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (ImpressionDecorated(Impression('k3', 'f3', 'on', 'l1', 123, None, utc_now-1), False), None) + (ImpressionDecorated(Impression('k3', 'f3', 'on', 'l1', 123, None, utc_now-1, None, None), False), None) ]) assert for_counter == [] assert deduped == 0 assert for_unique_keys_tracker == [] imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (ImpressionDecorated(Impression('k3', 'f3', 'on', 'l1', 123, None, utc_now-1), False), None) + (ImpressionDecorated(Impression('k3', 'f3', 'on', 'l1', 123, None, utc_now-1, None, None), False), None) ]) - assert for_counter == [Impression('k3', 'f3', 'on', 'l1', 123, None, utc_now-1, utc_now-1)] + assert for_counter == [Impression('k3', 'f3', 'on', 'l1', 123, None, utc_now-1, utc_now-1, None)] assert deduped == 1 assert for_unique_keys_tracker == [] @@ -186,27 +186,27 @@ def test_standalone_debug(self, mocker): # An impression that hasn't happened in the last hour (pt = None) should be tracked imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3), False), None), - (ImpressionDecorated(Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3), False), None) + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3, None, None), False), None), + (ImpressionDecorated(Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3, None, None), False), None) ]) - assert imps == [Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3), - Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3)] + assert imps == [Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3, None, None), + Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3, None, None)] assert for_counter == [] assert for_unique_keys_tracker == [] # Tracking the same impression a ms later should return the impression imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-2), False), None) + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-2, None, None), False), None) ]) - assert imps == [Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-2, utc_now-3)] + assert imps == [Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-2, utc_now-3, None)] assert for_counter == [] assert for_unique_keys_tracker == [] # Tracking a in impression with a different key makes it to the queue imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (ImpressionDecorated(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1), False), None) + (ImpressionDecorated(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1, None, None), False), None) ]) - assert imps == [Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1)] + assert imps == [Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1, None, None)] assert for_counter == [] assert for_unique_keys_tracker == [] @@ -218,11 +218,11 @@ def test_standalone_debug(self, mocker): # Track the same impressions but "one hour later" imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1), False), None), - (ImpressionDecorated(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2), False), None) + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1, None, None), False), None), + (ImpressionDecorated(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2, None, None), False), None) ]) - assert imps == [Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1, old_utc-3), - Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2, old_utc-1)] + assert imps == [Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1, old_utc-3, None), + Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2, old_utc-1, None)] assert for_counter == [] assert for_unique_keys_tracker == [] @@ -242,30 +242,30 @@ def test_standalone_none(self, mocker): # no impressions are tracked, only counter and mtk imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3), False), None), - (ImpressionDecorated(Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3), False), None) + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3, None, None), False), None), + (ImpressionDecorated(Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3, None, None), False), None) ]) assert imps == [] assert for_counter == [ - Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3), - Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3) + Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3, None, None), + Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3, None, None) ] assert for_unique_keys_tracker == [('k1', 'f1'), ('k1', 'f2')] # Tracking the same impression a ms later should not return the impression and no change on mtk cache imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-2), False), None) + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-2, None, None), False), None) ]) assert imps == [] # Tracking an impression with a different key, will only increase mtk imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (ImpressionDecorated(Impression('k3', 'f1', 'on', 'l1', 123, None, utc_now-1), False), None) + (ImpressionDecorated(Impression('k3', 'f1', 'on', 'l1', 123, None, utc_now-1, None, None), False), None) ]) assert imps == [] assert for_unique_keys_tracker == [('k3', 'f1')] assert for_counter == [ - Impression('k3', 'f1', 'on', 'l1', 123, None, utc_now-1) + Impression('k3', 'f1', 'on', 'l1', 123, None, utc_now-1, None, None) ] # Advance the perceived clock one hour @@ -276,13 +276,13 @@ def test_standalone_none(self, mocker): # Track the same impressions but "one hour later", no changes on mtk imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1), False), None), - (ImpressionDecorated(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2), False), None) + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1, None, None), False), None), + (ImpressionDecorated(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2, None, None), False), None) ]) assert imps == [] assert for_counter == [ - Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1), - Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2) + Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1, None, None), + Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2, None, None) ] def test_standalone_optimized_listener(self, mocker): @@ -301,32 +301,32 @@ def test_standalone_optimized_listener(self, mocker): # An impression that hasn't happened in the last hour (pt = None) should be tracked imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3), False), None), - (ImpressionDecorated(Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3), False), None) + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3, None, None), False), None), + (ImpressionDecorated(Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3, None, None), False), None) ]) - assert imps == [Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3), - Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3)] + assert imps == [Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3, None, None), + Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3, None, None)] assert deduped == 0 - assert listen == [(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3), None), - (Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3), None)] + assert listen == [(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3, None, None), None), + (Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3, None, None), None)] assert for_unique_keys_tracker == [] # Tracking the same impression a ms later should return empty imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-2), False), None) + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-2, None, None), False), None) ]) assert imps == [] assert deduped == 1 - assert listen == [(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-2, utc_now-3), None)] + assert listen == [(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-2, utc_now-3, None), None)] assert for_unique_keys_tracker == [] # Tracking a in impression with a different key makes it to the queue imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (ImpressionDecorated(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1), False), None) + (ImpressionDecorated(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1, None, None), False), None) ]) - assert imps == [Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1)] + assert imps == [Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1, None, None)] assert deduped == 0 - assert listen == [(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1), None)] + assert listen == [(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1, None, None), None)] assert for_unique_keys_tracker == [] # Advance the perceived clock one hour @@ -337,36 +337,36 @@ def test_standalone_optimized_listener(self, mocker): # Track the same impressions but "one hour later" imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1), False), None), - (ImpressionDecorated(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2), False), None) + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1, None, None), False), None), + (ImpressionDecorated(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2, None, None), False), None) ]) - assert imps == [Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1, old_utc-3), - Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2, old_utc-1)] + assert imps == [Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1, old_utc-3, None), + Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2, old_utc-1, None)] assert deduped == 0 assert listen == [ - (Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1, old_utc-3), None), - (Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2, old_utc-1), None), + (Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1, old_utc-3, None), None), + (Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2, old_utc-1, None), None), ] assert for_unique_keys_tracker == [] assert len(manager._strategy._observer._cache._data) == 3 # distinct impressions seen assert for_counter == [ - Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1, old_utc-3), - Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2, old_utc-1) + Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1, old_utc-3, None), + Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2, old_utc-1, None) ] # Test counting only from the second impression imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (ImpressionDecorated(Impression('k3', 'f3', 'on', 'l1', 123, None, utc_now-1), False), None) + (ImpressionDecorated(Impression('k3', 'f3', 'on', 'l1', 123, None, utc_now-1, None, None), False), None) ]) assert for_counter == [] assert deduped == 0 assert for_unique_keys_tracker == [] imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (ImpressionDecorated(Impression('k3', 'f3', 'on', 'l1', 123, None, utc_now-1), False), None) + (ImpressionDecorated(Impression('k3', 'f3', 'on', 'l1', 123, None, utc_now-1, None, None), False), None) ]) assert for_counter == [ - Impression('k3', 'f3', 'on', 'l1', 123, None, utc_now-1, utc_now-1) + Impression('k3', 'f3', 'on', 'l1', 123, None, utc_now-1, utc_now-1, None) ] assert deduped == 1 assert for_unique_keys_tracker == [] @@ -387,30 +387,30 @@ def test_standalone_debug_listener(self, mocker): # An impression that hasn't happened in the last hour (pt = None) should be tracked imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3), False), None), - (ImpressionDecorated(Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3), False), None) + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3, None, None), False), None), + (ImpressionDecorated(Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3, None, None), False), None) ]) - assert imps == [Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3), - Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3)] + assert imps == [Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3, None, None), + Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3, None, None)] - assert listen == [(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3), None), - (Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3), None)] + assert listen == [(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3, None, None), None), + (Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3, None, None), None)] # Tracking the same impression a ms later should return the imp imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-2), False), None) + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-2, None, None), False), None) ]) - assert imps == [Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-2, utc_now-3)] - assert listen == [(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-2, utc_now-3), None)] + assert imps == [Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-2, utc_now-3, None)] + assert listen == [(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-2, utc_now-3, None), None)] assert for_counter == [] assert for_unique_keys_tracker == [] # Tracking a in impression with a different key makes it to the queue imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (ImpressionDecorated(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1), False), None) + (ImpressionDecorated(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1, None, None), False), None) ]) - assert imps == [Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1)] - assert listen == [(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1), None)] + assert imps == [Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1, None, None)] + assert listen == [(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1, None, None), None)] assert for_counter == [] assert for_unique_keys_tracker == [] @@ -422,14 +422,14 @@ def test_standalone_debug_listener(self, mocker): # Track the same impressions but "one hour later" imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1), False), None), - (ImpressionDecorated(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2), False), None) + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1, None, None), False), None), + (ImpressionDecorated(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2, None, None), False), None) ]) - assert imps == [Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1, old_utc-3), - Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2, old_utc-1)] + assert imps == [Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1, old_utc-3, None), + Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2, old_utc-1, None)] assert listen == [ - (Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1, old_utc-3), None), - (Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2, old_utc-1), None) + (Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1, old_utc-3, None), None), + (Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2, old_utc-1, None), None) ] assert len(manager._strategy._observer._cache._data) == 3 # distinct impressions seen assert for_counter == [] @@ -449,33 +449,33 @@ def test_standalone_none_listener(self, mocker): # An impression that hasn't happened in the last hour (pt = None) should not be tracked imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3), False), None), - (ImpressionDecorated(Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3), False), None) + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3, None, None), False), None), + (ImpressionDecorated(Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3, None, None), False), None) ]) assert imps == [] - assert listen == [(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3), None), - (Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3), None)] + assert listen == [(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3, None, None), None), + (Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3, None, None), None)] - assert for_counter == [Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3), - Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3)] + assert for_counter == [Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3, None, None), + Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3, None, None)] assert for_unique_keys_tracker == [('k1', 'f1'), ('k1', 'f2')] # Tracking the same impression a ms later should return empty, no updates on mtk imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-2), False), None) + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-2, None, None), False), None) ]) assert imps == [] - assert listen == [(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-2, None), None)] - assert for_counter == [Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-2)] + assert listen == [(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-2, None, None), None)] + assert for_counter == [Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-2, None)] assert for_unique_keys_tracker == [('k1', 'f1')] # Tracking a in impression with a different key update mtk imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (ImpressionDecorated(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1), False), None) + (ImpressionDecorated(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1, None, None), False), None) ]) assert imps == [] - assert listen == [(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1), None)] - assert for_counter == [Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1)] + assert listen == [(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1, None, None), None)] + assert for_counter == [Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1, None, None)] assert for_unique_keys_tracker == [('k2', 'f1')] # Advance the perceived clock one hour @@ -486,15 +486,15 @@ def test_standalone_none_listener(self, mocker): # Track the same impressions but "one hour later" imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1), False), None), - (ImpressionDecorated(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2), False), None) + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1, None, None), False), None), + (ImpressionDecorated(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2, None, None), False), None) ]) assert imps == [] - assert for_counter == [Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1), - Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2)] + assert for_counter == [Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1, None, None), + Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2, None, None)] assert listen == [ - (Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1, None), None), - (Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2, None), None) + (Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1, None, None), None), + (Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2, None, None), None) ] assert for_unique_keys_tracker == [('k1', 'f1'), ('k2', 'f1')] @@ -517,12 +517,12 @@ def test_impression_toggle_optimized(self, mocker): # An impression that hasn't happened in the last hour (pt = None) should be tracked imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3), True), None), - (ImpressionDecorated(Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3), False), None) + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3, None, None), True), None), + (ImpressionDecorated(Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3, None, None), False), None) ]) assert for_unique_keys_tracker == [('k1', 'f1')] - assert imps == [Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3)] + assert imps == [Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3, None, None)] assert deduped == 1 def test_impression_toggle_debug(self, mocker): @@ -542,12 +542,12 @@ def test_impression_toggle_debug(self, mocker): # An impression that hasn't happened in the last hour (pt = None) should be tracked imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3), True), None), - (ImpressionDecorated(Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3), False), None) + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3, None, None), True), None), + (ImpressionDecorated(Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3, None, None), False), None) ]) assert for_unique_keys_tracker == [('k1', 'f1')] - assert imps == [Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3)] + assert imps == [Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3, None, None)] assert deduped == 1 def test_impression_toggle_none(self, mocker): @@ -567,10 +567,103 @@ def test_impression_toggle_none(self, mocker): # An impression that hasn't happened in the last hour (pt = None) should be tracked imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ - (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3), True), None), - (ImpressionDecorated(Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3), False), None) + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3, None, None), True), None), + (ImpressionDecorated(Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3, None, None), False), None) ]) assert for_unique_keys_tracker == [('k1', 'f1'), ('k1', 'f2')] assert imps == [] assert deduped == 2 + + def test_impressions_properties_optimized(self, mocker): + """Test impressions manager in optimized mode with impressions properties.""" + + # Mock utc_time function to be able to play with the clock + utc_now = truncate_time(utctime_ms_reimplement()) + 1800 * 1000 + utc_time_mock = mocker.Mock() + utc_time_mock.return_value = utc_now + mocker.patch('splitio.engine.impressions.strategies.utctime_ms', return_value=utc_time_mock()) + telemetry_storage = InMemoryTelemetryStorage() + telemetry_producer = TelemetryStorageProducer(telemetry_storage) + telemetry_runtime_producer = telemetry_producer.get_telemetry_runtime_producer() + + manager = Manager(StrategyOptimizedMode(), StrategyNoneMode(), telemetry_runtime_producer) # no listener + assert manager._strategy._observer is not None + assert isinstance(manager._strategy, StrategyOptimizedMode) + assert isinstance(manager._none_strategy, StrategyNoneMode) + + # An impression that hasn't happened in the last hour (pt = None) should be tracked + imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3, None, None), False), None), + (ImpressionDecorated(Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3, None, None), False), None) + ]) + + assert for_unique_keys_tracker == [] + assert imps == [Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3, None, None), + Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3, None, None)] + assert deduped == 0 + imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ + (ImpressionDecorated(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1, None, None), False), None) + ]) + assert imps == [Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1, None, None)] + + # Advance the perceived clock one hour + old_utc = utc_now # save it to compare captured impressions + utc_now += 3600 * 1000 + utc_time_mock.return_value = utc_now + mocker.patch('splitio.engine.impressions.strategies.utctime_ms', return_value=utc_time_mock()) + + # Track the same impressions but "one hour later" + imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1, None, {'prop': 'value'}), False), None), + (ImpressionDecorated(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2, None, None), False), None) + ]) + assert imps == [Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1, None, {'prop': 'value'}), + Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2, old_utc-1, None)] + assert deduped == 0 + assert for_unique_keys_tracker == [] + + def test_impressions_properties_debug(self, mocker): + """Test impressions manager in optimized mode with impressions properties.""" + + # Mock utc_time function to be able to play with the clock + utc_now = truncate_time(utctime_ms_reimplement()) + 1800 * 1000 + utc_time_mock = mocker.Mock() + utc_time_mock.return_value = utc_now + mocker.patch('splitio.engine.impressions.strategies.utctime_ms', return_value=utc_time_mock()) + telemetry_storage = InMemoryTelemetryStorage() + telemetry_producer = TelemetryStorageProducer(telemetry_storage) + telemetry_runtime_producer = telemetry_producer.get_telemetry_runtime_producer() + + manager = Manager(StrategyDebugMode(), StrategyNoneMode(), telemetry_runtime_producer) # no listener + + # An impression that hasn't happened in the last hour (pt = None) should be tracked + imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3, None, None), False), None), + (ImpressionDecorated(Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3, None, None), False), None) + ]) + + assert for_unique_keys_tracker == [] + assert imps == [Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-3, None, None), + Impression('k1', 'f2', 'on', 'l1', 123, None, utc_now-3, None, None)] + assert deduped == 0 + imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ + (ImpressionDecorated(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1, None, None), False), None) + ]) + assert imps == [Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-1, None, None)] + + # Advance the perceived clock one hour + old_utc = utc_now # save it to compare captured impressions + utc_now += 3600 * 1000 + utc_time_mock.return_value = utc_now + mocker.patch('splitio.engine.impressions.strategies.utctime_ms', return_value=utc_time_mock()) + + # Track the same impressions but "one hour later" + imps, deduped, listen, for_counter, for_unique_keys_tracker = manager.process_impressions([ + (ImpressionDecorated(Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1, None, {'prop': 'value'}), False), None), + (ImpressionDecorated(Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2, None, None), False), None) + ]) + assert imps == [Impression('k1', 'f1', 'on', 'l1', 123, None, utc_now-1, None, {'prop': 'value'}), + Impression('k2', 'f1', 'on', 'l1', 123, None, utc_now-2, old_utc-1, None)] + assert deduped == 0 + assert for_unique_keys_tracker == [] \ No newline at end of file