|
11 | 11 | ParseError, |
12 | 12 | FlagNotFoundError, |
13 | 13 | ) |
| 14 | +from openfeature.track import TrackingEventDetails |
14 | 15 |
|
15 | 16 | from openfeature_flagsmith.exceptions import FlagsmithProviderError |
16 | 17 | from openfeature_flagsmith.provider import FlagsmithProvider |
@@ -450,3 +451,143 @@ def test_resolve_boolean_details_uses_enabled_when_use_boolean_config_value_is_f |
450 | 451 | assert result.value is True |
451 | 452 | assert result.error_code is None |
452 | 453 | assert result.reason is None |
| 454 | + |
| 455 | + |
| 456 | +# --------------------------------------------------------------------------- |
| 457 | +# Tracking |
| 458 | +# --------------------------------------------------------------------------- |
| 459 | + |
| 460 | + |
| 461 | +def test_track_is_noop_without_track_event_on_client() -> None: |
| 462 | + # Given - client without track_event (e.g. older flagsmith version) |
| 463 | + client = MagicMock(spec=[]) |
| 464 | + provider = FlagsmithProvider(client) |
| 465 | + |
| 466 | + # When / Then - no error raised |
| 467 | + provider.track("purchase") |
| 468 | + |
| 469 | + |
| 470 | +def test_track_is_noop_when_pipeline_analytics_not_configured( |
| 471 | + mock_flagsmith_client: MagicMock, |
| 472 | +) -> None: |
| 473 | + # Given - client has track_event but raises ValueError (no analytics config) |
| 474 | + mock_flagsmith_client.track_event = MagicMock( |
| 475 | + side_effect=ValueError("Pipeline analytics is not configured") |
| 476 | + ) |
| 477 | + provider = FlagsmithProvider(mock_flagsmith_client) |
| 478 | + |
| 479 | + # When / Then - no error raised, ValueError caught silently |
| 480 | + provider.track("purchase") |
| 481 | + |
| 482 | + |
| 483 | +def test_track_delegates_to_client(mock_flagsmith_client: MagicMock) -> None: |
| 484 | + # Given |
| 485 | + mock_flagsmith_client.track_event = MagicMock() |
| 486 | + provider = FlagsmithProvider(mock_flagsmith_client) |
| 487 | + |
| 488 | + # When |
| 489 | + provider.track( |
| 490 | + "purchase", |
| 491 | + evaluation_context=EvaluationContext( |
| 492 | + targeting_key="user-123", |
| 493 | + attributes={"plan": "premium"}, |
| 494 | + ), |
| 495 | + tracking_event_details=TrackingEventDetails( |
| 496 | + value=99.77, |
| 497 | + attributes={"currency": "USD"}, |
| 498 | + ), |
| 499 | + ) |
| 500 | + |
| 501 | + # Then |
| 502 | + mock_flagsmith_client.track_event.assert_called_once_with( |
| 503 | + "purchase", |
| 504 | + identity_identifier="user-123", |
| 505 | + traits={"plan": "premium"}, |
| 506 | + metadata={"value": 99.77, "currency": "USD"}, |
| 507 | + ) |
| 508 | + |
| 509 | + |
| 510 | +def test_track_with_minimal_args(mock_flagsmith_client: MagicMock) -> None: |
| 511 | + # Given |
| 512 | + mock_flagsmith_client.track_event = MagicMock() |
| 513 | + provider = FlagsmithProvider(mock_flagsmith_client) |
| 514 | + |
| 515 | + # When |
| 516 | + provider.track("signup") |
| 517 | + |
| 518 | + # Then |
| 519 | + mock_flagsmith_client.track_event.assert_called_once_with( |
| 520 | + "signup", |
| 521 | + identity_identifier=None, |
| 522 | + traits=None, |
| 523 | + metadata=None, |
| 524 | + ) |
| 525 | + |
| 526 | + |
| 527 | +def test_track_value_takes_precedence_over_attributes_value( |
| 528 | + mock_flagsmith_client: MagicMock, |
| 529 | +) -> None: |
| 530 | + # Given - attributes also has a "value" key |
| 531 | + mock_flagsmith_client.track_event = MagicMock() |
| 532 | + provider = FlagsmithProvider(mock_flagsmith_client) |
| 533 | + |
| 534 | + # When |
| 535 | + provider.track( |
| 536 | + "checkout", |
| 537 | + tracking_event_details=TrackingEventDetails( |
| 538 | + value=99.77, |
| 539 | + attributes={"value": "should_be_overwritten", "other": "kept"}, |
| 540 | + ), |
| 541 | + ) |
| 542 | + |
| 543 | + # Then - explicit .value wins over attributes["value"] |
| 544 | + mock_flagsmith_client.track_event.assert_called_once_with( |
| 545 | + "checkout", |
| 546 | + identity_identifier=None, |
| 547 | + traits=None, |
| 548 | + metadata={"value": 99.77, "other": "kept"}, |
| 549 | + ) |
| 550 | + |
| 551 | + |
| 552 | +def test_track_with_details_value_only(mock_flagsmith_client: MagicMock) -> None: |
| 553 | + # Given |
| 554 | + mock_flagsmith_client.track_event = MagicMock() |
| 555 | + provider = FlagsmithProvider(mock_flagsmith_client) |
| 556 | + |
| 557 | + # When |
| 558 | + provider.track("checkout", tracking_event_details=TrackingEventDetails(value=99.77)) |
| 559 | + |
| 560 | + # Then |
| 561 | + mock_flagsmith_client.track_event.assert_called_once_with( |
| 562 | + "checkout", |
| 563 | + identity_identifier=None, |
| 564 | + traits=None, |
| 565 | + metadata={"value": 99.77}, |
| 566 | + ) |
| 567 | + |
| 568 | + |
| 569 | +def test_track_extracts_traits_from_context(mock_flagsmith_client: MagicMock) -> None: |
| 570 | + # Given - nested traits take precedence over flat attributes (same rule as _get_flags) |
| 571 | + mock_flagsmith_client.track_event = MagicMock() |
| 572 | + provider = FlagsmithProvider(mock_flagsmith_client) |
| 573 | + |
| 574 | + # When |
| 575 | + provider.track( |
| 576 | + "page_view", |
| 577 | + evaluation_context=EvaluationContext( |
| 578 | + targeting_key="user-123", |
| 579 | + attributes={ |
| 580 | + "shared_key": "flat_value", |
| 581 | + "other": "kept", |
| 582 | + "traits": {"shared_key": "nested_value"}, |
| 583 | + }, |
| 584 | + ), |
| 585 | + ) |
| 586 | + |
| 587 | + # Then |
| 588 | + mock_flagsmith_client.track_event.assert_called_once_with( |
| 589 | + "page_view", |
| 590 | + identity_identifier="user-123", |
| 591 | + traits={"shared_key": "nested_value", "other": "kept"}, |
| 592 | + metadata=None, |
| 593 | + ) |
0 commit comments