pmat 3.11.0

PMAT - Zero-config AI context generation and code quality toolkit (CLI, MCP, HTTP)
    // Cross-Mapper Integration Tests

    #[test]
    fn test_all_mappers_implement_clone_box() {
        // Ensure all mappers can be cloned via clone_box
        let mappers: Vec<Box<dyn LanguageMapper>> = vec![
            JavaMapper::new().clone_box(),
            KotlinMapper::new().clone_box(),
            ScalaMapper::new().clone_box(),
            TypeScriptMapper::new().clone_box(),
            JavaScriptMapper::new().clone_box(),
            CSharpMapper::new().clone_box(),
            RubyMapper::new().clone_box(),
            BaseLanguageMapper::new(Language::Python).clone_box(),
        ];

        let expected_languages = vec![
            Language::Java,
            Language::Kotlin,
            Language::Scala,
            Language::TypeScript,
            Language::JavaScript,
            Language::CSharp,
            Language::Ruby,
            Language::Python,
        ];

        for (mapper, expected_lang) in mappers.iter().zip(expected_languages.iter()) {
            assert_eq!(mapper.language(), *expected_lang);
        }
    }

    #[test]
    fn test_all_mappers_create_test_node() {
        let java_mapper = JavaMapper::new();
        let kotlin_mapper = KotlinMapper::new();
        let scala_mapper = ScalaMapper::new();
        let typescript_mapper = TypeScriptMapper::new();
        let javascript_mapper = JavaScriptMapper::new();

        let node1 = java_mapper.create_test_node(NodeKind::Class, "JavaClass");
        let node2 = kotlin_mapper.create_test_node(NodeKind::Class, "KotlinClass");
        let node3 = scala_mapper.create_test_node(NodeKind::Class, "ScalaClass");
        let node4 = typescript_mapper.create_test_node(NodeKind::Class, "TSClass");
        let node5 = javascript_mapper.create_test_node(NodeKind::Class, "JSClass");

        assert_eq!(node1.language, Language::Java);
        assert_eq!(node2.language, Language::Kotlin);
        assert_eq!(node3.language, Language::Scala);
        assert_eq!(node4.language, Language::TypeScript);
        assert_eq!(node5.language, Language::JavaScript);
    }

    #[tokio::test]
    async fn test_mappers_handle_directory_with_mixed_files() {
        let temp_dir = TempDir::new().unwrap();

        // Create files with various extensions
        fs::write(temp_dir.path().join("Test.java"), "class Test {}").unwrap();
        fs::write(temp_dir.path().join("Main.kt"), "fun main() {}").unwrap();
        fs::write(temp_dir.path().join("App.scala"), "object App").unwrap();
        fs::write(temp_dir.path().join("index.ts"), "const x = 1").unwrap();
        fs::write(temp_dir.path().join("utils.js"), "function f() {}").unwrap();
        fs::write(temp_dir.path().join("readme.txt"), "Documentation").unwrap();

        let java_mapper = JavaMapper::new();
        let kotlin_mapper = KotlinMapper::new();

        // Each mapper should only process files for its language
        let java_result = java_mapper.map_directory(temp_dir.path(), false).await;
        let kotlin_result = kotlin_mapper.map_directory(temp_dir.path(), false).await;

        // Results should be ok (may fail on actual parsing but directory traversal works)
        assert!(java_result.is_ok() || java_result.is_err());
        assert!(kotlin_result.is_ok() || kotlin_result.is_err());
    }

    // Edge Cases and Error Handling Tests

    #[test]
    fn test_convert_ast_items_with_all_item_types() {
        let mapper = BaseLanguageMapper::new(Language::Rust);
        let path = Path::new("/test/lib.rs");

        let items = vec![
            AstItem::Function {
                name: "async_func".to_string(),
                visibility: "pub".to_string(),
                is_async: true,
                line: 1,
            },
            AstItem::Struct {
                name: "MyStruct".to_string(),
                visibility: "pub".to_string(),
                fields_count: 3,
                derives: vec!["Debug".to_string(), "Clone".to_string()],
                line: 10,
            },
            AstItem::Enum {
                name: "Status".to_string(),
                visibility: "pub".to_string(),
                variants_count: 3,
                line: 20,
            },
            AstItem::Trait {
                name: "Handler".to_string(),
                visibility: "pub".to_string(),
                line: 30,
            },
            AstItem::Impl {
                type_name: "MyStruct".to_string(),
                trait_name: Some("Handler".to_string()),
                line: 40,
            },
            AstItem::Use {
                path: "std::io::Result".to_string(),
                line: 50,
            },
            AstItem::Module {
                name: "submodule".to_string(),
                visibility: "pub".to_string(),
                line: 60,
            },
            AstItem::Import {
                module: "external".to_string(),
                items: vec!["Item1".to_string()],
                alias: Some("ext".to_string()),
                line: 70,
            },
        ];

        let nodes = mapper.convert_ast_items(&items, path);

        assert_eq!(nodes.len(), 8);

        // Verify async function has async modifier
        assert!(nodes[0].attributes.contains_key("modifier:async"));

        // Verify struct has derive attributes
        assert!(nodes[1].attributes.contains_key("derive:Debug"));
        assert!(nodes[1].attributes.contains_key("derive:Clone"));
    }

    #[test]
    fn test_factory_create_all_returns_correct_count() {
        let mappers = LanguageMapperFactory::create_all();

        // Should have exactly 5 supported languages
        assert_eq!(mappers.len(), 5);
    }

    #[tokio::test]
    async fn test_mapper_handles_file_without_extension() {
        let temp_dir = TempDir::new().unwrap();
        let file_path = temp_dir.path().join("Makefile");
        fs::write(&file_path, "all: build").unwrap();

        let mapper = JavaMapper::new();
        // Should not crash but may return empty or error
        let result = mapper.map_file(&file_path).await;
        // File exists but wrong type - should still work (validation passes, parsing may fail)
        assert!(result.is_ok() || result.is_err());
    }

    #[test]
    fn test_process_java_specific_empty_nodes() {
        let mapper = JavaMapper::new();
        let mut nodes: Vec<UnifiedNode> = vec![];

        // Should not panic on empty input
        mapper.process_java_specific(&mut nodes);
        assert!(nodes.is_empty());
    }

    #[test]
    fn test_process_kotlin_specific_empty_nodes() {
        let mapper = KotlinMapper::new();
        let mut nodes: Vec<UnifiedNode> = vec![];

        mapper.process_kotlin_specific(&mut nodes);
        assert!(nodes.is_empty());
    }

    #[test]
    fn test_process_scala_specific_empty_nodes() {
        let mapper = ScalaMapper::new();
        let mut nodes: Vec<UnifiedNode> = vec![];

        mapper.process_scala_specific(&mut nodes);
        assert!(nodes.is_empty());
    }

    #[test]
    fn test_process_typescript_specific_empty_nodes() {
        let mapper = TypeScriptMapper::new();
        let mut nodes: Vec<UnifiedNode> = vec![];

        mapper.process_typescript_specific(&mut nodes);
        assert!(nodes.is_empty());
    }

    #[test]
    fn test_process_javascript_specific_empty_nodes() {
        let mapper = JavaScriptMapper::new();
        let mut nodes: Vec<UnifiedNode> = vec![];

        mapper.process_javascript_specific(&mut nodes);
        assert!(nodes.is_empty());
    }