Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions posthog/test/features/utils.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Feature: SDK utility behavior
The SDK utility module prepares user data, cache entries, and runtime metadata
for higher-level SDK flows.

Scenario: Clean SDK payload values before capture
Given an SDK event payload with Python-specific values
When the SDK cleans the event payload
Then transformed values are safe for SDK serialization
And unsupported payload values are dropped to null

Scenario: Reuse cached feature flag evaluations safely
Given a cached feature flag evaluation for a user
When the SDK reads the cached flag for current and newer definitions
Then the current flag definition uses the cached evaluation
And the newer flag definition misses the cache
When the old flag definition is invalidated
Then the cached evaluation is removed

Scenario: Build runtime system context
Given the SDK is running on a Linux host with distribution metadata
When the SDK builds system context
Then the context includes Python runtime and Linux metadata
158 changes: 158 additions & 0 deletions posthog/test/test_utils_acceptance.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import json
import sys
from dataclasses import dataclass
from decimal import Decimal
from uuid import UUID
from unittest import mock

from pytest_bdd import given, scenarios, then, when

from posthog import utils
from posthog.types import FeatureFlagResult

scenarios("features/utils.feature")


@dataclass
class AcceptancePayloadMetadata:
source: str
sample_rate: Decimal


@given("an SDK event payload with Python-specific values", target_fixture="payload")
def sdk_event_payload():
return {
"event": "plan upgraded",
"distinct_id": UUID("12345678123456781234567812345678"),
"properties": {
"price": Decimal("12.34"),
"metadata": AcceptancePayloadMetadata(
source="checkout",
sample_rate=Decimal("0.5"),
),
"labels": ("paid", "beta"),
"raw_bytes": b"hello",
"unsupported": lambda: None,
},
}


@when("the SDK cleans the event payload", target_fixture="cleaned_payload")
def clean_event_payload(payload):
return utils.clean(payload)


@then("transformed values are safe for SDK serialization")
def transformed_values_are_safe_for_sdk_serialization(cleaned_payload):
assert cleaned_payload["distinct_id"] == "12345678-1234-5678-1234-567812345678"
assert cleaned_payload["properties"]["price"] == 12.34
assert cleaned_payload["properties"]["metadata"] == {
"source": "checkout",
"sample_rate": 0.5,
}
assert cleaned_payload["properties"]["labels"] == ["paid", "beta"]
assert cleaned_payload["properties"]["raw_bytes"] == "hello"
json.dumps(cleaned_payload)


@then("unsupported payload values are dropped to null")
def unsupported_payload_values_are_dropped_to_null(cleaned_payload):
assert cleaned_payload["properties"]["unsupported"] is None


@given("a cached feature flag evaluation for a user", target_fixture="flag_cache_state")
def cached_feature_flag_evaluation():
cache = utils.FlagCache(max_size=10, default_ttl=60)
flag_result = FeatureFlagResult.from_value_and_payload(
"checkout-redesign",
True,
{"variant": "test"},
)
cache.set_cached_flag(
"user-123",
"checkout-redesign",
flag_result,
flag_definition_version=1,
)
return {"cache": cache, "flag_result": flag_result}


@when("the SDK reads the cached flag for current and newer definitions")
def read_cached_flag_versions(flag_cache_state):
cache = flag_cache_state["cache"]
flag_cache_state["current_result"] = cache.get_cached_flag(
"user-123",
"checkout-redesign",
current_flag_version=1,
)
flag_cache_state["newer_result"] = cache.get_cached_flag(
"user-123",
"checkout-redesign",
current_flag_version=2,
)


@then("the current flag definition uses the cached evaluation")
def current_definition_uses_cached_evaluation(flag_cache_state):
current_result = flag_cache_state["current_result"]
assert current_result is flag_cache_state["flag_result"]
assert current_result.get_value() is True
assert current_result.payload == {"variant": "test"}


@then("the newer flag definition misses the cache")
def newer_definition_misses_cache(flag_cache_state):
assert flag_cache_state["newer_result"] is None


@when("the old flag definition is invalidated")
def invalidate_old_flag_definition(flag_cache_state):
flag_cache_state["cache"].invalidate_version(1)


@then("the cached evaluation is removed")
def cached_evaluation_is_removed(flag_cache_state):
assert (
flag_cache_state["cache"].get_cached_flag(
"user-123",
"checkout-redesign",
current_flag_version=1,
)
is None
)


@given(
"the SDK is running on a Linux host with distribution metadata",
target_fixture="linux_host_context",
)
def linux_host_with_distribution_metadata():
patches = [
mock.patch.object(utils.sys, "platform", "linux"),
mock.patch.object(
utils.platform, "python_implementation", return_value="CPython"
),
mock.patch.object(utils.distro, "info", return_value={"version": "24.04"}),
mock.patch.object(utils.distro, "name", return_value="Ubuntu"),
]
for patch in patches:
patch.start()
yield
for patch in reversed(patches):
patch.stop()


@when("the SDK builds system context", target_fixture="system_context")
def build_system_context(linux_host_context):
return utils.system_context()


@then("the context includes Python runtime and Linux metadata")
def context_includes_python_runtime_and_linux_metadata(system_context):
assert system_context == {
"$python_runtime": "CPython",
"$python_version": f"{sys.version_info[0]}.{sys.version_info[1]}.{sys.version_info[2]}",
"$os": "Linux",
"$os_version": "24.04",
"$os_distro": "Ubuntu",
}
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ test = [
"claude-agent-sdk",
"opentelemetry-sdk>=1.20.0",
"opentelemetry-exporter-otlp-proto-http>=1.20.0",
"pytest-bdd>=8.1.0",
]

[tool.setuptools]
Expand Down
Loading
Loading