generated from coulomb/repo-seed
117 lines
2.9 KiB
Python
117 lines
2.9 KiB
Python
"""
|
|
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
|