|
| 1 | +from __future__ import annotations |
| 2 | + |
| 3 | +from typing import Any |
| 4 | + |
| 5 | +from prime_cli import __version__ |
| 6 | +from prime_cli.core import APIClient, APIError |
| 7 | +from prime_cli.feature_flags import ( |
| 8 | + FeatureFlagsClient, |
| 9 | + evaluate_feature_flags, |
| 10 | + is_feature_enabled, |
| 11 | +) |
| 12 | + |
| 13 | + |
| 14 | +class DummyConfig: |
| 15 | + def __init__(self, team_id: str | None = None) -> None: |
| 16 | + self.team_id = team_id |
| 17 | + |
| 18 | + |
| 19 | +class DummyFeatureFlagAPIClient(APIClient): |
| 20 | + def __init__( |
| 21 | + self, |
| 22 | + response: dict[str, Any] | None = None, |
| 23 | + team_id: str | None = None, |
| 24 | + error: APIError | None = None, |
| 25 | + ) -> None: |
| 26 | + self.config = DummyConfig(team_id) |
| 27 | + self.response = response or {"data": {"flags": {}}} |
| 28 | + self.error = error |
| 29 | + self.posts: list[tuple[str, dict[str, Any] | None]] = [] |
| 30 | + |
| 31 | + def post(self, endpoint: str, json: dict[str, Any] | None = None) -> dict[str, Any]: |
| 32 | + self.posts.append((endpoint, json)) |
| 33 | + if self.error: |
| 34 | + raise self.error |
| 35 | + return self.response |
| 36 | + |
| 37 | + |
| 38 | +def test_feature_flags_client_evaluates_flags_with_cli_context() -> None: |
| 39 | + client = DummyFeatureFlagAPIClient( |
| 40 | + response={"data": {"flags": {"cli-new-flow": True}}}, |
| 41 | + team_id="team-1", |
| 42 | + ) |
| 43 | + |
| 44 | + result = FeatureFlagsClient(client=client).evaluate( |
| 45 | + {"cli-new-flow": False, "copy.variant": "control"} |
| 46 | + ) |
| 47 | + |
| 48 | + assert result == {"cli-new-flow": True, "copy.variant": "control"} |
| 49 | + assert client.posts == [ |
| 50 | + ( |
| 51 | + "/feature-flags/evaluate", |
| 52 | + { |
| 53 | + "flags": {"cli-new-flow": False, "copy.variant": "control"}, |
| 54 | + "cli_version": __version__, |
| 55 | + "team_id": "team-1", |
| 56 | + }, |
| 57 | + ) |
| 58 | + ] |
| 59 | + |
| 60 | + |
| 61 | +def test_evaluate_feature_flags_returns_defaults_when_api_unavailable() -> None: |
| 62 | + client = DummyFeatureFlagAPIClient(error=APIError("feature flag service unavailable")) |
| 63 | + defaults = {"cli-new-flow": False} |
| 64 | + |
| 65 | + result = evaluate_feature_flags(defaults, client=client) |
| 66 | + |
| 67 | + assert result == defaults |
| 68 | + assert result is not defaults |
| 69 | + |
| 70 | + |
| 71 | +def test_feature_flags_client_skips_empty_request() -> None: |
| 72 | + client = DummyFeatureFlagAPIClient() |
| 73 | + |
| 74 | + assert FeatureFlagsClient(client=client).evaluate({}) == {} |
| 75 | + assert client.posts == [] |
| 76 | + |
| 77 | + |
| 78 | +def test_is_feature_enabled_only_accepts_boolean_true() -> None: |
| 79 | + enabled_client = DummyFeatureFlagAPIClient(response={"data": {"flags": {"enabled": True}}}) |
| 80 | + string_client = DummyFeatureFlagAPIClient(response={"data": {"flags": {"enabled": "true"}}}) |
| 81 | + |
| 82 | + assert is_feature_enabled("enabled", client=enabled_client) is True |
| 83 | + assert is_feature_enabled("enabled", default=True, client=string_client) is False |
0 commit comments