|
4 | 4 | import types |
5 | 5 | import uuid |
6 | 6 | from concurrent.futures import ThreadPoolExecutor |
7 | | -from unittest.mock import MagicMock |
| 7 | +from unittest.mock import MagicMock, Mock |
8 | 8 |
|
9 | 9 | import pytest |
10 | 10 |
|
11 | 11 | from openfeature import _event_support, api |
| 12 | +from openfeature import client as client_module |
12 | 13 | from openfeature.api import ( |
13 | 14 | add_hooks, |
14 | 15 | clear_hooks, |
|
20 | 21 | from openfeature.client import OpenFeatureClient, _typecheck_flag_value |
21 | 22 | from openfeature.evaluation_context import EvaluationContext |
22 | 23 | from openfeature.event import EventDetails, ProviderEvent, ProviderEventDetails |
23 | | -from openfeature.exception import ErrorCode, OpenFeatureError |
| 24 | +from openfeature.exception import ErrorCode, OpenFeatureError, ProviderFatalError |
24 | 25 | from openfeature.flag_evaluation import FlagResolutionDetails, FlagType, Reason |
25 | 26 | from openfeature.hook import Hook |
26 | 27 | from openfeature.provider import FeatureProvider, ProviderStatus |
@@ -291,9 +292,10 @@ def test_provider_should_return_error_status_if_failed(): |
291 | 292 | async def test_should_shortcircuit_if_provider_is_not_ready( |
292 | 293 | no_op_provider_client, monkeypatch |
293 | 294 | ): |
294 | | - # Given |
295 | 295 | monkeypatch.setattr( |
296 | | - no_op_provider_client, "get_provider_status", lambda: ProviderStatus.NOT_READY |
| 296 | + provider_registry, |
| 297 | + "get_provider_status", |
| 298 | + lambda provider: ProviderStatus.NOT_READY, |
297 | 299 | ) |
298 | 300 | spy_hook = MagicMock(spec=Hook) |
299 | 301 | no_op_provider_client.add_hooks([spy_hook]) |
@@ -321,9 +323,10 @@ async def test_should_shortcircuit_if_provider_is_not_ready( |
321 | 323 | async def test_should_shortcircuit_if_provider_is_in_irrecoverable_error_state( |
322 | 324 | no_op_provider_client, monkeypatch |
323 | 325 | ): |
324 | | - # Given |
325 | 326 | monkeypatch.setattr( |
326 | | - no_op_provider_client, "get_provider_status", lambda: ProviderStatus.FATAL |
| 327 | + provider_registry, |
| 328 | + "get_provider_status", |
| 329 | + lambda provider: ProviderStatus.FATAL, |
327 | 330 | ) |
328 | 331 | spy_hook = MagicMock(spec=Hook) |
329 | 332 | no_op_provider_client.add_hooks([spy_hook]) |
@@ -768,3 +771,32 @@ def test_should_noop_if_provider_does_not_support_tracking(monkeypatch): |
768 | 771 | set_provider(provider) |
769 | 772 | client = get_client() |
770 | 773 | client.track(tracking_event_name="test") |
| 774 | + |
| 775 | + |
| 776 | +def test_assert_provider_status_uses_passed_provider_not_current_registry_state(): |
| 777 | + fatal_provider = NoOpProvider() |
| 778 | + ready_provider = NoOpProvider() |
| 779 | + |
| 780 | + registry_mock = Mock() |
| 781 | + registry_mock.get_provider_status.side_effect = lambda p: ( |
| 782 | + ProviderStatus.FATAL if p is fatal_provider else ProviderStatus.READY |
| 783 | + ) |
| 784 | + registry_mock.get_provider.return_value = ready_provider |
| 785 | + |
| 786 | + original = client_module.provider_registry |
| 787 | + client_module.provider_registry = registry_mock |
| 788 | + try: |
| 789 | + c = OpenFeatureClient(domain=None, version=None) |
| 790 | + assert c.provider is ready_provider, ( |
| 791 | + "test setup: self.provider should resolve via the patched registry" |
| 792 | + ) |
| 793 | + |
| 794 | + err = c._assert_provider_status(fatal_provider) |
| 795 | + assert isinstance(err, ProviderFatalError), ( |
| 796 | + "status check used self.provider (READY) instead of the captured " |
| 797 | + "fatal_provider — TOCTOU regression" |
| 798 | + ) |
| 799 | + registry_mock.get_provider_status.assert_any_call(fatal_provider) |
| 800 | + assert c._assert_provider_status(ready_provider) is None |
| 801 | + finally: |
| 802 | + client_module.provider_registry = original |
0 commit comments