"""Tests for settings schema parser.""" import pytest from alfred.settings_schema import ( SettingDefinition, SettingSource, SettingType, load_schema, validate_value, ) @pytest.fixture def minimal_schema_toml(): """Minimal valid schema TOML.""" return """ [tool.alfred.settings_schema.TEST_STRING] type = "string" source = "env" default = "test_value" description = "Test string setting" category = "test" [tool.alfred.settings_schema.TEST_INTEGER] type = "integer" source = "env" default = 42 description = "Test integer setting" category = "test" validator = "range:1:100" [tool.alfred.settings_schema.TEST_SECRET] type = "secret" source = "generated" secret_rule = "32:b64" description = "Test secret" category = "security" required = true [tool.alfred.settings_schema.TEST_COMPUTED] type = "computed" source = "computed" compute_from = ["TEST_STRING", "TEST_INTEGER"] compute_template = "{TEST_STRING}_{TEST_INTEGER}" description = "Test computed" category = "test" [tool.alfred.settings_schema.TEST_OPTIONAL] type = "string" source = "env" required = false description = "Optional setting" category = "test" """ @pytest.fixture def create_schema_file(tmp_path): """Factory to create pyproject.toml with schema.""" def _create(content: str): toml_path = tmp_path / "pyproject.toml" full_content = f""" [tool.poetry] name = "test" version = "1.0.0" {content} """ toml_path.write_text(full_content) return tmp_path return _create class TestSettingDefinition: """Test SettingDefinition dataclass.""" def test_create_definition(self): """Test creating a setting definition.""" definition = SettingDefinition( name="TEST_SETTING", type=SettingType.STRING, source=SettingSource.ENV, description="Test setting", category="test", default="default_value", ) assert definition.name == "TEST_SETTING" assert definition.type == SettingType.STRING assert definition.source == SettingSource.ENV assert definition.default == "default_value" assert definition.required is True # Default class TestSettingsSchema: """Test SettingsSchema parser.""" def test_parse_schema(self, create_schema_file, minimal_schema_toml): """Test parsing schema from TOML.""" base_dir = create_schema_file(minimal_schema_toml) schema = load_schema(base_dir) assert len(schema) == 5 assert "TEST_STRING" in schema.definitions assert "TEST_INTEGER" in schema.definitions assert "TEST_SECRET" in schema.definitions assert "TEST_COMPUTED" in schema.definitions assert "TEST_OPTIONAL" in schema.definitions def test_get_definition(self, create_schema_file, minimal_schema_toml): """Test getting a definition by name.""" base_dir = create_schema_file(minimal_schema_toml) schema = load_schema(base_dir) definition = schema.get("TEST_STRING") assert definition is not None assert definition.name == "TEST_STRING" assert definition.type == SettingType.STRING assert definition.default == "test_value" def test_get_by_category(self, create_schema_file, minimal_schema_toml): """Test getting definitions by category.""" base_dir = create_schema_file(minimal_schema_toml) schema = load_schema(base_dir) test_settings = schema.get_by_category("test") assert len(test_settings) == 4 security_settings = schema.get_by_category("security") assert len(security_settings) == 1 def test_get_by_source(self, create_schema_file, minimal_schema_toml): """Test getting definitions by source.""" base_dir = create_schema_file(minimal_schema_toml) schema = load_schema(base_dir) env_settings = schema.get_by_source(SettingSource.ENV) assert len(env_settings) == 3 generated_settings = schema.get_by_source(SettingSource.GENERATED) assert len(generated_settings) == 1 computed_settings = schema.get_by_source(SettingSource.COMPUTED) assert len(computed_settings) == 1 def test_get_required(self, create_schema_file, minimal_schema_toml): """Test getting required settings.""" base_dir = create_schema_file(minimal_schema_toml) schema = load_schema(base_dir) required = schema.get_required() # TEST_OPTIONAL is not required assert len(required) == 4 def test_parse_types(self, create_schema_file): """Test parsing different setting types.""" schema_toml = """ [tool.alfred.settings_schema.STR_SETTING] type = "string" source = "env" default = "text" [tool.alfred.settings_schema.INT_SETTING] type = "integer" source = "env" default = 42 [tool.alfred.settings_schema.FLOAT_SETTING] type = "float" source = "env" default = 3.14 [tool.alfred.settings_schema.BOOL_SETTING] type = "boolean" source = "env" default = true """ base_dir = create_schema_file(schema_toml) schema = load_schema(base_dir) assert schema.get("STR_SETTING").default == "text" assert schema.get("INT_SETTING").default == 42 assert schema.get("FLOAT_SETTING").default == 3.14 assert schema.get("BOOL_SETTING").default is True def test_missing_schema_section(self, tmp_path): """Test error when schema section is missing.""" toml_path = tmp_path / "pyproject.toml" toml_path.write_text(""" [tool.poetry] name = "test" version = "1.0.0" """) with pytest.raises(KeyError, match="settings_schema"): load_schema(tmp_path) class TestValidateValue: """Test value validation.""" def test_validate_string(self): """Test validating string values.""" definition = SettingDefinition( name="TEST", type=SettingType.STRING, source=SettingSource.ENV, ) assert validate_value(definition, "test") is True with pytest.raises(ValueError, match="must be string"): validate_value(definition, 123) def test_validate_integer(self): """Test validating integer values.""" definition = SettingDefinition( name="TEST", type=SettingType.INTEGER, source=SettingSource.ENV, ) assert validate_value(definition, 42) is True with pytest.raises(ValueError, match="must be integer"): validate_value(definition, "not an int") def test_validate_float(self): """Test validating float values.""" definition = SettingDefinition( name="TEST", type=SettingType.FLOAT, source=SettingSource.ENV, ) assert validate_value(definition, 3.14) is True assert validate_value(definition, 42) is True # int is ok for float with pytest.raises(ValueError, match="must be float"): validate_value(definition, "not a float") def test_validate_required(self): """Test validating required settings.""" definition = SettingDefinition( name="TEST", type=SettingType.STRING, source=SettingSource.ENV, required=True, ) with pytest.raises(ValueError, match="is required"): validate_value(definition, None) def test_validate_optional(self): """Test validating optional settings.""" definition = SettingDefinition( name="TEST", type=SettingType.STRING, source=SettingSource.ENV, required=False, ) assert validate_value(definition, None) is True def test_validate_range(self): """Test range validator.""" definition = SettingDefinition( name="TEST", type=SettingType.INTEGER, source=SettingSource.ENV, validator="range:1:100", ) assert validate_value(definition, 50) is True assert validate_value(definition, 1) is True assert validate_value(definition, 100) is True with pytest.raises(ValueError, match=r"must be between .* and .*, got"): validate_value(definition, 0) with pytest.raises(ValueError, match=r"must be between .* and .*, got"): validate_value(definition, 101) def test_validate_float_range(self): """Test range validator with floats.""" definition = SettingDefinition( name="TEST", type=SettingType.FLOAT, source=SettingSource.ENV, validator="range:0.0:2.0", ) assert validate_value(definition, 1.5) is True assert validate_value(definition, 0.0) is True assert validate_value(definition, 2.0) is True with pytest.raises(ValueError, match="must be between 0.0 and 2.0"): validate_value(definition, -0.1) with pytest.raises(ValueError, match="must be between 0.0 and 2.0"): validate_value(definition, 2.1) def test_invalid_validator(self): """Test unknown validator raises error.""" definition = SettingDefinition( name="TEST", type=SettingType.STRING, source=SettingSource.ENV, validator="unknown:validator", ) with pytest.raises(ValueError, match="Unknown validator"): validate_value(definition, "test") class TestSchemaIteration: """Test schema iteration.""" def test_iterate_schema(self, create_schema_file, minimal_schema_toml): """Test iterating over schema definitions.""" base_dir = create_schema_file(minimal_schema_toml) schema = load_schema(base_dir) definitions = list(schema) assert len(definitions) == 5 names = [d.name for d in definitions] assert "TEST_STRING" in names assert "TEST_INTEGER" in names