From af15c66fd6da511665d1878e14f26fee3ffa5028 Mon Sep 17 00:00:00 2001 From: Evgeny Kiriyak <224408464+evkir@users.noreply.github.com> Date: Thu, 30 Jul 2026 22:05:25 +0200 Subject: [PATCH] test(mcp): cover the subdomain_enum tool handler The handler was rewired onto enumerate_subdomains without a test, leaving the MCP tool as the one place where the shape returned to a client was unverified. Exercise it through call_tool and assert both the shared result shape and that an explicit wordlist reaches the enumerator. --- tests/unit/test_mcp.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tests/unit/test_mcp.py b/tests/unit/test_mcp.py index 04d487b..d649322 100644 --- a/tests/unit/test_mcp.py +++ b/tests/unit/test_mcp.py @@ -88,3 +88,45 @@ def test_call_tool_result_is_json_text(): assert json.loads(res[0].text) == {"items": [1, 2, 3]} finally: TOOL_REGISTRY.pop("list_test", None) + + +def test_subdomain_enum_tool_returns_the_shared_enumerator_shape(monkeypatch): + """The MCP tool must return what both pipeline paths write under + recon.subdomains. It used to wrap dns_tool.detect_subdomains, which + returned a different key, so an MCP client saw a third shape.""" + from cyberai.mcp import tools as mcp_tools + + enum_result = { + "domain": "t.local", + "found": [{"fqdn": "api.t.local", "ips": ["1.2.3.4"]}], + "count": 1, + "checked": 1, + "wordlist": ["api"], + } + captured = {} + + def fake_enum(target, wordlist=None): + captured["args"] = (target, wordlist) + return enum_result + + monkeypatch.setattr(mcp_tools, "enumerate_subdomains", fake_enum) + + res = asyncio.run(call_tool("subdomain_enum", {"target": "t.local"})) + assert json.loads(res[0].text) == enum_result + assert captured["args"] == ("t.local", None) + + +def test_subdomain_enum_tool_forwards_an_explicit_wordlist(monkeypatch): + """wordlist is optional in the schema but must reach the enumerator.""" + from cyberai.mcp import tools as mcp_tools + + captured = {} + + def fake_enum(target, wordlist=None): + captured["args"] = (target, wordlist) + return {"domain": target, "found": [], "count": 0} + + monkeypatch.setattr(mcp_tools, "enumerate_subdomains", fake_enum) + + asyncio.run(call_tool("subdomain_enum", {"target": "t.local", "wordlist": ["dev"]})) + assert captured["args"] == ("t.local", ["dev"])