|
1 | 1 | from unittest.mock import MagicMock |
2 | 2 | import pytest |
| 3 | +from openfeature.evaluation_context import EvaluationContext |
3 | 4 | from openfeature.exception import ErrorCode |
4 | 5 | from openfeature.flag_evaluation import Reason |
5 | 6 |
|
@@ -294,3 +295,142 @@ def test_remote_provider_always_ready(): |
294 | 295 |
|
295 | 296 | def test_shutdown_is_noop(provider): |
296 | 297 | provider.shutdown() # Should not raise |
| 298 | + |
| 299 | + |
| 300 | +# --- EvaluationContext forwarding --- |
| 301 | + |
| 302 | + |
| 303 | +def test_forwards_targeting_key_as_distinct_id(provider, mock_flags): |
| 304 | + setup_flag(mock_flags, "flag", "val") |
| 305 | + ctx = EvaluationContext(targeting_key="user-123") |
| 306 | + provider.resolve_string_details("flag", "default", ctx) |
| 307 | + _, _, user_context = mock_flags.get_variant.call_args[0] |
| 308 | + assert user_context["distinct_id"] == "user-123" |
| 309 | + |
| 310 | + |
| 311 | +def test_forwards_attributes_as_custom_properties(provider, mock_flags): |
| 312 | + setup_flag(mock_flags, "flag", "val") |
| 313 | + ctx = EvaluationContext(attributes={"plan": "pro", "beta": True}) |
| 314 | + provider.resolve_string_details("flag", "default", ctx) |
| 315 | + _, _, user_context = mock_flags.get_variant.call_args[0] |
| 316 | + assert user_context["custom_properties"] == {"plan": "pro", "beta": True} |
| 317 | + |
| 318 | + |
| 319 | +def test_forwards_full_context(provider, mock_flags): |
| 320 | + setup_flag(mock_flags, "flag", "val") |
| 321 | + ctx = EvaluationContext( |
| 322 | + targeting_key="user-456", attributes={"tier": "enterprise"} |
| 323 | + ) |
| 324 | + provider.resolve_string_details("flag", "default", ctx) |
| 325 | + _, _, user_context = mock_flags.get_variant.call_args[0] |
| 326 | + assert user_context == { |
| 327 | + "distinct_id": "user-456", |
| 328 | + "custom_properties": {"tier": "enterprise"}, |
| 329 | + } |
| 330 | + |
| 331 | + |
| 332 | +def test_no_context_passes_empty_dict(provider, mock_flags): |
| 333 | + setup_flag(mock_flags, "flag", "val") |
| 334 | + provider.resolve_string_details("flag", "default") |
| 335 | + _, _, user_context = mock_flags.get_variant.call_args[0] |
| 336 | + assert user_context == {} |
| 337 | + |
| 338 | + |
| 339 | +# --- Variant key passthrough --- |
| 340 | + |
| 341 | + |
| 342 | +def test_variant_key_present_in_boolean_resolution(provider, mock_flags): |
| 343 | + setup_flag(mock_flags, "bool-flag", True, variant_key="control") |
| 344 | + result = provider.resolve_boolean_details("bool-flag", False) |
| 345 | + assert result.value is True |
| 346 | + assert result.variant == "control" |
| 347 | + assert result.reason == Reason.STATIC |
| 348 | + |
| 349 | + |
| 350 | +def test_variant_key_present_in_string_resolution(provider, mock_flags): |
| 351 | + setup_flag(mock_flags, "string-flag", "hello", variant_key="treatment-a") |
| 352 | + result = provider.resolve_string_details("string-flag", "default") |
| 353 | + assert result.value == "hello" |
| 354 | + assert result.variant == "treatment-a" |
| 355 | + assert result.reason == Reason.STATIC |
| 356 | + |
| 357 | + |
| 358 | +def test_variant_key_present_in_integer_resolution(provider, mock_flags): |
| 359 | + setup_flag(mock_flags, "int-flag", 42, variant_key="v2") |
| 360 | + result = provider.resolve_integer_details("int-flag", 0) |
| 361 | + assert result.value == 42 |
| 362 | + assert result.variant == "v2" |
| 363 | + assert result.reason == Reason.STATIC |
| 364 | + |
| 365 | + |
| 366 | +def test_variant_key_present_in_float_resolution(provider, mock_flags): |
| 367 | + setup_flag(mock_flags, "float-flag", 3.14, variant_key="v3") |
| 368 | + result = provider.resolve_float_details("float-flag", 0.0) |
| 369 | + assert result.value == pytest.approx(3.14) |
| 370 | + assert result.variant == "v3" |
| 371 | + assert result.reason == Reason.STATIC |
| 372 | + |
| 373 | + |
| 374 | +def test_variant_key_present_in_object_resolution(provider, mock_flags): |
| 375 | + setup_flag(mock_flags, "obj-flag", {"key": "value"}, variant_key="v4") |
| 376 | + result = provider.resolve_object_details("obj-flag", {}) |
| 377 | + assert result.value == {"key": "value"} |
| 378 | + assert result.variant == "v4" |
| 379 | + assert result.reason == Reason.STATIC |
| 380 | + |
| 381 | + |
| 382 | +# --- SDK exception handling --- |
| 383 | + |
| 384 | + |
| 385 | +def test_sdk_exception_returns_default_boolean(provider, mock_flags): |
| 386 | + mock_flags.get_variant.side_effect = RuntimeError("SDK failure") |
| 387 | + result = provider.resolve_boolean_details("flag", True) |
| 388 | + assert result.value is True |
| 389 | + assert result.error_code == ErrorCode.GENERAL |
| 390 | + assert result.reason == Reason.ERROR |
| 391 | + |
| 392 | + |
| 393 | +def test_sdk_exception_returns_default_string(provider, mock_flags): |
| 394 | + mock_flags.get_variant.side_effect = RuntimeError("SDK failure") |
| 395 | + result = provider.resolve_string_details("flag", "fallback") |
| 396 | + assert result.value == "fallback" |
| 397 | + assert result.error_code == ErrorCode.GENERAL |
| 398 | + assert result.reason == Reason.ERROR |
| 399 | + |
| 400 | + |
| 401 | +def test_sdk_exception_returns_default_integer(provider, mock_flags): |
| 402 | + mock_flags.get_variant.side_effect = RuntimeError("SDK failure") |
| 403 | + result = provider.resolve_integer_details("flag", 99) |
| 404 | + assert result.value == 99 |
| 405 | + assert result.error_code == ErrorCode.GENERAL |
| 406 | + assert result.reason == Reason.ERROR |
| 407 | + |
| 408 | + |
| 409 | +# --- Null variant key --- |
| 410 | + |
| 411 | + |
| 412 | +def test_null_variant_key_boolean(provider, mock_flags): |
| 413 | + setup_flag(mock_flags, "flag", True, variant_key=None) |
| 414 | + result = provider.resolve_boolean_details("flag", False) |
| 415 | + assert result.value is True |
| 416 | + assert result.variant is None |
| 417 | + assert result.reason == Reason.STATIC |
| 418 | + assert result.error_code is None |
| 419 | + |
| 420 | + |
| 421 | +def test_null_variant_key_string(provider, mock_flags): |
| 422 | + setup_flag(mock_flags, "flag", "hello", variant_key=None) |
| 423 | + result = provider.resolve_string_details("flag", "default") |
| 424 | + assert result.value == "hello" |
| 425 | + assert result.variant is None |
| 426 | + assert result.reason == Reason.STATIC |
| 427 | + assert result.error_code is None |
| 428 | + |
| 429 | + |
| 430 | +def test_null_variant_key_object(provider, mock_flags): |
| 431 | + setup_flag(mock_flags, "flag", {"key": "value"}, variant_key=None) |
| 432 | + result = provider.resolve_object_details("flag", {}) |
| 433 | + assert result.value == {"key": "value"} |
| 434 | + assert result.variant is None |
| 435 | + assert result.reason == Reason.STATIC |
| 436 | + assert result.error_code is None |
0 commit comments