feat: structural schema lint for server definitions (#54)
CI / Lint (ruff) (pull_request) Successful in 6s
CI / Tests (py3.10 / ubuntu-latest) (pull_request) Successful in 8s
CI / Tests (py3.12 / windows-latest) (pull_request) Successful in 19s
CI / Tests (py3.13 / ubuntu-latest) (pull_request) Successful in 9s
CI / Tests (py3.12 / ubuntu-latest) (pull_request) Successful in 8s
CI / Lint (ruff) (pull_request) Successful in 6s
CI / Tests (py3.10 / ubuntu-latest) (pull_request) Successful in 8s
CI / Tests (py3.12 / windows-latest) (pull_request) Successful in 19s
CI / Tests (py3.13 / ubuntu-latest) (pull_request) Successful in 9s
CI / Tests (py3.12 / ubuntu-latest) (pull_request) Successful in 8s
Closes #54 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -181,6 +181,85 @@ def test_valid_set_passes_clean():
|
||||
assert c.validate_servers([c.ServerEntry("good", {"command": "node"}, True)]) == []
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# 4b. Lint: non-blocking structural warnings
|
||||
# --------------------------------------------------------------------------- #
|
||||
def test_lint_flags_non_string_command():
|
||||
warnings = c.lint_server("x", {"command": 5})
|
||||
assert any("'command' should be a string" in w for w in warnings)
|
||||
|
||||
|
||||
def test_lint_flags_args_not_a_list():
|
||||
warnings = c.lint_server("x", {"command": "node", "args": "-y foo"})
|
||||
assert any("'args' should be a list" in w for w in warnings)
|
||||
|
||||
|
||||
def test_lint_flags_args_with_non_string_items():
|
||||
warnings = c.lint_server("x", {"command": "node", "args": ["-y", 5]})
|
||||
assert any("'args' contains non-string values" in w for w in warnings)
|
||||
|
||||
|
||||
def test_lint_flags_env_not_a_dict():
|
||||
warnings = c.lint_server("x", {"command": "node", "env": ["FOO=bar"]})
|
||||
assert any("'env' should be an object" in w for w in warnings)
|
||||
|
||||
|
||||
def test_lint_flags_env_with_non_string_values():
|
||||
warnings = c.lint_server("x", {"command": "node", "env": {"FOO": 5}})
|
||||
assert any("'env' contains non-string values" in w for w in warnings)
|
||||
|
||||
|
||||
def test_lint_flags_headers_not_a_dict():
|
||||
warnings = c.lint_server("x", {"url": "https://x", "headers": ["Authorization: x"]})
|
||||
assert any("'headers' should be an object" in w for w in warnings)
|
||||
|
||||
|
||||
def test_lint_flags_headers_with_non_string_values():
|
||||
warnings = c.lint_server("x", {"url": "https://x", "headers": {"Authorization": 5}})
|
||||
assert any("'headers' contains non-string values" in w for w in warnings)
|
||||
|
||||
|
||||
def test_lint_flags_bad_type_value():
|
||||
warnings = c.lint_server("x", {"url": "https://x", "type": "websocket"})
|
||||
assert any("'type'" in w and "websocket" in w for w in warnings)
|
||||
|
||||
|
||||
def test_lint_flags_extra_unknown_fields():
|
||||
warnings = c.lint_server("x", {"command": "node", "cwd": "/tmp", "timeout": 30})
|
||||
matches = [w for w in warnings if "extra fields preserved as-is" in w]
|
||||
assert len(matches) == 1
|
||||
assert "cwd" in matches[0]
|
||||
assert "timeout" in matches[0]
|
||||
|
||||
|
||||
def test_lint_clean_server_yields_no_warnings():
|
||||
assert c.lint_server("good", {"command": "node", "args": ["a.js"]}) == []
|
||||
assert (
|
||||
c.lint_server(
|
||||
"remote", {"url": "https://x", "type": "http", "headers": {"Authorization": "x"}}
|
||||
)
|
||||
== []
|
||||
)
|
||||
|
||||
|
||||
def test_lint_servers_aggregates_across_entries():
|
||||
entries = [
|
||||
c.ServerEntry("a", {"command": 5}, True),
|
||||
c.ServerEntry("b", {"url": "https://x", "type": "bogus"}, True),
|
||||
]
|
||||
warnings = c.lint_servers(entries)
|
||||
assert any("'a'" in w and "'command' should be a string" in w for w in warnings)
|
||||
assert any("'b'" in w and "'type'" in w for w in warnings)
|
||||
|
||||
|
||||
def test_lint_warning_is_not_a_blocking_problem():
|
||||
# args given as a single string isn't caught by validate_servers (a
|
||||
# command is still present), but it's a lint warning.
|
||||
entry = c.ServerEntry("x", {"command": "node", "args": "-y foo"}, True)
|
||||
assert c.validate_servers([entry]) == []
|
||||
assert c.lint_servers([entry]) != []
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# 5. Secrets: detection + redaction
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
Reference in New Issue
Block a user