Files
SnarfCode/tests/unit/test_scan_profile_validation.py
2026-05-22 00:19:30 -04:00

194 lines
7.1 KiB
Python

"""Unit tests for ScanProfile validation logic."""
import pytest
from iac_reverse.models import (
MAX_RESOURCE_TYPE_FILTERS,
PROVIDER_SUPPORTED_RESOURCE_TYPES,
ProviderType,
ScanProfile,
)
class TestScanProfileValidationCredentials:
"""Tests for credentials validation."""
def test_empty_credentials_returns_error(self):
profile = ScanProfile(
provider=ProviderType.KUBERNETES,
credentials={},
)
errors = profile.validate()
assert any("credentials must not be empty" in e for e in errors)
def test_non_empty_credentials_passes(self):
profile = ScanProfile(
provider=ProviderType.KUBERNETES,
credentials={"kubeconfig_path": "/home/user/.kube/config"},
)
errors = profile.validate()
assert errors == []
class TestScanProfileValidationResourceTypeFilters:
"""Tests for resource_type_filters count limit."""
def test_none_filters_passes(self):
profile = ScanProfile(
provider=ProviderType.DOCKER_SWARM,
credentials={"host": "localhost"},
resource_type_filters=None,
)
errors = profile.validate()
assert errors == []
def test_empty_filters_passes(self):
profile = ScanProfile(
provider=ProviderType.DOCKER_SWARM,
credentials={"host": "localhost"},
resource_type_filters=[],
)
errors = profile.validate()
assert errors == []
def test_filters_at_max_limit_passes(self):
# Use valid resource types repeated to reach the limit
valid_types = PROVIDER_SUPPORTED_RESOURCE_TYPES[ProviderType.WINDOWS]
filters = (valid_types * (MAX_RESOURCE_TYPE_FILTERS // len(valid_types) + 1))[
:MAX_RESOURCE_TYPE_FILTERS
]
profile = ScanProfile(
provider=ProviderType.WINDOWS,
credentials={"host": "win01"},
resource_type_filters=filters,
)
errors = profile.validate()
assert errors == []
def test_filters_exceeding_max_limit_returns_error(self):
# Use valid resource types repeated to exceed the limit
valid_types = PROVIDER_SUPPORTED_RESOURCE_TYPES[ProviderType.WINDOWS]
filters = (valid_types * (MAX_RESOURCE_TYPE_FILTERS // len(valid_types) + 1))[
: MAX_RESOURCE_TYPE_FILTERS + 1
]
profile = ScanProfile(
provider=ProviderType.WINDOWS,
credentials={"host": "win01"},
resource_type_filters=filters,
)
errors = profile.validate()
assert any("at most" in e and "200" in e for e in errors)
def test_max_limit_is_200(self):
assert MAX_RESOURCE_TYPE_FILTERS == 200
class TestScanProfileValidationResourceTypeSupport:
"""Tests for resource type validation against provider's supported types."""
def test_valid_resource_types_for_kubernetes(self):
profile = ScanProfile(
provider=ProviderType.KUBERNETES,
credentials={"kubeconfig_path": "/path"},
resource_type_filters=["kubernetes_deployment", "kubernetes_service"],
)
errors = profile.validate()
assert errors == []
def test_valid_resource_types_for_docker_swarm(self):
profile = ScanProfile(
provider=ProviderType.DOCKER_SWARM,
credentials={"host": "localhost"},
resource_type_filters=["docker_service", "docker_network"],
)
errors = profile.validate()
assert errors == []
def test_unsupported_resource_type_returns_error(self):
profile = ScanProfile(
provider=ProviderType.KUBERNETES,
credentials={"kubeconfig_path": "/path"},
resource_type_filters=["kubernetes_deployment", "invalid_type"],
)
errors = profile.validate()
assert any("unsupported resource types" in e for e in errors)
assert any("invalid_type" in e for e in errors)
def test_cross_provider_resource_type_returns_error(self):
"""A resource type valid for one provider is invalid for another."""
profile = ScanProfile(
provider=ProviderType.KUBERNETES,
credentials={"kubeconfig_path": "/path"},
resource_type_filters=["docker_service"],
)
errors = profile.validate()
assert any("unsupported resource types" in e for e in errors)
assert any("docker_service" in e for e in errors)
def test_multiple_unsupported_types_listed_in_error(self):
profile = ScanProfile(
provider=ProviderType.SYNOLOGY,
credentials={"host": "nas01"},
resource_type_filters=["fake_type_a", "fake_type_b"],
)
errors = profile.validate()
assert any("fake_type_a" in e and "fake_type_b" in e for e in errors)
@pytest.mark.parametrize("provider", list(ProviderType))
def test_all_providers_have_supported_types(self, provider):
"""Every provider must have at least one supported resource type."""
assert provider in PROVIDER_SUPPORTED_RESOURCE_TYPES
assert len(PROVIDER_SUPPORTED_RESOURCE_TYPES[provider]) > 0
@pytest.mark.parametrize("provider", list(ProviderType))
def test_all_supported_types_pass_validation(self, provider):
"""All listed supported types for a provider should pass validation."""
supported = PROVIDER_SUPPORTED_RESOURCE_TYPES[provider]
profile = ScanProfile(
provider=provider,
credentials={"key": "value"},
resource_type_filters=supported,
)
errors = profile.validate()
assert errors == []
class TestScanProfileValidationMultipleErrors:
"""Tests that all validation errors are returned in a single response."""
def test_empty_credentials_and_too_many_filters(self):
filters = ["invalid_type"] * (MAX_RESOURCE_TYPE_FILTERS + 1)
profile = ScanProfile(
provider=ProviderType.HARVESTER,
credentials={},
resource_type_filters=filters,
)
errors = profile.validate()
# Should have at least: credentials error, too many filters, unsupported types
assert len(errors) >= 3
assert any("credentials" in e for e in errors)
assert any("at most" in e for e in errors)
assert any("unsupported" in e for e in errors)
def test_empty_credentials_and_unsupported_types(self):
profile = ScanProfile(
provider=ProviderType.BARE_METAL,
credentials={},
resource_type_filters=["nonexistent_type"],
)
errors = profile.validate()
assert len(errors) >= 2
assert any("credentials" in e for e in errors)
assert any("unsupported" in e for e in errors)
def test_no_short_circuit_on_first_error(self):
"""Validation must not stop at the first error found."""
profile = ScanProfile(
provider=ProviderType.WINDOWS,
credentials={},
resource_type_filters=["totally_fake_resource"],
)
errors = profile.validate()
# Both credentials and unsupported type errors should be present
assert len(errors) >= 2