code-analyze-mcp 0.4.1

MCP server for multi-language code structure analysis
Documentation
// SPDX-FileCopyrightText: 2026 code-analyze-mcp contributors
// SPDX-License-Identifier: Apache-2.0
use code_analyze_mcp::CodeAnalyzer;
use serde_json::Value;

#[test]
fn test_all_tools_have_correct_annotations() {
    let tools = CodeAnalyzer::list_tools();

    assert_eq!(tools.len(), 4, "expected 4 registered tools");

    let expected_names = [
        "analyze_directory",
        "analyze_file",
        "analyze_module",
        "analyze_symbol",
    ];

    for tool in &tools {
        let name = tool.name.as_ref();
        assert!(
            expected_names.contains(&name),
            "unexpected tool name: {}",
            name
        );

        let annotations = tool
            .annotations
            .as_ref()
            .unwrap_or_else(|| panic!("tool {} is missing annotations", name));

        assert_eq!(
            annotations.read_only_hint,
            Some(true),
            "tool {} must have read_only_hint=true",
            name
        );
        assert_eq!(
            annotations.destructive_hint,
            Some(false),
            "tool {} must have destructive_hint=false",
            name
        );
        assert_eq!(
            annotations.idempotent_hint,
            Some(true),
            "tool {} must have idempotent_hint=true",
            name
        );
        assert_eq!(
            annotations.open_world_hint,
            Some(false),
            "tool {} must have open_world_hint=false",
            name
        );
    }
}

#[test]
fn test_all_tools_have_non_empty_descriptions() {
    let tools = CodeAnalyzer::list_tools();
    for tool in &tools {
        let name = tool.name.as_ref();
        let desc = tool.description.as_deref().unwrap_or("");
        assert!(
            !desc.is_empty(),
            "tool '{}' has an empty or missing description",
            name
        );
    }
}

#[test]
fn test_all_tool_parameters_have_descriptions() {
    let tools = CodeAnalyzer::list_tools();
    for tool in &tools {
        let tool_name = tool.name.as_ref();
        let schema = &tool.input_schema;
        let properties = match schema.get("properties").and_then(Value::as_object) {
            Some(p) => p,
            None => continue,
        };
        for (param_name, param_schema) in properties.iter() {
            let desc = param_schema
                .get("description")
                .and_then(Value::as_str)
                .unwrap_or("");
            assert!(
                !desc.is_empty(),
                "tool '{}' parameter '{}' has an empty or missing description in inputSchema",
                tool_name,
                param_name
            );
        }
    }
}

#[test]
fn test_flatten_fields_have_descriptions() {
    let tools = CodeAnalyzer::list_tools();
    let flatten_fields = ["cursor", "page_size", "summary", "force", "verbose"];
    for tool in &tools {
        let tool_name = tool.name.as_ref();
        if tool_name == "analyze_module" {
            continue;
        }
        let properties = match tool
            .input_schema
            .get("properties")
            .and_then(Value::as_object)
        {
            Some(p) => p,
            None => continue,
        };
        for field in &flatten_fields {
            if let Some(param_schema) = properties.get(*field) {
                let desc = param_schema
                    .get("description")
                    .and_then(Value::as_str)
                    .unwrap_or("");
                assert!(
                    !desc.is_empty(),
                    "tool '{}' flattened field '{}' is missing a description in inputSchema",
                    tool_name,
                    field
                );
            }
        }
    }
}

#[test]
fn test_complex_params_have_examples() {
    let tools = CodeAnalyzer::list_tools();
    let checks: &[(&str, &str)] = &[("analyze_file", "fields"), ("analyze_symbol", "match_mode")];
    for (tool_name, param_name) in checks {
        let tool = tools
            .iter()
            .find(|t| t.name.as_ref() == *tool_name)
            .unwrap_or_else(|| panic!("tool '{}' not found", tool_name));
        let properties = tool
            .input_schema
            .get("properties")
            .and_then(Value::as_object)
            .unwrap_or_else(|| panic!("tool '{}' has no properties", tool_name));
        let param = properties
            .get(*param_name)
            .unwrap_or_else(|| panic!("tool '{}' has no parameter '{}'", tool_name, param_name));
        let examples = param.get("examples").and_then(Value::as_array);
        assert!(
            examples.map_or(false, |arr| !arr.is_empty()),
            "tool '{}' parameter '{}' is missing a JSON Schema 'examples' array",
            tool_name,
            param_name
        );
    }
}