generated from coulomb/repo-seed
First implementation
This commit is contained in:
116
tests/test_registry_resolver.py
Normal file
116
tests/test_registry_resolver.py
Normal file
@@ -0,0 +1,116 @@
|
||||
"""
|
||||
Tests for T02 registry + T03 resolver (WP-0003).
|
||||
|
||||
Run: PYTHONPATH=src python -m pytest tests/test_registry_resolver.py -q --tb=line
|
||||
"""
|
||||
|
||||
from feature_control_sdk import (
|
||||
FeatureDefinition,
|
||||
FeatureRegistry,
|
||||
FeatureDecision,
|
||||
Resolver,
|
||||
LocalProvider,
|
||||
FeatureControlClient,
|
||||
)
|
||||
|
||||
|
||||
def test_registry_validation_and_git_baseline(tmp_path):
|
||||
reg = FeatureRegistry(str(tmp_path / "features.json"))
|
||||
|
||||
# Valid
|
||||
fd = FeatureDefinition(
|
||||
feature_key="test.compute",
|
||||
name="Test Compute",
|
||||
description="...",
|
||||
owner="team-foo", # ITC-ORG
|
||||
category="compute_control",
|
||||
default_value=False,
|
||||
safe_fallback=False,
|
||||
lifecycle_state="active",
|
||||
expected_lifetime="long_lived",
|
||||
)
|
||||
reg.register(fd)
|
||||
reg.save()
|
||||
|
||||
assert "test.compute" in reg.keys()
|
||||
loaded = FeatureRegistry(str(tmp_path / "features.json"))
|
||||
assert loaded.get("test.compute").owner == "team-foo"
|
||||
|
||||
# Invalid: no owner
|
||||
try:
|
||||
reg.register(FeatureDefinition("bad", "b", "d", owner=""))
|
||||
assert False
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# Temp without review
|
||||
try:
|
||||
reg.register(
|
||||
FeatureDefinition(
|
||||
"temp.f",
|
||||
"t",
|
||||
"d",
|
||||
owner="o",
|
||||
expected_lifetime="short",
|
||||
)
|
||||
)
|
||||
assert False
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
|
||||
def test_resolver_precedence_and_decision():
|
||||
reg = FeatureRegistry()
|
||||
reg.register(
|
||||
FeatureDefinition(
|
||||
"kill.test",
|
||||
"Kill Test",
|
||||
"...",
|
||||
owner="o",
|
||||
default_value=True,
|
||||
safe_fallback=False,
|
||||
)
|
||||
)
|
||||
reg.register(
|
||||
FeatureDefinition(
|
||||
"tenant.test",
|
||||
"Tenant Test",
|
||||
"...",
|
||||
owner="o",
|
||||
default_value=False,
|
||||
)
|
||||
)
|
||||
|
||||
provider = LocalProvider(
|
||||
{
|
||||
"kill:kill.test": True, # kill signal
|
||||
"tenant:acme:tenant.test": True,
|
||||
}
|
||||
)
|
||||
resolver = Resolver(reg, provider.values)
|
||||
|
||||
# Kill wins
|
||||
d = resolver.evaluate("kill.test", True, {"tenant_id": "acme"})
|
||||
assert d.value is False
|
||||
assert d.reason == "kill_switch"
|
||||
assert d.source == "kill_switch"
|
||||
|
||||
# Tenant policy
|
||||
d2 = resolver.evaluate("tenant.test", False, {"tenant_id": "acme"})
|
||||
assert d2.value is True
|
||||
assert d2.reason == "tenant_policy"
|
||||
assert d2.scope == "tenant:acme"
|
||||
|
||||
# Default
|
||||
d3 = resolver.evaluate("tenant.test", False, {"tenant_id": "other"})
|
||||
assert d3.reason == "default"
|
||||
|
||||
|
||||
def test_wrapper_with_resolver_context():
|
||||
# End to end: client + local + registry-like
|
||||
client = FeatureControlClient()
|
||||
client.set_provider(LocalProvider({"feat.agent": True}))
|
||||
|
||||
ctx = {"actor_type": "agent", "agent_id": "foo", "tenant_id": "acme"}
|
||||
val = client.get_boolean_value("feat.agent", False, ctx)
|
||||
assert val is True
|
||||
64
tests/test_sdk_wrapper.py
Normal file
64
tests/test_sdk_wrapper.py
Normal file
@@ -0,0 +1,64 @@
|
||||
"""
|
||||
Basic tests for feature-control-sdk wrapper + local provider.
|
||||
|
||||
Run with: pytest tests/test_sdk_wrapper.py
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from feature_control_sdk import FeatureControlClient, LocalProvider
|
||||
|
||||
|
||||
def test_local_provider_boolean():
|
||||
provider = LocalProvider({"test.feature": True})
|
||||
client = FeatureControlClient()
|
||||
client.set_provider(provider)
|
||||
|
||||
assert client.get_boolean_value("test.feature", False) is True
|
||||
assert client.get_boolean_value("missing.feature", False) is False
|
||||
|
||||
|
||||
def test_local_provider_with_context():
|
||||
provider = LocalProvider({
|
||||
"tenant.feature": False,
|
||||
"agent.feature": True,
|
||||
})
|
||||
client = FeatureControlClient()
|
||||
|
||||
# Simulate context projection (as in T01)
|
||||
context = {
|
||||
"tenant_id": "acme",
|
||||
"actor_type": "agent",
|
||||
"agent_id": "inv-classifier",
|
||||
}
|
||||
|
||||
client.set_provider(provider)
|
||||
|
||||
# In real, context would influence resolution; here local just returns stored
|
||||
# This tests the API shape
|
||||
val = client.get_boolean_value("agent.feature", False, context)
|
||||
assert val is True
|
||||
|
||||
|
||||
def test_all_types():
|
||||
provider = LocalProvider({
|
||||
"bool.f": True,
|
||||
"str.f": "hello",
|
||||
"num.f": 42,
|
||||
"obj.f": {"a": 1},
|
||||
})
|
||||
client = FeatureControlClient()
|
||||
client.set_provider(provider)
|
||||
|
||||
assert client.get_boolean_value("bool.f", False) is True
|
||||
assert client.get_string_value("str.f", "x") == "hello"
|
||||
assert client.get_number_value("num.f", 0) == 42
|
||||
assert client.get_object_value("obj.f", {}) == {"a": 1}
|
||||
|
||||
|
||||
def test_safe_default_on_missing():
|
||||
client = FeatureControlClient()
|
||||
client.set_provider(LocalProvider({}))
|
||||
|
||||
assert client.get_boolean_value("no.such", False) is False
|
||||
assert client.get_string_value("no.such", "def") == "def"
|
||||
Reference in New Issue
Block a user