From a508aea894011796855f40bd508e48d38fff7ae2 Mon Sep 17 00:00:00 2001 From: manusjs Date: Mon, 3 Aug 2026 08:11:11 +0000 Subject: [PATCH] test(tools): comprehensive test suites for search_exploit_db, search_packetstorm, and get_cwe_details (+125 tests) --- tests/test_get_cwe_details.py | 475 +++++++++++++++++++++++++++++++ tests/test_search_exploit_db.py | 428 ++++++++++++++++++++++++++++ tests/test_search_packetstorm.py | 440 ++++++++++++++++++++++++++++ 3 files changed, 1343 insertions(+) create mode 100644 tests/test_get_cwe_details.py create mode 100644 tests/test_search_exploit_db.py create mode 100644 tests/test_search_packetstorm.py diff --git a/tests/test_get_cwe_details.py b/tests/test_get_cwe_details.py new file mode 100644 index 0000000..f669e2e --- /dev/null +++ b/tests/test_get_cwe_details.py @@ -0,0 +1,475 @@ +"""Comprehensive test suite for the get_cwe_details tool module. + +Tests cover: +- TOOL_SPEC schema validation +- Input validation (missing/empty/invalid CWE ID, non-digit suffix) +- Successful lookup with description parsed +- Description not found on page +- HTTP errors (timeout, connection error, non-200 status) +- Unexpected exceptions +- HTML parsing edge cases (no Extended_Description marker, no next div) +- HTML tag stripping in description +- URL construction from CWE number +- tool_output_logger integration +""" + +from __future__ import annotations + +from typing import Any +from unittest.mock import MagicMock, patch + +import requests + +from manus_agent.tools.get_cwe_details import TOOL_SPEC, get_cwe_details + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + + +def _make_tool_use(cwe_id: Any = "CWE-79") -> dict: + """Build a minimal ToolUse dict for the get_cwe_details function.""" + return { + "toolUseId": "test-tool-use-id", + "input": {"cwe_id": cwe_id}, + } + + +def _build_html(description: str, has_extended: bool = True) -> str: + """Build a fake CWE page with Description section.""" + extended = '
Extended info
' if has_extended else "" + return ( + "" + f'
{description}
' + f"{extended}" + '
Other content
' + "" + ) + + +# --------------------------------------------------------------------------- +# TOOL_SPEC validation +# --------------------------------------------------------------------------- + + +class TestToolSpec: + def test_spec_has_name(self): + assert TOOL_SPEC["name"] == "get_cwe_details" + + def test_spec_has_description(self): + assert "CWE" in TOOL_SPEC["description"] + assert "MITRE" in TOOL_SPEC["description"] + + def test_spec_has_input_schema(self): + schema = TOOL_SPEC["inputSchema"]["json"] + assert schema["type"] == "object" + assert "cwe_id" in schema["properties"] + assert "cwe_id" in schema["required"] + + def test_spec_cwe_id_is_string(self): + schema = TOOL_SPEC["inputSchema"]["json"] + assert schema["properties"]["cwe_id"]["type"] == "string" + + def test_spec_description_mentions_mitigations(self): + assert "mitigation" in TOOL_SPEC["description"].lower() + + +# --------------------------------------------------------------------------- +# Input validation +# --------------------------------------------------------------------------- + + +class TestInputValidation: + def test_missing_cwe_id_returns_error(self): + tool = {"toolUseId": "tid", "input": {}} + result = get_cwe_details(tool) + assert result["status"] == "error" + assert "Invalid CWE ID" in result["content"][0]["text"] + + def test_none_cwe_id_returns_error(self): + tool = {"toolUseId": "tid", "input": {"cwe_id": None}} + result = get_cwe_details(tool) + assert result["status"] == "error" + + def test_empty_string_returns_error(self): + tool = _make_tool_use("") + result = get_cwe_details(tool) + assert result["status"] == "error" + + def test_numeric_input_returns_error(self): + tool = _make_tool_use(79) + result = get_cwe_details(tool) + assert result["status"] == "error" + + def test_list_input_returns_error(self): + tool = _make_tool_use(["CWE-79"]) + result = get_cwe_details(tool) + assert result["status"] == "error" + + def test_no_cwe_prefix_returns_error(self): + tool = _make_tool_use("79") + result = get_cwe_details(tool) + assert result["status"] == "error" + assert "Invalid CWE ID" in result["content"][0]["text"] + + def test_wrong_prefix_returns_error(self): + tool = _make_tool_use("CVE-79") + result = get_cwe_details(tool) + assert result["status"] == "error" + + def test_non_digit_number_returns_error(self): + tool = _make_tool_use("CWE-abc") + result = get_cwe_details(tool) + assert result["status"] == "error" + assert "Number part" in result["content"][0]["text"] + + def test_cwe_with_trailing_text_returns_error(self): + tool = _make_tool_use("CWE-79abc") + result = get_cwe_details(tool) + assert result["status"] == "error" + + def test_cwe_hyphen_only_returns_error(self): + tool = _make_tool_use("CWE-") + result = get_cwe_details(tool) + assert result["status"] == "error" + + def test_tool_use_id_preserved_on_error(self): + tool = {"toolUseId": "my-unique-id", "input": {"cwe_id": ""}} + result = get_cwe_details(tool) + assert result["toolUseId"] == "my-unique-id" + + def test_case_insensitive_prefix(self): + """CWE- prefix should be case-insensitive (cwe-79 accepted).""" + # The function does .upper() so lowercase should work + with patch("manus_agent.tools.get_cwe_details.requests.get") as mock_get: + html = _build_html("

Cross-site scripting

") + mock_get.return_value = MagicMock(status_code=200, text=html) + mock_get.return_value.raise_for_status = MagicMock() + + result = get_cwe_details(_make_tool_use("cwe-79")) + assert result["status"] == "success" + + +# --------------------------------------------------------------------------- +# Successful lookup +# --------------------------------------------------------------------------- + + +class TestSuccessfulLookup: + @patch("manus_agent.tools.get_cwe_details.requests.get") + def test_basic_description_parsed(self, mock_get): + html = _build_html("

Cross-site scripting vulnerability

") + mock_get.return_value = MagicMock(status_code=200, text=html) + mock_get.return_value.raise_for_status = MagicMock() + + result = get_cwe_details(_make_tool_use("CWE-79")) + assert result["status"] == "success" + payload = result["content"][0]["json"] + assert payload["cwe_id"] == "CWE-79" + assert "Cross-site scripting" in payload["description"] + assert "79.html" in payload["url"] + + @patch("manus_agent.tools.get_cwe_details.requests.get") + def test_html_tags_stripped(self, mock_get): + html = _build_html("

Buffer
overflow

") + mock_get.return_value = MagicMock(status_code=200, text=html) + mock_get.return_value.raise_for_status = MagicMock() + + result = get_cwe_details(_make_tool_use("CWE-120")) + payload = result["content"][0]["json"] + # All basic HTML tags should be stripped + assert "

" not in payload["description"] + assert "
" not in payload["description"] + assert "