|
1 | 1 | import datetime |
| 2 | +import os |
2 | 3 | import unittest |
3 | 4 |
|
4 | 5 | import mock |
5 | 6 | from dateutil import parser, tz |
6 | 7 | from freezegun import freeze_time |
| 8 | +from parameterized import parameterized |
7 | 9 |
|
8 | 10 | from posthog.client import Client |
9 | 11 | from posthog.feature_flags import ( |
@@ -3780,6 +3782,80 @@ def test_get_all_flags_fallback_when_device_id_missing_for_some_flags( |
3780 | 3782 | self.assertEqual(patch_flags.call_count, 1) |
3781 | 3783 |
|
3782 | 3784 |
|
| 3785 | +class TestLocalEvalEndpointConfig(unittest.TestCase): |
| 3786 | + @classmethod |
| 3787 | + def setUpClass(cls): |
| 3788 | + cls.capture_patch = mock.patch.object(Client, "capture") |
| 3789 | + cls.capture_patch.start() |
| 3790 | + |
| 3791 | + @classmethod |
| 3792 | + def tearDownClass(cls): |
| 3793 | + cls.capture_patch.stop() |
| 3794 | + |
| 3795 | + @parameterized.expand( |
| 3796 | + [ |
| 3797 | + ("custom_endpoint", "/flags/definitions", "/flags/definitions?"), |
| 3798 | + ("default_endpoint", None, "/api/feature_flag/local_evaluation/"), |
| 3799 | + ] |
| 3800 | + ) |
| 3801 | + @mock.patch("posthog.client.get") |
| 3802 | + def test_endpoint_selection(self, _name, env_value, expected_prefix, patch_get): |
| 3803 | + patch_get.return_value = GetResponse( |
| 3804 | + data={"flags": [], "group_type_mapping": {}}, |
| 3805 | + etag=None, |
| 3806 | + not_modified=False, |
| 3807 | + ) |
| 3808 | + env = {"POSTHOG_LOCAL_EVALUATION_ENDPOINT": env_value} if env_value else {} |
| 3809 | + with mock.patch.dict("os.environ", env, clear=False): |
| 3810 | + if env_value is None: |
| 3811 | + os.environ.pop("POSTHOG_LOCAL_EVALUATION_ENDPOINT", None) |
| 3812 | + client = Client(FAKE_TEST_API_KEY, personal_api_key="test-key") |
| 3813 | + client._fetch_feature_flags_from_api() |
| 3814 | + call_url = patch_get.call_args[0][1] |
| 3815 | + self.assertTrue( |
| 3816 | + call_url.startswith(expected_prefix), |
| 3817 | + f"Expected URL starting with {expected_prefix}, got: {call_url}", |
| 3818 | + ) |
| 3819 | + |
| 3820 | + @parameterized.expand( |
| 3821 | + [ |
| 3822 | + ("custom_endpoint_falls_back", "/flags/definitions", 2), |
| 3823 | + ("default_endpoint_no_fallback", None, 1), |
| 3824 | + ] |
| 3825 | + ) |
| 3826 | + @mock.patch("posthog.client.get") |
| 3827 | + def test_endpoint_fallback_on_failure( |
| 3828 | + self, _name, env_value, expected_call_count, patch_get |
| 3829 | + ): |
| 3830 | + success_response = GetResponse( |
| 3831 | + data={"flags": [], "group_type_mapping": {}}, |
| 3832 | + etag=None, |
| 3833 | + not_modified=False, |
| 3834 | + ) |
| 3835 | + if expected_call_count == 2: |
| 3836 | + patch_get.side_effect = [Exception("connection refused"), success_response] |
| 3837 | + else: |
| 3838 | + patch_get.side_effect = Exception("connection refused") |
| 3839 | + |
| 3840 | + env = {"POSTHOG_LOCAL_EVALUATION_ENDPOINT": env_value} if env_value else {} |
| 3841 | + with mock.patch.dict("os.environ", env, clear=False): |
| 3842 | + if env_value is None: |
| 3843 | + os.environ.pop("POSTHOG_LOCAL_EVALUATION_ENDPOINT", None) |
| 3844 | + client = Client(FAKE_TEST_API_KEY, personal_api_key="test-key") |
| 3845 | + client._fetch_feature_flags_from_api() |
| 3846 | + self.assertEqual(patch_get.call_count, expected_call_count) |
| 3847 | + if expected_call_count == 2: |
| 3848 | + # First call used custom endpoint, second fell back to default |
| 3849 | + self.assertTrue( |
| 3850 | + patch_get.call_args_list[0][0][1].startswith("/flags/definitions?") |
| 3851 | + ) |
| 3852 | + self.assertTrue( |
| 3853 | + patch_get.call_args_list[1][0][1].startswith( |
| 3854 | + "/api/feature_flag/local_evaluation/" |
| 3855 | + ) |
| 3856 | + ) |
| 3857 | + |
| 3858 | + |
3783 | 3859 | class TestMatchProperties(unittest.TestCase): |
3784 | 3860 | def property(self, key, value, operator=None): |
3785 | 3861 | result = {"key": key, "value": value} |
|
0 commit comments