8bcf7296d5
REQ-223: mcp/atelier/server.py plugin-registry MCP server (stdio, D-135). NovaAtelierServer wraps MCPServer (SDK v2, D-137) if installed; degrades to _ToolRegistry fallback if SDK absent (testable in CI without SDK). plugins/principles.py (lookup_principle, list_domains, matrix_lookup) + plugins/validation.py (validate_against_principles — agentic validation beyond Wiz/Checkmarx/Mend). 4 tools, 2 plugins. REQ-224: mcp/atelier/vendor/ pinned Atelier v0.3.6 (D-136) — core/ first-principles, domains/security/first-principles, review/agent-checklist, matrix/principles-matrix. vendor/VERSION.md + scripts/update_atelier_vendor.sh for intentional upgrades. mcp/atelier/README.md (tools, architecture, running, vendoring, extensibility, transport). REQ-225: tests/test_atelier_mcp.py — 16 tests, all pass. Covers: plugin discovery (both loaded), 4 tools registered, lookup_security_P4 (+P1, unknown domain/principle), list_domains (19, security-relevant, ui-ux-not), matrix_lookup (security 10 P-rules, unknown), validation (good-passes, bad-secret-fails, bad-swallowed-error-fails, bad-obfuscated-names-fails, result-structure). ---ci--- project: acdl phase: 5 milestone: v1.18 status: execute requirements: covered: [REQ-223, REQ-224, REQ-225] partial: [] ---/ci---
167 lines
6.3 KiB
Python
167 lines
6.3 KiB
Python
"""tests/test_atelier_mcp.py — REQ-225.
|
|
|
|
Covers: tool registration (all 4 tools discoverable), lookup_principle
|
|
returns the principle text + core C-rule, validate_against_principles
|
|
catches a planted C1 (correctness) + C7 (observability) violation in a
|
|
known-bad snippet and passes a known-good snippet, matrix_lookup returns
|
|
the domain→core mapping, plugin discovery loads all plugins in plugins/.
|
|
"""
|
|
import os
|
|
import sys
|
|
import unittest
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
from mcp.atelier.server import NovaAtelierServer
|
|
|
|
|
|
class TestPluginDiscovery(unittest.TestCase):
|
|
def setUp(self):
|
|
self.server = NovaAtelierServer()
|
|
self.loaded = self.server.load_plugins()
|
|
|
|
def test_both_plugins_loaded(self):
|
|
self.assertIn("principles", self.loaded)
|
|
self.assertIn("validation", self.loaded)
|
|
|
|
def test_four_tools_registered(self):
|
|
tools = self.server.list_tools()
|
|
names = {t["name"] for t in tools}
|
|
self.assertIn("atelier_lookup_principle", names)
|
|
self.assertIn("atelier_list_domains", names)
|
|
self.assertIn("atelier_matrix_lookup", names)
|
|
self.assertIn("atelier_validate_against_principles", names)
|
|
self.assertEqual(len(names), 4)
|
|
|
|
|
|
class TestLookupPrinciple(unittest.TestCase):
|
|
def setUp(self):
|
|
self.server = NovaAtelierServer()
|
|
self.server.load_plugins()
|
|
|
|
def test_lookup_security_p4(self):
|
|
result = self.server.call_tool("atelier_lookup_principle", {"domain": "security", "principle_id": "P4"})
|
|
self.assertNotIn("error", result)
|
|
self.assertEqual(result["domain"], "security")
|
|
self.assertEqual(result["principle_id"], "P4")
|
|
self.assertIn("Secrets", result["title"])
|
|
self.assertIn("C1", result["core_c_rule"])
|
|
self.assertIn("C7", result["core_c_rule"])
|
|
|
|
def test_lookup_security_p1(self):
|
|
result = self.server.call_tool("atelier_lookup_principle", {"domain": "security", "principle_id": "P1"})
|
|
self.assertNotIn("error", result)
|
|
self.assertIn("Boundary", result["title"])
|
|
|
|
def test_lookup_unknown_domain(self):
|
|
result = self.server.call_tool("atelier_lookup_principle", {"domain": "nonexistent", "principle_id": "P1"})
|
|
self.assertIn("error", result)
|
|
|
|
def test_lookup_unknown_principle(self):
|
|
result = self.server.call_tool("atelier_lookup_principle", {"domain": "security", "principle_id": "P99"})
|
|
self.assertIn("error", result)
|
|
|
|
|
|
class TestListDomains(unittest.TestCase):
|
|
def setUp(self):
|
|
self.server = NovaAtelierServer()
|
|
self.server.load_plugins()
|
|
|
|
def test_returns_19_domains(self):
|
|
result = self.server.call_tool("atelier_list_domains", {})
|
|
self.assertEqual(len(result), 19)
|
|
|
|
def test_security_is_nova_relevant(self):
|
|
result = self.server.call_tool("atelier_list_domains", {})
|
|
sec = next(d for d in result if d["domain"] == "security")
|
|
self.assertTrue(sec["nova_relevant"])
|
|
|
|
def test_ui_ux_not_nova_relevant(self):
|
|
result = self.server.call_tool("atelier_list_domains", {})
|
|
ui = next(d for d in result if d["domain"] == "ui-ux")
|
|
self.assertFalse(ui["nova_relevant"])
|
|
|
|
|
|
class TestMatrixLookup(unittest.TestCase):
|
|
def setUp(self):
|
|
self.server = NovaAtelierServer()
|
|
self.server.load_plugins()
|
|
|
|
def test_security_matrix(self):
|
|
result = self.server.call_tool("atelier_matrix_lookup", {"domain": "security"})
|
|
self.assertEqual(result["domain"], "security")
|
|
self.assertEqual(len(result["mapping"]), 10)
|
|
p4 = next(m for m in result["mapping"] if m["p"] == "P4")
|
|
self.assertIn("C1", p4["core"])
|
|
self.assertIn("C7", p4["core"])
|
|
|
|
def test_unknown_domain_matrix(self):
|
|
result = self.server.call_tool("atelier_matrix_lookup", {"domain": "nonexistent"})
|
|
self.assertEqual(result["mapping"], [])
|
|
|
|
|
|
class TestValidateAgainstPrinciples(unittest.TestCase):
|
|
def setUp(self):
|
|
self.server = NovaAtelierServer()
|
|
self.server.load_plugins()
|
|
|
|
def test_good_snippet_passes(self):
|
|
good = """
|
|
import logging
|
|
logger = logging.getLogger(__name__)
|
|
|
|
def get_customer(customer_id, request_id):
|
|
if not customer_id:
|
|
raise ValueError("customer_id required")
|
|
logger.info("fetching customer %s (request %s)", customer_id, request_id)
|
|
return db.query(customer_id)
|
|
"""
|
|
result = self.server.call_tool("atelier_validate_against_principles", {"snippet": good})
|
|
# Should not have FAIL on secrets (no hardcoded secrets)
|
|
sec_checks = [r for r in result["results"] if r["check_id"].startswith("SEC.1")]
|
|
for c in sec_checks:
|
|
self.assertEqual(c["status"], "PASS", f"SEC.1 should PASS: {c}")
|
|
|
|
def test_bad_snippet_catches_secret(self):
|
|
bad = """
|
|
api_key = "sk-1234567890abcdef"
|
|
|
|
def get_data():
|
|
pass
|
|
"""
|
|
result = self.server.call_tool("atelier_validate_against_principles", {"snippet": bad})
|
|
# SEC.1 (secrets in code) should FAIL
|
|
sec1 = next(r for r in result["results"] if r["check_id"] == "SEC.1")
|
|
self.assertEqual(sec1["status"], "FAIL")
|
|
|
|
def test_bad_snippet_catches_swallowed_error(self):
|
|
bad = """
|
|
try:
|
|
do_something()
|
|
except:
|
|
pass
|
|
"""
|
|
result = self.server.call_tool("atelier_validate_against_principles", {"snippet": bad})
|
|
# C1.2 (handles failure cases) should FAIL because of `except: pass`
|
|
c12 = next(r for r in result["results"] if r["check_id"] == "C1.2")
|
|
self.assertEqual(c12["status"], "FAIL")
|
|
|
|
def test_bad_snippet_catches_obfuscated_names(self):
|
|
bad = """
|
|
def doStuff(data, temp, x):
|
|
return data + temp + x
|
|
"""
|
|
result = self.server.call_tool("atelier_validate_against_principles", {"snippet": bad})
|
|
# C2.1 (names intent-revealing) should FAIL
|
|
c21 = next(r for r in result["results"] if r["check_id"] == "C2.1")
|
|
self.assertEqual(c21["status"], "FAIL")
|
|
|
|
def test_result_structure(self):
|
|
result = self.server.call_tool("atelier_validate_against_principles", {"snippet": "x = 1"})
|
|
self.assertIn("overall", result)
|
|
self.assertIn("results", result)
|
|
self.assertIsInstance(result["results"], list)
|
|
self.assertGreater(len(result["results"]), 0)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main() |