From 9b7f15c1152e6916ba9eb7efe73bc98879340b1f Mon Sep 17 00:00:00 2001 From: James Kebinger Date: Mon, 14 Apr 2025 09:50:13 -0500 Subject: [PATCH] Add special case context handling for prefab.current-time --- prefab_cloud_python/config_resolver.py | 7 +- tests/test_criteria_evaluator.py | 96 ++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 1 deletion(-) diff --git a/prefab_cloud_python/config_resolver.py b/prefab_cloud_python/config_resolver.py index 4253c97..b4d3059 100644 --- a/prefab_cloud_python/config_resolver.py +++ b/prefab_cloud_python/config_resolver.py @@ -1,5 +1,6 @@ from __future__ import annotations import functools +import time from collections.abc import Sequence from .read_write_lock import ReadWriteLock @@ -129,7 +130,11 @@ def all_criteria_match(self, conditional_value, props): return True def evaluate_criterion(self, criterion, properties): - value_from_properties = properties.get(criterion.property_name) + if criterion.property_name == "prefab.current-time": + value_from_properties = int(time.time() * 1000) + else: + value_from_properties = properties.get(criterion.property_name) + deepest_value = ConfigValueUnwrapper.deepest_value( criterion.value_to_match, self.config, properties ) diff --git a/tests/test_criteria_evaluator.py b/tests/test_criteria_evaluator.py index 95cffa3..7774278 100644 --- a/tests/test_criteria_evaluator.py +++ b/tests/test_criteria_evaluator.py @@ -1,6 +1,8 @@ from prefab_cloud_python.config_resolver import CriteriaEvaluator from prefab_cloud_python.context import Context import prefab_pb2 as Prefab +from datetime import datetime, timezone, timedelta +from unittest.mock import patch project_env_id = 1 test_env_id = 2 @@ -1019,6 +1021,100 @@ def test_stringifying_property_values_and_names(self): == desired_value ) + def test_prefab_current_time(self): + # Set up a fixed time for testing + test_time = datetime(2024, 1, 1, 12, 0, 0, tzinfo=timezone.utc) + int(test_time.timestamp() * 1000) + + # Create a config that checks if current time is before a future time + future_time = test_time + timedelta(hours=1) + future_time_millis = int(future_time.timestamp() * 1000) + + config = Prefab.Config( + key=key, + rows=[ + default_row, + Prefab.ConfigRow( + project_env_id=project_env_id, + values=[ + Prefab.ConditionalValue( + criteria=[ + Prefab.Criterion( + operator=Prefab.Criterion.CriterionOperator.PROP_BEFORE, + property_name="prefab.current-time", + value_to_match=Prefab.ConfigValue( + int=future_time_millis + ), + ) + ], + value=Prefab.ConfigValue(string=desired_value), + ) + ], + ), + ], + ) + + # Create a config that checks if current time is after a past time + past_time = test_time - timedelta(hours=1) + past_time_millis = int(past_time.timestamp() * 1000) + + config_past = Prefab.Config( + key=key, + rows=[ + default_row, + Prefab.ConfigRow( + project_env_id=project_env_id, + values=[ + Prefab.ConditionalValue( + criteria=[ + Prefab.Criterion( + operator=Prefab.Criterion.CriterionOperator.PROP_AFTER, + property_name="prefab.current-time", + value_to_match=Prefab.ConfigValue( + int=past_time_millis + ), + ) + ], + value=Prefab.ConfigValue(string=desired_value), + ) + ], + ), + ], + ) + + with patch("time.time") as mock_time: + # Set the mock to return our test time + mock_time.return_value = test_time.timestamp() + + evaluator = CriteriaEvaluator( + config, project_env_id, resolver=None, base_client=None + ) + evaluator_past = CriteriaEvaluator( + config_past, project_env_id, resolver=None, base_client=None + ) + + # Test current time is before future time + evaluation = evaluator.evaluate(context({})) + assert evaluation.raw_config_value().string == desired_value + + # Test current time is after past time + evaluation = evaluator_past.evaluate(context({})) + assert evaluation.raw_config_value().string == desired_value + + # Test with a different time that's after the future time + mock_time.return_value = ( + future_time.timestamp() + 3600 + ) # 1 hour after future_time + evaluation = evaluator.evaluate(context({})) + assert evaluation.raw_config_value().string == default_value + + # Test with a different time that's before the past time + mock_time.return_value = ( + past_time.timestamp() - 3600 + ) # 1 hour before past_time + evaluation = evaluator_past.evaluate(context({})) + assert evaluation.raw_config_value().string == default_value + @staticmethod def mock_resolver(config): return MockResolver(config)