We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/kpeacocke/souschef'
If you have feedback or need assistance with the MCP directory API, please join our Discord server
"""Focused tests for assessment.py validation and utility functions."""
from pathlib import Path
from unittest.mock import patch
from souschef.assessment import (
_analyse_cookbook_metrics,
_get_overall_complexity_level,
_normalize_cookbook_root,
_parse_cookbook_paths,
_validate_assessment_inputs,
)
class TestValidateAssessmentInputs:
"""Tests for _validate_assessment_inputs function."""
def test_empty_cookbook_paths(self):
"""Test validation with empty cookbook paths."""
result = _validate_assessment_inputs("", "full", "ansible_core")
assert result is not None
assert "Cookbook paths cannot be empty" in result
def test_whitespace_cookbook_paths(self):
"""Test validation with whitespace-only paths."""
result = _validate_assessment_inputs(" ", "full", "ansible_core")
assert result is not None
assert "Cookbook paths cannot be empty" in result
def test_invalid_migration_scope(self):
"""Test validation with invalid migration scope."""
result = _validate_assessment_inputs("/path", "invalid_scope", "ansible_core")
assert result is not None
assert "Invalid migration scope" in result
assert "full, recipes_only, infrastructure_only" in result
def test_invalid_platform(self):
"""Test validation with invalid target platform."""
result = _validate_assessment_inputs("/path", "full", "invalid_platform")
assert result is not None
assert "Invalid target platform" in result
assert "ansible_awx, ansible_core, ansible_tower" in result
def test_valid_inputs_full(self):
"""Test validation with all valid inputs (full scope)."""
result = _validate_assessment_inputs("/path", "full", "ansible_core")
assert result is None
def test_valid_inputs_recipes_only(self):
"""Test validation with recipes_only scope."""
result = _validate_assessment_inputs("/path", "recipes_only", "ansible_awx")
assert result is None
def test_valid_inputs_infrastructure_only(self):
"""Test validation with infrastructure_only scope."""
result = _validate_assessment_inputs(
"/path", "infrastructure_only", "ansible_tower"
)
assert result is None
class TestGetOverallComplexityLevel:
"""Tests for _get_overall_complexity_level function."""
def test_low_complexity(self):
"""Test low complexity determination."""
metrics = {"avg_complexity": 15}
result = _get_overall_complexity_level(metrics)
assert result == "Low"
def test_medium_complexity(self):
"""Test medium complexity determination."""
metrics = {"avg_complexity": 50}
result = _get_overall_complexity_level(metrics)
assert result == "Medium"
def test_high_complexity(self):
"""Test high complexity determination."""
metrics = {"avg_complexity": 75}
result = _get_overall_complexity_level(metrics)
assert result == "High"
def test_boundary_low_medium(self):
"""Test boundary between low and medium."""
metrics = {"avg_complexity": 30}
result = _get_overall_complexity_level(metrics)
assert result == "Medium" # At boundary, should be Medium
def test_missing_avg_complexity(self):
"""Test with missing avg_complexity key."""
metrics = {}
result = _get_overall_complexity_level(metrics)
assert result == "Low" # Defaults to 0, which is < 30
class TestParseBookPaths:
"""Tests for _parse_cookbook_paths function."""
def test_single_path(self, tmp_path):
"""Test parsing single cookbook path."""
cookbook = tmp_path / "cookbook1"
cookbook.mkdir()
result = _parse_cookbook_paths(str(cookbook))
assert len(result) == 1
assert result[0] == cookbook
def test_multiple_paths(self, tmp_path):
"""Test parsing multiple cookbook paths."""
cookbook1 = tmp_path / "cookbook1"
cookbook2 = tmp_path / "cookbook2"
cookbook1.mkdir()
cookbook2.mkdir()
paths = f"{cookbook1},{cookbook2}"
result = _parse_cookbook_paths(paths)
assert len(result) == 2
assert cookbook1 in result
assert cookbook2 in result
def test_nonexistent_paths_filtered(self, tmp_path):
"""Test that nonexistent paths are filtered out."""
cookbook1 = tmp_path / "cookbook1"
cookbook2 = tmp_path / "cookbook2"
cookbook1.mkdir()
# cookbook2 deliberately not created
paths = f"{cookbook1},{cookbook2}"
result = _parse_cookbook_paths(paths)
assert len(result) == 1
assert cookbook1 in result
def test_whitespace_handling(self, tmp_path):
"""Test that whitespace is properly stripped."""
cookbook = tmp_path / "cookbook1"
cookbook.mkdir()
paths = f" {cookbook} , {cookbook} "
result = _parse_cookbook_paths(paths)
assert len(result) == 2
class TestNormalizeCookbookRoot:
"""Tests for _normalize_cookbook_root function."""
def test_string_path(self):
"""Test normalization of string path."""
result = _normalize_cookbook_root("/path/to/cookbook")
assert isinstance(result, Path)
assert str(result) == "/path/to/cookbook"
def test_path_object(self):
"""Test normalization of Path object."""
path = Path("/path/to/cookbook")
result = _normalize_cookbook_root(path)
assert isinstance(result, Path)
assert result == path
class TestAnalyseCookbookMetrics:
"""Tests for _analyse_cookbook_metrics function."""
def test_empty_paths(self):
"""Test with empty cookbook paths list."""
cookbook_assessments, overall_metrics = _analyse_cookbook_metrics([])
assert len(cookbook_assessments) == 0
assert overall_metrics["total_cookbooks"] == 0
assert overall_metrics["total_recipes"] == 0
@patch("souschef.assessment._assess_single_cookbook")
def test_single_cookbook(self, mock_assess):
"""Test metrics for single cookbook."""
mock_assess.return_value = {
"metrics": {"recipe_count": 5, "resource_count": 10},
"complexity_score": 25,
"estimated_effort_days": 3,
}
paths = [Path("/fake/cookbook")]
cookbook_assessments, overall_metrics = _analyse_cookbook_metrics(paths)
assert len(cookbook_assessments) == 1
assert overall_metrics["total_cookbooks"] == 1
assert overall_metrics["total_recipes"] == 5
assert overall_metrics["total_resources"] == 10
assert overall_metrics["complexity_score"] == 25
assert overall_metrics["estimated_effort_days"] == 3
assert overall_metrics["avg_complexity"] == 25
@patch("souschef.assessment._assess_single_cookbook")
def test_multiple_cookbooks_aggregation(self, mock_assess):
"""Test metrics aggregation for multiple cookbooks."""
mock_assess.side_effect = [
{
"metrics": {"recipe_count": 5, "resource_count": 10},
"complexity_score": 20,
"estimated_effort_days": 2,
},
{
"metrics": {"recipe_count": 10, "resource_count": 20},
"complexity_score": 40,
"estimated_effort_days": 4,
},
]
paths = [Path("/fake/cookbook1"), Path("/fake/cookbook2")]
cookbook_assessments, overall_metrics = _analyse_cookbook_metrics(paths)
assert len(cookbook_assessments) == 2
assert overall_metrics["total_cookbooks"] == 2
assert overall_metrics["total_recipes"] == 15
assert overall_metrics["total_resources"] == 30
assert overall_metrics["complexity_score"] == 60
assert overall_metrics["estimated_effort_days"] == 6
assert overall_metrics["avg_complexity"] == 30 # (20 + 40) / 2
# TestFormatAssessmentReport removed - too complex with many dependencies
# The function is already well-tested through integration tests