mirror of
https://github.com/ChristianLempa/boilerplates.git
synced 2026-08-28 02:24:12 -05:00
288 lines
9.6 KiB
Python
288 lines
9.6 KiB
Python
"""Unit tests for Variable class."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from cli.core.template.variable import Variable
|
|
|
|
TEST_PORT = 8080
|
|
TEST_COUNT = 42
|
|
TEST_RATE = 3.14
|
|
TEST_SECRET_BYTES = 16
|
|
TEST_SLIDER_MAX = 9
|
|
TEST_SLIDER_STEP = 2
|
|
|
|
|
|
class TestVariableInitialization:
|
|
"""Tests for Variable initialization."""
|
|
|
|
def test_create_simple_variable(self):
|
|
var = Variable({"name": "test_var", "type": "str"})
|
|
assert var.name == "test_var"
|
|
assert var.type == "str"
|
|
assert var.value is None
|
|
|
|
def test_create_variable_with_default(self):
|
|
var = Variable({"name": "port", "type": "int", "default": TEST_PORT})
|
|
assert var.name == "port"
|
|
assert var.value == TEST_PORT
|
|
|
|
def test_create_variable_with_value_takes_precedence_over_default(self):
|
|
var = Variable({"name": "environment", "type": "str", "default": "prod", "value": "stage"})
|
|
assert var.value == "stage"
|
|
|
|
def test_create_bool_variable_without_default(self):
|
|
var = Variable({"name": "enabled", "type": "bool"})
|
|
assert var.value is False
|
|
|
|
def test_create_variable_with_description(self):
|
|
var = Variable({"name": "test", "type": "str", "description": "Test variable"})
|
|
assert var.description == "Test variable"
|
|
|
|
def test_missing_name_raises_error(self):
|
|
with pytest.raises(ValueError, match="must contain 'name' key"):
|
|
Variable({"type": "str"})
|
|
|
|
def test_invalid_data_type_raises_error(self):
|
|
with pytest.raises(ValueError, match="must be a dictionary"):
|
|
Variable("not a dict")
|
|
|
|
|
|
class TestVariableTypes:
|
|
"""Tests for variable type handling."""
|
|
|
|
def test_string_type(self):
|
|
var = Variable({"name": "test", "type": "str", "default": "hello"})
|
|
assert var.value == "hello"
|
|
|
|
def test_int_type(self):
|
|
var = Variable({"name": "count", "type": "int", "default": TEST_COUNT})
|
|
assert var.value == TEST_COUNT
|
|
|
|
def test_bool_type_true(self):
|
|
var = Variable({"name": "enabled", "type": "bool", "default": True})
|
|
assert var.value is True
|
|
|
|
def test_bool_type_false(self):
|
|
var = Variable({"name": "disabled", "type": "bool", "default": False})
|
|
assert var.value is False
|
|
|
|
def test_float_type(self):
|
|
var = Variable({"name": "rate", "type": "float", "default": TEST_RATE})
|
|
assert var.value == TEST_RATE
|
|
|
|
def test_autogenerated_is_rejected_for_non_secret_variables(self):
|
|
with pytest.raises(ValueError, match="only supported for secret variables"):
|
|
Variable({"name": "token", "type": "str", "config": {"autogenerated": True}})
|
|
|
|
|
|
class TestVariableProperties:
|
|
"""Tests for variable properties."""
|
|
|
|
def test_secret_autogenerated_flag_from_config(self):
|
|
var = Variable({"name": "secret", "type": "secret", "config": {"autogenerated": True}})
|
|
assert var.autogenerated is True
|
|
assert var.autogenerated_base64 is False
|
|
|
|
def test_secret_autogenerated_base64_object(self):
|
|
var = Variable(
|
|
{
|
|
"name": "secret",
|
|
"type": "secret",
|
|
"config": {
|
|
"autogenerated": {
|
|
"kind": "base64",
|
|
"bytes": TEST_SECRET_BYTES,
|
|
}
|
|
},
|
|
}
|
|
)
|
|
assert var.autogenerated is True
|
|
assert var.autogenerated_base64 is True
|
|
assert var.autogenerated_config is not None
|
|
assert var.autogenerated_config.bytes == TEST_SECRET_BYTES
|
|
|
|
def test_required_flag(self):
|
|
var = Variable({"name": "hostname", "type": "str", "required": True})
|
|
assert var.required is True
|
|
|
|
def test_options_list(self):
|
|
var = Variable({"name": "mode", "type": "enum", "config": {"options": ["dev", "prod"]}})
|
|
assert var.options == ["dev", "prod"]
|
|
|
|
def test_extra_help_text(self):
|
|
var = Variable({"name": "test", "type": "str", "extra": "Additional info"})
|
|
assert var.extra == "Additional info"
|
|
|
|
def test_string_textarea_and_placeholder_config(self):
|
|
var = Variable(
|
|
{
|
|
"name": "notes",
|
|
"type": "str",
|
|
"config": {
|
|
"textarea": True,
|
|
"placeholder": "Line 1",
|
|
},
|
|
}
|
|
)
|
|
assert var.config.textarea is True
|
|
assert var.config.placeholder == "Line 1"
|
|
|
|
def test_integer_slider_config(self):
|
|
var = Variable(
|
|
{
|
|
"name": "replicas",
|
|
"type": "int",
|
|
"default": 3,
|
|
"config": {
|
|
"slider": True,
|
|
"min": 1,
|
|
"max": TEST_SLIDER_MAX,
|
|
"step": TEST_SLIDER_STEP,
|
|
"unit": "nodes",
|
|
},
|
|
}
|
|
)
|
|
assert var.config.slider is True
|
|
assert var.config.min == 1
|
|
assert var.config.max == TEST_SLIDER_MAX
|
|
assert var.config.step == TEST_SLIDER_STEP
|
|
assert var.config.unit == "nodes"
|
|
|
|
|
|
class TestVariableNeeds:
|
|
"""Tests for variable dependency constraints."""
|
|
|
|
def test_needs_single_string(self):
|
|
var = Variable({"name": "test", "type": "str", "needs": "other_var=value"})
|
|
assert var.needs == ["other_var=value"]
|
|
|
|
def test_needs_semicolon_separated(self):
|
|
var = Variable({"name": "test", "type": "str", "needs": "var1=value1;var2=value2"})
|
|
assert var.needs == ["var1=value1", "var2=value2"]
|
|
|
|
def test_needs_list(self):
|
|
var = Variable({"name": "test", "type": "str", "needs": ["var1=value1", "var2=value2"]})
|
|
assert var.needs == ["var1=value1", "var2=value2"]
|
|
|
|
def test_needs_empty(self):
|
|
var = Variable({"name": "test", "type": "str"})
|
|
assert var.needs == []
|
|
|
|
|
|
class TestVariableConversion:
|
|
"""Tests for variable value conversion."""
|
|
|
|
def test_convert_string_to_int(self):
|
|
var = Variable({"name": "count", "type": "int"})
|
|
result = var.convert("42")
|
|
assert result == TEST_COUNT
|
|
assert isinstance(result, int)
|
|
|
|
def test_convert_string_to_bool_true(self):
|
|
var = Variable({"name": "enabled", "type": "bool"})
|
|
assert var.convert("true") is True
|
|
assert var.convert("1") is True
|
|
assert var.convert("yes") is True
|
|
|
|
def test_convert_string_to_bool_false(self):
|
|
var = Variable({"name": "disabled", "type": "bool"})
|
|
assert var.convert("false") is False
|
|
assert var.convert("0") is False
|
|
assert var.convert("no") is False
|
|
|
|
def test_convert_string_to_float(self):
|
|
var = Variable({"name": "rate", "type": "float"})
|
|
result = var.convert("3.14")
|
|
assert result == TEST_RATE
|
|
assert isinstance(result, float)
|
|
|
|
def test_slider_validation_rejects_out_of_range_value(self):
|
|
var = Variable(
|
|
{
|
|
"name": "replicas",
|
|
"type": "int",
|
|
"config": {"slider": True, "min": 1, "max": 5, "step": 2},
|
|
}
|
|
)
|
|
with pytest.raises(ValueError, match="at most 5"):
|
|
var.validate_and_convert(6)
|
|
|
|
def test_slider_validation_rejects_step_mismatch(self):
|
|
var = Variable(
|
|
{
|
|
"name": "replicas",
|
|
"type": "int",
|
|
"config": {"slider": True, "min": 1, "max": 7, "step": 2},
|
|
}
|
|
)
|
|
with pytest.raises(ValueError, match="align with step 2"):
|
|
var.validate_and_convert(4)
|
|
|
|
def test_slider_default_value_is_validated(self):
|
|
with pytest.raises(ValueError, match="at most 5"):
|
|
Variable(
|
|
{
|
|
"name": "replicas",
|
|
"type": "int",
|
|
"default": 6,
|
|
"config": {"slider": True, "min": 1, "max": 5},
|
|
}
|
|
)
|
|
|
|
def test_autogenerated_secret_cannot_define_default(self):
|
|
with pytest.raises(ValueError, match="autogenerated secrets cannot define defaults"):
|
|
Variable(
|
|
{
|
|
"name": "secret",
|
|
"type": "secret",
|
|
"default": "fixed",
|
|
"config": {"autogenerated": True},
|
|
}
|
|
)
|
|
|
|
def test_base64_secret_cannot_use_length(self):
|
|
with pytest.raises(ValueError, match="use bytes instead of length"):
|
|
Variable(
|
|
{
|
|
"name": "secret",
|
|
"type": "secret",
|
|
"config": {
|
|
"autogenerated": {
|
|
"kind": "base64",
|
|
"length": 16,
|
|
}
|
|
},
|
|
}
|
|
)
|
|
|
|
def test_character_secret_rejects_multi_character_charset_entries(self):
|
|
with pytest.raises(ValueError, match="exactly one character"):
|
|
Variable(
|
|
{
|
|
"name": "secret",
|
|
"type": "secret",
|
|
"config": {
|
|
"autogenerated": {
|
|
"kind": "characters",
|
|
"characters": ["ab"],
|
|
}
|
|
},
|
|
}
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"var_type,default_value,expected",
|
|
[
|
|
("str", "hello", "hello"),
|
|
("int", TEST_COUNT, TEST_COUNT),
|
|
("bool", True, True),
|
|
("float", TEST_RATE, TEST_RATE),
|
|
],
|
|
)
|
|
def test_variable_types_parametrized(var_type, default_value, expected):
|
|
var = Variable({"name": "test", "type": var_type, "default": default_value})
|
|
assert var.value == expected
|