Skip to content

Commit 9951cea

Browse files
committed
test: add utils acceptance scenarios
1 parent f7d8526 commit 9951cea

4 files changed

Lines changed: 330 additions & 1 deletion

File tree

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
Feature: SDK utility behavior
2+
The SDK utility module prepares user data, cache entries, and runtime metadata
3+
for higher-level SDK flows.
4+
5+
Scenario: Clean SDK payload values before capture
6+
Given an SDK event payload with Python-specific values
7+
When the SDK cleans the event payload
8+
Then transformed values are safe for SDK serialization
9+
And unsupported payload values are dropped to null
10+
11+
Scenario: Reuse cached feature flag evaluations safely
12+
Given a cached feature flag evaluation for a user
13+
When the SDK reads the cached flag for current and newer definitions
14+
Then the current flag definition uses the cached evaluation
15+
And the newer flag definition misses the cache
16+
When the old flag definition is invalidated
17+
Then the cached evaluation is removed
18+
19+
Scenario: Build runtime system context
20+
Given the SDK is running on a Linux host with distribution metadata
21+
When the SDK builds system context
22+
Then the context includes Python runtime and Linux metadata
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import json
2+
import sys
3+
from dataclasses import dataclass
4+
from decimal import Decimal
5+
from uuid import UUID
6+
from unittest import mock
7+
8+
from pytest_bdd import given, scenarios, then, when
9+
10+
from posthog import utils
11+
from posthog.types import FeatureFlagResult
12+
13+
scenarios("features/utils.feature")
14+
15+
16+
@dataclass
17+
class AcceptancePayloadMetadata:
18+
source: str
19+
sample_rate: Decimal
20+
21+
22+
@given("an SDK event payload with Python-specific values", target_fixture="payload")
23+
def sdk_event_payload():
24+
return {
25+
"event": "plan upgraded",
26+
"distinct_id": UUID("12345678123456781234567812345678"),
27+
"properties": {
28+
"price": Decimal("12.34"),
29+
"metadata": AcceptancePayloadMetadata(
30+
source="checkout",
31+
sample_rate=Decimal("0.5"),
32+
),
33+
"labels": ("paid", "beta"),
34+
"raw_bytes": b"hello",
35+
"unsupported": lambda: None,
36+
},
37+
}
38+
39+
40+
@when("the SDK cleans the event payload", target_fixture="cleaned_payload")
41+
def clean_event_payload(payload):
42+
return utils.clean(payload)
43+
44+
45+
@then("transformed values are safe for SDK serialization")
46+
def transformed_values_are_safe_for_sdk_serialization(cleaned_payload):
47+
assert cleaned_payload["distinct_id"] == "12345678-1234-5678-1234-567812345678"
48+
assert cleaned_payload["properties"]["price"] == 12.34
49+
assert cleaned_payload["properties"]["metadata"] == {
50+
"source": "checkout",
51+
"sample_rate": 0.5,
52+
}
53+
assert cleaned_payload["properties"]["labels"] == ["paid", "beta"]
54+
assert cleaned_payload["properties"]["raw_bytes"] == "hello"
55+
json.dumps(cleaned_payload)
56+
57+
58+
@then("unsupported payload values are dropped to null")
59+
def unsupported_payload_values_are_dropped_to_null(cleaned_payload):
60+
assert cleaned_payload["properties"]["unsupported"] is None
61+
62+
63+
@given("a cached feature flag evaluation for a user", target_fixture="flag_cache_state")
64+
def cached_feature_flag_evaluation():
65+
cache = utils.FlagCache(max_size=10, default_ttl=60)
66+
flag_result = FeatureFlagResult.from_value_and_payload(
67+
"checkout-redesign",
68+
True,
69+
{"variant": "test"},
70+
)
71+
cache.set_cached_flag(
72+
"user-123",
73+
"checkout-redesign",
74+
flag_result,
75+
flag_definition_version=1,
76+
)
77+
return {"cache": cache, "flag_result": flag_result}
78+
79+
80+
@when("the SDK reads the cached flag for current and newer definitions")
81+
def read_cached_flag_versions(flag_cache_state):
82+
cache = flag_cache_state["cache"]
83+
flag_cache_state["current_result"] = cache.get_cached_flag(
84+
"user-123",
85+
"checkout-redesign",
86+
current_flag_version=1,
87+
)
88+
flag_cache_state["newer_result"] = cache.get_cached_flag(
89+
"user-123",
90+
"checkout-redesign",
91+
current_flag_version=2,
92+
)
93+
94+
95+
@then("the current flag definition uses the cached evaluation")
96+
def current_definition_uses_cached_evaluation(flag_cache_state):
97+
current_result = flag_cache_state["current_result"]
98+
assert current_result is flag_cache_state["flag_result"]
99+
assert current_result.get_value() is True
100+
assert current_result.payload == {"variant": "test"}
101+
102+
103+
@then("the newer flag definition misses the cache")
104+
def newer_definition_misses_cache(flag_cache_state):
105+
assert flag_cache_state["newer_result"] is None
106+
107+
108+
@when("the old flag definition is invalidated")
109+
def invalidate_old_flag_definition(flag_cache_state):
110+
flag_cache_state["cache"].invalidate_version(1)
111+
112+
113+
@then("the cached evaluation is removed")
114+
def cached_evaluation_is_removed(flag_cache_state):
115+
assert (
116+
flag_cache_state["cache"].get_cached_flag(
117+
"user-123",
118+
"checkout-redesign",
119+
current_flag_version=1,
120+
)
121+
is None
122+
)
123+
124+
125+
@given(
126+
"the SDK is running on a Linux host with distribution metadata",
127+
target_fixture="linux_host_context",
128+
)
129+
def linux_host_with_distribution_metadata():
130+
patches = [
131+
mock.patch.object(utils.sys, "platform", "linux"),
132+
mock.patch.object(
133+
utils.platform, "python_implementation", return_value="CPython"
134+
),
135+
mock.patch.object(utils.distro, "info", return_value={"version": "24.04"}),
136+
mock.patch.object(utils.distro, "name", return_value="Ubuntu"),
137+
]
138+
for patch in patches:
139+
patch.start()
140+
yield
141+
for patch in reversed(patches):
142+
patch.stop()
143+
144+
145+
@when("the SDK builds system context", target_fixture="system_context")
146+
def build_system_context(linux_host_context):
147+
return utils.system_context()
148+
149+
150+
@then("the context includes Python runtime and Linux metadata")
151+
def context_includes_python_runtime_and_linux_metadata(system_context):
152+
assert system_context == {
153+
"$python_runtime": "CPython",
154+
"$python_version": f"{sys.version_info[0]}.{sys.version_info[1]}.{sys.version_info[2]}",
155+
"$os": "Linux",
156+
"$os_version": "24.04",
157+
"$os_distro": "Ubuntu",
158+
}

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ test = [
8181
"claude-agent-sdk",
8282
"opentelemetry-sdk>=1.20.0",
8383
"opentelemetry-exporter-otlp-proto-http>=1.20.0",
84+
"pytest-bdd>=8.1.0",
8485
]
8586

8687
[tool.setuptools]

0 commit comments

Comments
 (0)