Harden M8 preflight foundation
This commit is contained in:
+61
-1
@@ -1,8 +1,18 @@
|
||||
"""Tests for preflight analysis engine."""
|
||||
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import patch
|
||||
|
||||
from cmdforge.preflight import PreflightReport, analyze_tool, _check_config_integrity
|
||||
import pytest
|
||||
|
||||
from cmdforge.preflight import (
|
||||
PreflightReport,
|
||||
analyze_tool,
|
||||
_check_config_integrity,
|
||||
_check_similar_tools,
|
||||
_is_semver,
|
||||
)
|
||||
from cmdforge.registry_client import PaginatedResponse, RegistryError
|
||||
|
||||
|
||||
class TestPreflightReport:
|
||||
@@ -22,6 +32,12 @@ class TestPreflightReport:
|
||||
assert c.warnings == ["w1"]
|
||||
assert c.suggestions == ["s1"]
|
||||
|
||||
def test_to_dict_returns_independent_lists(self):
|
||||
report = PreflightReport(errors=["bad"])
|
||||
serialized = report.to_dict()
|
||||
serialized["errors"].append("another")
|
||||
assert report.errors == ["bad"]
|
||||
|
||||
|
||||
class TestAnalyzeTool:
|
||||
def test_no_steps_warns(self, tmp_path):
|
||||
@@ -59,6 +75,12 @@ class TestAnalyzeTool:
|
||||
_check_config_integrity(tool, report)
|
||||
assert report.ok
|
||||
|
||||
def test_constant_tool_without_steps_is_not_passthrough_warning(self):
|
||||
from cmdforge.tool import Tool
|
||||
|
||||
report = analyze_tool(Tool(name="constant", output="hello"))
|
||||
assert not any("passthrough" in warning for warning in report.warnings)
|
||||
|
||||
def test_secret_pattern_in_prompt(self, tmp_path):
|
||||
from cmdforge.tool import Tool, PromptStep
|
||||
|
||||
@@ -74,3 +96,41 @@ class TestAnalyzeTool:
|
||||
from cmdforge.preflight import _check_secrets
|
||||
_check_secrets(tool, report)
|
||||
assert any("secret" in w.lower() for w in report.warnings)
|
||||
|
||||
@pytest.mark.parametrize("version", ["1.2.3garbage", "1.2.3.4", "1.2"])
|
||||
def test_semver_rejects_trailing_or_incomplete_values(self, version):
|
||||
assert _is_semver(version) is False
|
||||
|
||||
def test_registry_similarity_uses_paginated_data(self):
|
||||
class Client:
|
||||
def search_tools(self, query, per_page):
|
||||
assert (query, per_page) == ("summarize", 5)
|
||||
return PaginatedResponse(data=[{
|
||||
"owner": "official",
|
||||
"name": "summary",
|
||||
"description": "Summarize text",
|
||||
"downloads": 10,
|
||||
}])
|
||||
|
||||
report = PreflightReport()
|
||||
_check_similar_tools(SimpleNamespace(name="summarize"), report, Client())
|
||||
assert report.similar_tools[0]["name"] == "official/summary"
|
||||
|
||||
def test_registry_error_becomes_warning(self):
|
||||
class Client:
|
||||
def search_tools(self, query, per_page):
|
||||
raise RegistryError("CONNECTION_ERROR", "offline")
|
||||
|
||||
report = PreflightReport()
|
||||
_check_similar_tools(SimpleNamespace(name="summarize"), report, Client())
|
||||
assert report.warnings == ["Could not search registry for similar tools"]
|
||||
|
||||
def test_programming_error_is_not_swallowed(self):
|
||||
class Client:
|
||||
def search_tools(self, query, per_page):
|
||||
raise TypeError("broken adapter")
|
||||
|
||||
with pytest.raises(TypeError, match="broken adapter"):
|
||||
_check_similar_tools(
|
||||
SimpleNamespace(name="summarize"), PreflightReport(), Client()
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user