Files
Lotty/src/backend/apps/flags/tests/test_flags.py
T

229 lines
7.6 KiB
Python

from django.core.exceptions import ValidationError
from django.test import TestCase
from apps.decision.services import decide_for_flag
from apps.flags.models import (
FeatureFlagType,
validate_value_for_type,
)
from apps.flags.selectors import feature_flag_get_by_key, feature_flag_list
from apps.flags.services import (
feature_flag_create,
feature_flag_update_default,
)
from config.errors import ConflictError
class ValidateValueForTypeTest(TestCase):
def test_boolean_true_variants(self) -> None:
for val in ("true", "True", "1", "yes", "YES"):
self.assertEqual(
validate_value_for_type(val, FeatureFlagType.BOOLEAN), "true"
)
def test_boolean_false_variants(self) -> None:
for val in ("false", "False", "0", "no", "NO"):
self.assertEqual(
validate_value_for_type(val, FeatureFlagType.BOOLEAN), "false"
)
def test_boolean_invalid(self) -> None:
with self.assertRaises(ValidationError):
validate_value_for_type("maybe", FeatureFlagType.BOOLEAN)
def test_integer_valid(self) -> None:
self.assertEqual(
validate_value_for_type("42", FeatureFlagType.INTEGER), "42"
)
def test_integer_negative(self) -> None:
self.assertEqual(
validate_value_for_type("-7", FeatureFlagType.INTEGER), "-7"
)
def test_integer_invalid(self) -> None:
with self.assertRaises(ValidationError):
validate_value_for_type("abc", FeatureFlagType.INTEGER)
def test_integer_float(self) -> None:
with self.assertRaises(ValidationError):
validate_value_for_type("3.14", FeatureFlagType.INTEGER)
def test_string_passthrough(self) -> None:
self.assertEqual(
validate_value_for_type("anything", FeatureFlagType.STRING),
"anything",
)
class FeatureFlagServiceTest(TestCase):
def test_create_string_flag(self) -> None:
flag = feature_flag_create(
key="svc_string",
name="Service String",
value_type=FeatureFlagType.STRING,
default_value="hello",
)
self.assertEqual(flag.key, "svc_string")
self.assertEqual(flag.value_type, FeatureFlagType.STRING)
self.assertEqual(flag.default_value, "hello")
def test_create_boolean_normalises(self) -> None:
flag = feature_flag_create(
key="svc_bool",
name="Service Bool",
value_type=FeatureFlagType.BOOLEAN,
default_value="Yes",
)
self.assertEqual(flag.default_value, "true")
def test_create_integer_normalises(self) -> None:
flag = feature_flag_create(
key="svc_int",
name="Service Int",
value_type=FeatureFlagType.INTEGER,
default_value="007",
)
self.assertEqual(flag.default_value, "7")
def test_create_invalid_type_raises(self) -> None:
with self.assertRaises(ValidationError):
feature_flag_create(
key="svc_bad",
name="Bad",
value_type="float",
default_value="1.0",
)
def test_create_bool_invalid_value_raises(self) -> None:
with self.assertRaises(ValidationError):
feature_flag_create(
key="svc_bad_bool",
name="Bad Bool",
value_type=FeatureFlagType.BOOLEAN,
default_value="maybe",
)
def test_create_int_invalid_value_raises(self) -> None:
with self.assertRaises(ValidationError):
feature_flag_create(
key="svc_bad_int",
name="Bad Int",
value_type=FeatureFlagType.INTEGER,
default_value="abc",
)
def test_create_duplicate_key_raises(self) -> None:
feature_flag_create(
key="svc_dup",
name="First",
value_type=FeatureFlagType.STRING,
default_value="a",
)
with self.assertRaises(ConflictError):
feature_flag_create(
key="svc_dup",
name="Second",
value_type=FeatureFlagType.STRING,
default_value="b",
)
def test_update_default_value(self) -> None:
flag = feature_flag_create(
key="svc_upd",
name="Update",
value_type=FeatureFlagType.STRING,
default_value="old",
)
updated = feature_flag_update_default(flag=flag, default_value="new")
self.assertEqual(updated.default_value, "new")
flag.refresh_from_db()
self.assertEqual(flag.default_value, "new")
def test_update_bool_validates(self) -> None:
flag = feature_flag_create(
key="svc_upd_bool",
name="Update Bool",
value_type=FeatureFlagType.BOOLEAN,
default_value="true",
)
with self.assertRaises(ValidationError):
feature_flag_update_default(flag=flag, default_value="maybe")
def test_update_int_validates(self) -> None:
flag = feature_flag_create(
key="svc_upd_int",
name="Update Int",
value_type=FeatureFlagType.INTEGER,
default_value="5",
)
with self.assertRaises(ValidationError):
feature_flag_update_default(flag=flag, default_value="bad")
def test_update_default_invalidates_decide_flag_cache(self) -> None:
flag = feature_flag_create(
key="svc_upd_cache",
name="Update Cache",
value_type=FeatureFlagType.STRING,
default_value="old",
)
first = decide_for_flag(flag.key, "svc_subj_1", {})
self.assertEqual(first["value"], "old")
feature_flag_update_default(flag=flag, default_value="new")
second = decide_for_flag(flag.key, "svc_subj_2", {})
self.assertEqual(second["reason"], "no_active_experiment")
self.assertEqual(second["value"], "new")
class FeatureFlagSelectorTest(TestCase):
def test_list_all(self) -> None:
feature_flag_create(
key="sel_a", name="A", value_type="string", default_value="a"
)
feature_flag_create(
key="sel_b", name="B", value_type="boolean", default_value="true"
)
self.assertEqual(feature_flag_list().count(), 2)
def test_list_filter_type(self) -> None:
feature_flag_create(
key="sel_ft_s",
name="S",
value_type="string",
default_value="s",
)
feature_flag_create(
key="sel_ft_b",
name="B",
value_type="boolean",
default_value="true",
)
qs = feature_flag_list(value_type="boolean")
self.assertEqual(qs.count(), 1)
self.assertEqual(qs.first().key, "sel_ft_b")
def test_list_search(self) -> None:
feature_flag_create(
key="sel_search_match",
name="M",
value_type="string",
default_value="m",
)
feature_flag_create(
key="sel_other", name="O", value_type="string", default_value="o"
)
qs = feature_flag_list(search="match")
self.assertEqual(qs.count(), 1)
def test_get_by_key(self) -> None:
feature_flag_create(
key="sel_get", name="G", value_type="string", default_value="g"
)
flag = feature_flag_get_by_key("sel_get")
self.assertIsNotNone(flag)
self.assertEqual(flag.key, "sel_get")
def test_get_by_key_not_found(self) -> None:
flag = feature_flag_get_by_key("nonexistent")
self.assertIsNone(flag)