#
tokens: 44759/50000 8/79 files (page 2/3)
lines: off (toggle) GitHub
raw markdown copy
This is page 2 of 3. Use http://codebase.md/utensils/mcp-nixos?page={x} to view the full context.

# Directory Structure

```
├── .claude
│   ├── agents
│   │   ├── mcp-server-architect.md
│   │   ├── nix-expert.md
│   │   └── python-expert.md
│   ├── commands
│   │   └── release.md
│   └── settings.json
├── .dockerignore
├── .envrc
├── .github
│   └── workflows
│       ├── ci.yml
│       ├── claude-code-review.yml
│       ├── claude.yml
│       ├── deploy-flakehub.yml
│       ├── deploy-website.yml
│       └── publish.yml
├── .gitignore
├── .mcp.json
├── .pre-commit-config.yaml
├── CLAUDE.md
├── Dockerfile
├── flake.lock
├── flake.nix
├── LICENSE
├── MANIFEST.in
├── mcp_nixos
│   ├── __init__.py
│   └── server.py
├── pyproject.toml
├── pytest.ini
├── README.md
├── RELEASE_NOTES.md
├── RELEASE_WORKFLOW.md
├── smithery.yaml
├── tests
│   ├── __init__.py
│   ├── conftest.py
│   ├── test_channels.py
│   ├── test_edge_cases.py
│   ├── test_evals.py
│   ├── test_flakes.py
│   ├── test_integration.py
│   ├── test_main.py
│   ├── test_mcp_behavior.py
│   ├── test_mcp_tools.py
│   ├── test_nixhub.py
│   ├── test_nixos_stats.py
│   ├── test_options.py
│   ├── test_plain_text_output.py
│   ├── test_real_world_scenarios.py
│   ├── test_regression.py
│   └── test_server.py
├── uv.lock
└── website
    ├── .eslintignore
    ├── .eslintrc.json
    ├── .gitignore
    ├── .prettierignore
    ├── .prettierrc
    ├── .vscode
    │   └── settings.json
    ├── app
    │   ├── about
    │   │   └── page.tsx
    │   ├── docs
    │   │   └── claude.html
    │   ├── globals.css
    │   ├── layout.tsx
    │   ├── page.tsx
    │   ├── test-code-block
    │   │   └── page.tsx
    │   └── usage
    │       └── page.tsx
    ├── components
    │   ├── AnchorHeading.tsx
    │   ├── ClientFooter.tsx
    │   ├── ClientNavbar.tsx
    │   ├── CodeBlock.tsx
    │   ├── CollapsibleSection.tsx
    │   ├── FeatureCard.tsx
    │   ├── Footer.tsx
    │   └── Navbar.tsx
    ├── metadata-checker.html
    ├── netlify.toml
    ├── next.config.js
    ├── package-lock.json
    ├── package.json
    ├── postcss.config.js
    ├── public
    │   ├── favicon
    │   │   ├── android-chrome-192x192.png
    │   │   ├── android-chrome-512x512.png
    │   │   ├── apple-touch-icon.png
    │   │   ├── browserconfig.xml
    │   │   ├── favicon-16x16.png
    │   │   ├── favicon-32x32.png
    │   │   ├── favicon.ico
    │   │   ├── mstile-150x150.png
    │   │   ├── README.md
    │   │   └── site.webmanifest
    │   ├── images
    │   │   ├── .gitkeep
    │   │   ├── attribution.md
    │   │   ├── claude-logo.png
    │   │   ├── JamesBrink.jpeg
    │   │   ├── mcp-nixos.png
    │   │   ├── nixos-snowflake-colour.svg
    │   │   ├── og-image.png
    │   │   ├── sean-callan.png
    │   │   └── utensils-logo.png
    │   ├── robots.txt
    │   └── sitemap.xml
    ├── README.md
    ├── tailwind.config.js
    ├── tsconfig.json
    └── windsurf_deployment.yaml
```

# Files

--------------------------------------------------------------------------------
/tests/test_real_world_scenarios.py:
--------------------------------------------------------------------------------

```python
#!/usr/bin/env python3
"""Real-world scenario tests based on actual MCP tool usage patterns."""

from unittest.mock import Mock, patch

import pytest
from mcp_nixos import server


def get_tool_function(tool_name: str):
    """Get the underlying function from a FastMCP tool."""
    tool = getattr(server, tool_name)
    if hasattr(tool, "fn"):
        return tool.fn
    return tool


# Get the underlying functions for direct use
darwin_options_by_prefix = get_tool_function("darwin_options_by_prefix")
darwin_search = get_tool_function("darwin_search")
home_manager_info = get_tool_function("home_manager_info")
home_manager_options_by_prefix = get_tool_function("home_manager_options_by_prefix")
home_manager_search = get_tool_function("home_manager_search")
home_manager_stats = get_tool_function("home_manager_stats")
nixos_channels = get_tool_function("nixos_channels")
nixos_info = get_tool_function("nixos_info")
nixos_search = get_tool_function("nixos_search")
nixos_stats = get_tool_function("nixos_stats")


class TestRealWorldScenarios:
    """Test scenarios based on real user interactions with the MCP tools."""

    @pytest.mark.asyncio
    async def test_scenario_installing_development_tools(self):
        """User wants to set up a development environment with Git."""
        # Step 1: Search for Git package
        with patch("mcp_nixos.server.es_query") as mock_es:
            mock_es.return_value = [
                {
                    "_source": {
                        "type": "package",
                        "package_pname": "git",
                        "package_pversion": "2.49.0",
                        "package_description": "Distributed version control system",
                    }
                }
            ]

            result = await nixos_search("git")
            assert "git (2.49.0)" in result
            assert "Distributed version control system" in result

        # Step 2: Get package details
        with patch("mcp_nixos.server.es_query") as mock_es:
            mock_es.return_value = [
                {
                    "_source": {
                        "type": "package",
                        "package_pname": "git",
                        "package_pversion": "2.49.0",
                        "package_description": "Distributed version control system",
                        "package_homepage": ["https://git-scm.com/"],
                    }
                }
            ]

            result = await nixos_info("git")
            assert "Package: git" in result
            assert "Homepage: https://git-scm.com/" in result

        # Step 3: Configure Git in Home Manager
        # First, discover available options
        with patch("mcp_nixos.server.parse_html_options") as mock_parse:
            mock_parse.return_value = [
                {"name": "programs.git.enable", "type": "boolean", "description": "Whether to enable Git"},
                {"name": "programs.git.userName", "type": "string", "description": "Default user name"},
                {"name": "programs.git.userEmail", "type": "string", "description": "Default user email"},
            ]

            result = await home_manager_options_by_prefix("programs.git")
            assert "programs.git.enable" in result
            assert "programs.git.userName" in result

        # Step 4: Get specific option details
        with patch("mcp_nixos.server.parse_html_options") as mock_parse:
            mock_parse.return_value = [
                {
                    "name": "programs.git.enable",
                    "type": "boolean",
                    "description": "Whether to enable Git",
                }
            ]

            result = await home_manager_info("programs.git.enable")
            assert "Type: boolean" in result

    @pytest.mark.asyncio
    async def test_scenario_migrating_nixos_channels(self):
        """User wants to understand and migrate between NixOS channels."""
        # Step 1: Check available channels (note: 24.11 removed from version list as EOL)
        with patch("mcp_nixos.server.channel_cache.get_available") as mock_discover:
            mock_discover.return_value = {
                "latest-43-nixos-25.05": "151,698 documents",
                "latest-43-nixos-25.11": "152,000 documents",
                "latest-43-nixos-unstable": "151,798 documents",
            }

            # Mock that we're not using fallback
            from mcp_nixos.server import channel_cache

            channel_cache.using_fallback = False

            result = await nixos_channels()
            assert "stable (current: 25.05)" in result or "stable (current: 25.11)" in result
            assert "25.05" in result or "25.11" in result
            assert "unstable" in result

        # Step 2: Compare package availability across channels
        channels_to_test = ["stable", "25.05", "unstable"]

        for channel in channels_to_test:
            with patch("mcp_nixos.server.get_channels") as mock_get:
                mock_get.return_value = {
                    "stable": "latest-43-nixos-25.05",
                    "25.05": "latest-43-nixos-25.05",
                    "25.11": "latest-43-nixos-25.11",
                    "unstable": "latest-43-nixos-unstable",
                }

                with patch("mcp_nixos.server.es_query") as mock_es:
                    mock_es.return_value = []
                    result = await nixos_search("firefox", channel=channel)
                    # Should work with all valid channels
                    assert "Error" not in result or "Invalid channel" not in result

    @pytest.mark.asyncio
    async def test_scenario_configuring_macos_with_darwin(self):
        """User wants to configure macOS system settings."""
        # Step 1: Search for dock settings
        with patch("mcp_nixos.server.parse_html_options") as mock_parse:
            mock_parse.return_value = [
                {
                    "name": "system.defaults.dock.autohide",
                    "type": "boolean",
                    "description": "Whether to automatically hide the dock",
                }
            ]

            result = await darwin_search("dock autohide")
            assert "system.defaults.dock.autohide" in result

        # Step 2: Browse all dock options
        with patch("mcp_nixos.server.parse_html_options") as mock_parse:
            mock_parse.return_value = [
                {"name": "system.defaults.dock.autohide", "type": "boolean", "description": "Auto-hide dock"},
                {"name": "system.defaults.dock.autohide-delay", "type": "float", "description": "Auto-hide delay"},
                {"name": "system.defaults.dock.orientation", "type": "string", "description": "Dock position"},
                {"name": "system.defaults.dock.show-recents", "type": "boolean", "description": "Show recent apps"},
            ]

            result = await darwin_options_by_prefix("system.defaults.dock")
            assert "system.defaults.dock.autohide" in result
            assert "system.defaults.dock.orientation" in result

    @pytest.mark.asyncio
    async def test_scenario_discovering_program_options(self):
        """User exploring what programs can be configured in Home Manager."""
        # Step 1: Search for shell configuration
        with patch("mcp_nixos.server.parse_html_options") as mock_parse:
            mock_parse.return_value = [
                {"name": "programs.zsh.enable", "type": "boolean", "description": "Whether to enable zsh"},
                {"name": "programs.bash.enable", "type": "boolean", "description": "Whether to enable bash"},
                {"name": "programs.fish.enable", "type": "boolean", "description": "Whether to enable fish"},
            ]

            result = await home_manager_search("shell")
            # At least one shell option should be found
            assert any(shell in result for shell in ["zsh", "bash", "fish"])

        # Step 2: Explore specific shell options
        with patch("mcp_nixos.server.parse_html_options") as mock_parse:
            mock_parse.return_value = [
                {"name": "programs.zsh.enable", "type": "boolean", "description": "Whether to enable zsh"},
                {"name": "programs.zsh.oh-my-zsh.enable", "type": "boolean", "description": "Enable oh-my-zsh"},
                {"name": "programs.zsh.oh-my-zsh.theme", "type": "string", "description": "oh-my-zsh theme"},
                {"name": "programs.zsh.shellAliases", "type": "attribute set", "description": "Shell aliases"},
            ]

            result = await home_manager_options_by_prefix("programs.zsh")
            assert "programs.zsh.oh-my-zsh.enable" in result
            assert "programs.zsh.shellAliases" in result

    @pytest.mark.asyncio
    async def test_scenario_invalid_option_names(self):
        """Test what happens when users provide invalid option names."""
        # Common mistake: using partial names
        test_cases = [
            ("programs.git", "programs.git.enable"),  # Missing .enable
            ("git", "programs.git.enable"),  # Missing programs prefix
            ("system", "system.defaults"),  # Too generic
        ]

        for invalid_name, _ in test_cases:
            with patch("mcp_nixos.server.parse_html_options") as mock_parse:
                mock_parse.return_value = []  # No exact match

                result = await home_manager_info(invalid_name)
                assert "not found" in result.lower()

    @pytest.mark.asyncio
    async def test_scenario_exploring_available_packages_by_type(self):
        """User wants to find packages by category."""
        # Search for different types of packages
        package_types = [
            ("editor", ["neovim", "vim", "emacs"]),
            ("browser", ["firefox", "chromium"]),
            ("terminal", ["alacritty", "kitty", "wezterm"]),
        ]

        for search_term, expected_packages in package_types:
            with patch("mcp_nixos.server.es_query") as mock_es:
                # Return at least one expected package
                mock_es.return_value = [
                    {
                        "_source": {
                            "type": "package",
                            "package_pname": expected_packages[0],
                            "package_pversion": "1.0.0",
                            "package_description": f"A {search_term}",
                        }
                    }
                ]

                result = await nixos_search(search_term)
                assert any(pkg in result for pkg in expected_packages)

    @pytest.mark.asyncio
    async def test_scenario_understanding_option_types(self):
        """User needs to understand different option types in configurations."""
        # Different option types in Home Manager
        option_examples = [
            ("programs.git.enable", "boolean", "true/false value"),
            ("programs.git.userName", "string", "text value"),
            ("home.packages", "list of package", "list of packages"),
            ("programs.git.aliases", "attribute set of string", "key-value pairs"),
            (
                "services.dunst.settings",
                "attribute set of (attribute set of (string or signed integer or boolean))",
                "complex nested structure",
            ),
        ]

        for option_name, type_str, _ in option_examples:
            with patch("mcp_nixos.server.parse_html_options") as mock_parse:
                mock_parse.return_value = [
                    {
                        "name": option_name,
                        "type": type_str,
                        "description": "Test option",
                    }
                ]

                result = await home_manager_info(option_name)
                assert f"Type: {type_str}" in result

    @pytest.mark.asyncio
    async def test_scenario_channel_suggestions_for_typos(self):
        """User makes typos in channel names and needs suggestions."""
        typo_tests = [
            ("stabel", ["stable"]),  # Typo
            ("25.11", ["25.05", "24.11"]),  # Future version
            ("nixos-24.11", ["24.11"]),  # Wrong format
        ]

        for typo, expected_suggestions in typo_tests:
            with patch("mcp_nixos.server.get_channels") as mock_get:
                mock_get.return_value = {
                    "stable": "latest-43-nixos-25.05",
                    "unstable": "latest-43-nixos-unstable",
                    "25.05": "latest-43-nixos-25.05",
                    "24.11": "latest-43-nixos-24.11",
                }

                result = await nixos_search("test", channel=typo)
                assert "Invalid channel" in result
                assert "Available channels:" in result
                # At least one suggestion should be present
                assert any(sug in result for sug in expected_suggestions)

    @pytest.mark.asyncio
    async def test_scenario_performance_with_wildcards(self):
        """User uses wildcards in searches."""
        # NixOS option search with wildcards
        with patch("mcp_nixos.server.es_query") as mock_es:
            mock_es.return_value = [
                {
                    "_source": {
                        "type": "option",
                        "option_name": "services.nginx.enable",
                        "option_type": "boolean",
                        "option_description": "Whether to enable nginx",
                    }
                }
            ]

            # Search for options with wildcards
            result = await nixos_search("*.nginx.*", search_type="options")
            assert "services.nginx.enable" in result

    @pytest.mark.asyncio
    async def test_scenario_stats_usage_patterns(self):
        """User wants to understand the scale of available packages/options."""
        # Get stats for different channels
        with patch("mcp_nixos.server.get_channels") as mock_get:
            mock_get.return_value = {
                "unstable": "latest-43-nixos-unstable",
                "stable": "latest-43-nixos-25.05",
            }

            with patch("requests.post") as mock_post:
                mock_resp = Mock()
                mock_resp.status_code = 200
                mock_resp.json.side_effect = [
                    {"count": 129865},  # packages
                    {"count": 21933},  # options
                ]
                mock_resp.raise_for_status.return_value = None
                mock_post.return_value = mock_resp

                result = await nixos_stats("unstable")
                assert "129,865" in result  # Formatted number
                assert "21,933" in result

        # Stats functions now return actual statistics
        with patch("mcp_nixos.server.parse_html_options") as mock_parse:
            # Mock parsed options
            mock_parse.return_value = [
                {"name": "programs.git.enable", "type": "boolean", "description": "Enable git"},
                {"name": "programs.zsh.enable", "type": "boolean", "description": "Enable zsh"},
                {"name": "services.gpg-agent.enable", "type": "boolean", "description": "Enable GPG agent"},
                {"name": "home.packages", "type": "list", "description": "Packages to install"},
                {"name": "wayland.windowManager.sway.enable", "type": "boolean", "description": "Enable Sway"},
                {"name": "xsession.enable", "type": "boolean", "description": "Enable X session"},
            ]

            result = await home_manager_stats()
            assert "Home Manager Statistics:" in result
            assert "Total options:" in result
            assert "Categories:" in result
            assert "Top categories:" in result

```

--------------------------------------------------------------------------------
/tests/test_regression.py:
--------------------------------------------------------------------------------

```python
#!/usr/bin/env python3
"""Comprehensive tests for the fixes to issues found in Claude Desktop testing."""

from unittest.mock import MagicMock, patch

import pytest
from mcp_nixos import server


def get_tool_function(tool_name: str):
    """Get the underlying function from a FastMCP tool."""
    tool = getattr(server, tool_name)
    if hasattr(tool, "fn"):
        return tool.fn
    return tool


# Get the underlying functions for direct use
darwin_stats = get_tool_function("darwin_stats")
home_manager_stats = get_tool_function("home_manager_stats")
nixos_flakes_search = get_tool_function("nixos_flakes_search")


class TestFlakeSearchDeduplication:
    """Test that flake search properly deduplicates results."""

    @patch("mcp_nixos.server.requests.post")
    @pytest.mark.asyncio
    async def test_flake_search_deduplicates_packages(self, mock_post):
        """Test that multiple packages from same flake are grouped."""
        # Mock response with duplicate flakes (different packages)
        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "hits": {
                "hits": [
                    {
                        "_source": {
                            "flake_name": "home-manager",
                            "flake_description": "Home Manager for Nix",
                            "flake_resolved": {
                                "owner": "nix-community",
                                "repo": "home-manager",
                                "type": "github",
                            },
                            "package_attr_name": "default",
                        }
                    },
                    {
                        "_source": {
                            "flake_name": "home-manager",
                            "flake_description": "Home Manager for Nix",
                            "flake_resolved": {
                                "owner": "nix-community",
                                "repo": "home-manager",
                                "type": "github",
                            },
                            "package_attr_name": "docs-json",
                        }
                    },
                    {
                        "_source": {
                            "flake_name": "home-manager",
                            "flake_description": "Home Manager for Nix",
                            "flake_resolved": {
                                "owner": "nix-community",
                                "repo": "home-manager",
                                "type": "github",
                            },
                            "package_attr_name": "docs-html",
                        }
                    },
                ]
            }
        }
        mock_post.return_value = mock_response

        result = await nixos_flakes_search("home-manager", limit=10)

        # Should only show 1 unique flake
        assert "Found 1 unique flakes matching 'home-manager':" in result
        assert result.count("• home-manager") == 1
        # Should show all packages together
        assert "Packages: default, docs-html, docs-json" in result

    @patch("mcp_nixos.server.requests.post")
    @pytest.mark.asyncio
    async def test_flake_search_handles_many_packages(self, mock_post):
        """Test that flakes with many packages are handled properly."""
        # Create a flake with 10 packages
        hits = []
        for i in range(10):
            hits.append(
                {
                    "_source": {
                        "flake_name": "multi-package-flake",
                        "flake_description": "A flake with many packages",
                        "flake_resolved": {
                            "owner": "test",
                            "repo": "multi-flake",
                            "type": "github",
                        },
                        "package_attr_name": f"package{i}",
                    }
                }
            )

        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = {"hits": {"hits": hits}}
        mock_post.return_value = mock_response

        result = await nixos_flakes_search("multi-package", limit=20)

        # Should show only first 5 packages with total count
        assert "Found 1 unique flakes matching 'multi-package':" in result
        assert "Packages: package0, package1, package2, package3, package4, ... (10 total)" in result

    @patch("mcp_nixos.server.requests.post")
    @pytest.mark.asyncio
    async def test_flake_search_handles_mixed_flakes(self, mock_post):
        """Test deduplication with multiple different flakes."""
        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "hits": {
                "hits": [
                    # home-manager with 2 packages
                    {
                        "_source": {
                            "flake_name": "home-manager",
                            "flake_description": "Home Manager for Nix",
                            "flake_resolved": {
                                "owner": "nix-community",
                                "repo": "home-manager",
                                "type": "github",
                            },
                            "package_attr_name": "default",
                        }
                    },
                    {
                        "_source": {
                            "flake_name": "home-manager",
                            "flake_description": "Home Manager for Nix",
                            "flake_resolved": {
                                "owner": "nix-community",
                                "repo": "home-manager",
                                "type": "github",
                            },
                            "package_attr_name": "docs-json",
                        }
                    },
                    # nixpkgs with 1 package
                    {
                        "_source": {
                            "flake_name": "nixpkgs",
                            "flake_description": "Nix Packages collection",
                            "flake_resolved": {
                                "owner": "NixOS",
                                "repo": "nixpkgs",
                                "type": "github",
                            },
                            "package_attr_name": "hello",
                        }
                    },
                ]
            }
        }
        mock_post.return_value = mock_response

        result = await nixos_flakes_search("test", limit=10)

        # Should show 2 unique flakes
        assert "Found 2 unique flakes matching 'test':" in result
        assert result.count("• home-manager") == 1
        assert result.count("• nixpkgs") == 1
        # home-manager should show 2 packages
        assert "default, docs-json" in result
        # nixpkgs should show 1 package
        assert "hello" in result


class TestHomeManagerStats:
    """Test improved home_manager_stats functionality."""

    @patch("mcp_nixos.server.parse_html_options")
    @pytest.mark.asyncio
    async def test_home_manager_stats_returns_statistics(self, mock_parse):
        """Test that home_manager_stats returns actual statistics."""
        # Mock parsed options
        mock_parse.return_value = [
            {"name": "programs.git.enable", "type": "boolean", "description": "Enable git"},
            {"name": "programs.vim.enable", "type": "boolean", "description": "Enable vim"},
            {"name": "services.dunst.enable", "type": "boolean", "description": "Enable dunst"},
            {"name": "home.username", "type": "string", "description": "Username"},
            {"name": "home.packages", "type": "list of packages", "description": "Packages"},
            {"name": "wayland.enable", "type": "null or boolean", "description": "Enable wayland"},
        ]

        result = await home_manager_stats()

        # Should return statistics, not redirect message
        assert "Home Manager Statistics:" in result
        assert "Total options: 6" in result
        assert "Categories: 4" in result
        assert "programs: 2 options" in result
        assert "services: 1 options" in result
        assert "home: 2 options" in result
        assert "wayland: 1 options" in result

        # Should not contain the old redirect message
        assert "require parsing the full documentation" not in result
        assert "Use home_manager_list_options" not in result

    @patch("mcp_nixos.server.parse_html_options")
    @pytest.mark.asyncio
    async def test_home_manager_stats_handles_errors(self, mock_parse):
        """Test that home_manager_stats handles errors gracefully."""
        mock_parse.side_effect = Exception("Network error")

        result = await home_manager_stats()

        assert "Error (ERROR): Network error" in result

    @patch("mcp_nixos.server.parse_html_options")
    @pytest.mark.asyncio
    async def test_home_manager_stats_handles_no_options(self, mock_parse):
        """Test that home_manager_stats handles empty results."""
        mock_parse.return_value = []

        result = await home_manager_stats()

        assert "Error (ERROR): Failed to fetch Home Manager statistics" in result


class TestDarwinStats:
    """Test improved darwin_stats functionality."""

    @patch("mcp_nixos.server.parse_html_options")
    @pytest.mark.asyncio
    async def test_darwin_stats_returns_statistics(self, mock_parse):
        """Test that darwin_stats returns actual statistics."""
        # Mock parsed options
        mock_parse.return_value = [
            {"name": "system.defaults.dock.autohide", "type": "boolean", "description": "Auto-hide dock"},
            {
                "name": "system.defaults.NSGlobalDomain.AppleShowAllFiles",
                "type": "boolean",
                "description": "Show all files",
            },
            {"name": "services.nix-daemon.enable", "type": "boolean", "description": "Enable nix-daemon"},
            {"name": "programs.zsh.enable", "type": "boolean", "description": "Enable zsh"},
            {"name": "homebrew.enable", "type": "boolean", "description": "Enable homebrew"},
            {"name": "launchd.agents.test", "type": "attribute set", "description": "Test agent"},
        ]

        result = await darwin_stats()

        # Should return statistics, not redirect message
        assert "nix-darwin Statistics:" in result
        assert "Total options: 6" in result
        assert "Categories: 5" in result
        assert "services: 1 options" in result
        assert "system: 2 options" in result
        assert "programs: 1 options" in result
        assert "homebrew: 1 options" in result
        assert "launchd: 1 options" in result

        # Should not contain the old redirect message
        assert "require parsing the full documentation" not in result
        assert "Use darwin_list_options" not in result

    @patch("mcp_nixos.server.parse_html_options")
    @pytest.mark.asyncio
    async def test_darwin_stats_handles_errors(self, mock_parse):
        """Test that darwin_stats handles errors gracefully."""
        mock_parse.side_effect = Exception("Connection timeout")

        result = await darwin_stats()

        assert "Error (ERROR): Connection timeout" in result

    @patch("mcp_nixos.server.parse_html_options")
    @pytest.mark.asyncio
    async def test_darwin_stats_handles_no_options(self, mock_parse):
        """Test that darwin_stats handles empty results."""
        mock_parse.return_value = []

        result = await darwin_stats()

        assert "Error (ERROR): Failed to fetch nix-darwin statistics" in result


class TestIntegration:
    """Integration tests for all fixes."""

    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_flake_search_real_deduplication(self):
        """Test flake deduplication against real API."""
        result = await nixos_flakes_search("home-manager", limit=20)

        # Count how many times "• home-manager" appears
        # Should be 1 after deduplication
        home_manager_count = result.count("• home-manager")
        assert home_manager_count <= 1, f"home-manager appears {home_manager_count} times, should be deduplicated"

        # If found, should show packages
        if "• home-manager" in result:
            assert "Repository: nix-community/home-manager" in result
            assert "Packages:" in result or "Package:" in result

    @pytest.mark.integration
    @pytest.mark.slow
    @pytest.mark.asyncio
    async def test_home_manager_stats_real_data(self):
        """Test home_manager_stats with real data."""
        result = await home_manager_stats()

        # Should return real statistics
        assert "Home Manager Statistics:" in result
        assert "Total options:" in result
        assert "Categories:" in result
        assert "programs:" in result
        assert "services:" in result

        # Should have reasonable numbers
        assert "Total options: 0" not in result  # Should have some options
        assert "Categories: 0" not in result  # Should have some categories

    @pytest.mark.integration
    @pytest.mark.slow
    @pytest.mark.asyncio
    async def test_darwin_stats_real_data(self):
        """Test darwin_stats with real data."""
        result = await darwin_stats()

        # Should return real statistics
        assert "nix-darwin Statistics:" in result
        assert "Total options:" in result
        assert "Categories:" in result
        assert "services:" in result
        assert "system:" in result

        # Should have reasonable numbers
        assert "Total options: 0" not in result  # Should have some options
        assert "Categories: 0" not in result  # Should have some categories


# Quick smoke test
if __name__ == "__main__":
    print("Running comprehensive tests for fixes...")

    # Test flake deduplication
    test = TestFlakeSearchDeduplication()
    with patch("requests.post") as mock_post:
        # Set up mock response
        mock_post.return_value.status_code = 200
        mock_post.return_value.json.return_value = {
            "hits": {
                "hits": [
                    {"_source": {"flake_resolved": {"url": "github:user/repo1"}, "package_pname": "pkg1"}},
                    {"_source": {"flake_resolved": {"url": "github:user/repo1"}, "package_pname": "pkg2"}},
                    {"_source": {"flake_resolved": {"url": "github:user/repo2"}, "package_pname": "pkg3"}},
                ]
            }
        }
        test.test_flake_search_deduplicates_packages(mock_post)
    print("✓ Flake deduplication test passed")

    # Test stats improvements
    test_hm = TestHomeManagerStats()
    with patch("mcp_nixos.server.parse_html_options") as mock_parse:
        # Set up mock response
        mock_parse.return_value = [
            {"name": "programs.git.enable", "type": "boolean"},
            {"name": "programs.neovim.enable", "type": "boolean"},
            {"name": "services.gpg-agent.enable", "type": "boolean"},
        ]
        test_hm.test_home_manager_stats_returns_statistics(mock_parse)
    print("✓ Home Manager stats test passed")

    test_darwin = TestDarwinStats()
    with patch("mcp_nixos.server.parse_html_options") as mock_parse:
        # Set up mock response
        mock_parse.return_value = [
            {"name": "system.defaults.dock.autohide", "type": "boolean"},
            {"name": "services.nix-daemon.enable", "type": "boolean"},
        ]
        test_darwin.test_darwin_stats_returns_statistics(mock_parse)
    print("✓ Darwin stats test passed")

    print("\nAll tests passed!")

```

--------------------------------------------------------------------------------
/tests/test_integration.py:
--------------------------------------------------------------------------------

```python
"""Real integration tests that verify actual API responses."""

import pytest
from mcp_nixos import server


def get_tool_function(tool_name: str):
    """Get the underlying function from a FastMCP tool."""
    tool = getattr(server, tool_name)
    if hasattr(tool, "fn"):
        return tool.fn
    return tool


# Get the underlying functions for direct use
darwin_info = get_tool_function("darwin_info")
darwin_options_by_prefix = get_tool_function("darwin_options_by_prefix")
darwin_search = get_tool_function("darwin_search")
home_manager_info = get_tool_function("home_manager_info")
home_manager_list_options = get_tool_function("home_manager_list_options")
home_manager_search = get_tool_function("home_manager_search")
home_manager_options_by_prefix = get_tool_function("home_manager_options_by_prefix")
nixos_info = get_tool_function("nixos_info")
nixos_search = get_tool_function("nixos_search")
nixos_stats = get_tool_function("nixos_stats")


@pytest.mark.integration
class TestRealIntegration:
    """Test against real APIs to ensure implementation works."""

    @pytest.mark.asyncio
    async def test_nixos_search_real(self):
        """Test real NixOS package search."""
        result = await nixos_search("firefox", search_type="packages", limit=3)
        assert "Found" in result
        assert "firefox" in result
        assert "•" in result  # Bullet point
        assert "(" in result  # Version in parentheses
        assert "<" not in result  # No XML

    @pytest.mark.asyncio
    async def test_nixos_info_real(self):
        """Test real NixOS package info."""
        result = await nixos_info("firefox", type="package")
        assert "Package: firefox" in result
        assert "Version:" in result
        assert "Description:" in result
        assert "<" not in result  # No XML

    @pytest.mark.asyncio
    async def test_nixos_option_search_real(self):
        """Test real NixOS option search."""
        result = await nixos_search("nginx", search_type="options", limit=3)
        # Should find nginx options (now using wildcard, may find options with nginx anywhere)
        assert "nginx" in result.lower() or "No options found" in result
        assert "<" not in result  # No XML

    @pytest.mark.asyncio
    async def test_nixos_option_info_real(self):
        """Test real NixOS option info."""
        # Test with a common option that should exist
        result = await nixos_info("services.nginx.enable", type="option")
        if "NOT_FOUND" not in result:
            assert "Option: services.nginx.enable" in result
            assert "Type:" in result
            assert "<" not in result  # No XML
        else:
            # If not found, try another common option
            result = await nixos_info("boot.loader.grub.enable", type="option")
            if "NOT_FOUND" not in result:
                assert "Option: boot.loader.grub.enable" in result

    @pytest.mark.asyncio
    async def test_nixos_stats_real(self):
        """Test real NixOS stats."""
        result = await nixos_stats()
        assert "NixOS Statistics" in result
        assert "Packages:" in result
        assert "Options:" in result
        assert "<" not in result  # No XML

    @pytest.mark.asyncio
    async def test_home_manager_search_real(self):
        """Test real Home Manager search."""
        result = await home_manager_search("git", limit=3)
        # Should find git-related options
        assert "git" in result.lower() or "No Home Manager options found" in result
        assert "<" not in result  # No XML

    @pytest.mark.asyncio
    async def test_home_manager_info_real(self):
        """Test real Home Manager info."""
        result = await home_manager_info("programs.git.enable")
        assert "Option: programs.git.enable" in result or "not found" in result
        assert "<" not in result  # No XML

    @pytest.mark.asyncio
    async def test_darwin_search_real(self):
        """Test real Darwin search."""
        result = await darwin_search("dock", limit=3)
        # Should find dock-related options
        assert "dock" in result.lower() or "No nix-darwin options found" in result
        # Allow <name> as it's a placeholder, not XML
        if "<" in result:
            assert "<name>" in result  # This is OK, it's a placeholder
            assert "</" not in result  # No closing XML tags

    @pytest.mark.asyncio
    async def test_plain_text_format_consistency(self):
        """Ensure all outputs follow consistent plain text format."""
        # Test various searches
        results = [
            await nixos_search("python", search_type="packages", limit=2),
            await home_manager_search("shell", limit=2),
            await darwin_search("system", limit=2),
        ]

        for result in results:
            # Check for common plain text patterns
            if "Found" in result:
                assert ":" in result  # Colon after "Found X matching"
                assert "•" in result  # Bullet points for items
            elif "No" in result:
                assert "found" in result  # "No X found"

            # Ensure no XML tags
            assert "<" not in result
            assert ">" not in result

    @pytest.mark.asyncio
    async def test_error_handling_plain_text(self):
        """Test error messages are plain text."""
        # Test with invalid type
        result = await nixos_search("test", search_type="invalid")
        assert "Error" in result
        assert "<" not in result

        # Test with invalid channel
        result = await nixos_search("test", channel="invalid")
        assert "Error" in result
        assert "Invalid channel" in result
        assert "<" not in result


# ===== Content from test_advanced_integration.py =====
@pytest.mark.integration
class TestAdvancedIntegration:
    """Test advanced scenarios with real APIs."""

    @pytest.mark.asyncio
    async def test_nixos_search_special_characters(self):
        """Test searching with special characters and symbols."""
        # Test with hyphens
        result = await nixos_search("ruby-build", search_type="packages")
        assert "ruby-build" in result or "No packages found" in result

        # Test with dots
        result = await nixos_search("lib.so", search_type="packages")
        # Should handle dots in search gracefully
        assert "Error" not in result

        # Test with underscores
        result = await nixos_search("python3_12", search_type="packages")
        assert "Error" not in result

    @pytest.mark.asyncio
    async def test_nixos_search_case_sensitivity(self):
        """Test case sensitivity in searches."""
        # Search with different cases
        result_lower = await nixos_search("firefox", search_type="packages", limit=5)
        result_upper = await nixos_search("FIREFOX", search_type="packages", limit=5)
        result_mixed = await nixos_search("FireFox", search_type="packages", limit=5)

        # All should find firefox (case-insensitive search)
        assert "firefox" in result_lower.lower()
        assert "firefox" in result_upper.lower()
        assert "firefox" in result_mixed.lower()

    @pytest.mark.asyncio
    async def test_nixos_option_hierarchical_search(self):
        """Test searching hierarchical option names."""
        # Search for nested options
        result = await nixos_search("systemd.services", search_type="options", limit=10)
        assert "systemd.services" in result or "No options found" in result

        # Search for deeply nested options
        result = await nixos_search("networking.firewall.allowedTCPPorts", search_type="options", limit=5)
        # Should handle long option names
        assert "Error" not in result

    @pytest.mark.asyncio
    async def test_nixos_cross_channel_consistency(self):
        """Test that different channels return consistent data structure."""
        channels = ["unstable", "stable"]

        for channel in channels:
            # Stats should work for all channels
            stats = await nixos_stats(channel=channel)
            assert "Packages:" in stats
            assert "Options:" in stats
            assert "Error" not in stats

            # Search should return same structure
            search = await nixos_search("git", search_type="packages", channel=channel, limit=3)
            if "Found" in search:
                assert "•" in search  # Bullet points
                assert "(" in search  # Version in parentheses

    @pytest.mark.asyncio
    async def test_nixos_info_edge_packages(self):
        """Test info retrieval for packages with unusual names."""
        # Test package with version in name
        edge_packages = [
            "python3",  # Common package
            "gcc",  # Short name
            "gnome.nautilus",  # Namespaced package
        ]

        for pkg in edge_packages:
            result = await nixos_info(pkg, type="package")
            if "not found" not in result:
                assert "Package:" in result
                assert "Version:" in result

    @pytest.mark.asyncio
    async def test_home_manager_search_complex_queries(self):
        """Test complex search patterns in Home Manager."""
        # Search for options with dots
        result = await home_manager_search("programs.git.delta", limit=10)
        if "Found" in result:
            assert "programs.git.delta" in result

        # Search for options with underscores
        result = await home_manager_search("enable_", limit=10)
        # Should handle underscore in search
        assert "Error" not in result

        # Search for very short terms
        result = await home_manager_search("qt", limit=5)
        assert "Error" not in result

    @pytest.mark.asyncio
    async def test_home_manager_category_completeness(self):
        """Test that list_options returns all major categories."""
        result = await home_manager_list_options()

        # Check for expected major categories
        expected_categories = ["programs", "services", "home", "xdg"]
        for category in expected_categories:
            assert category in result

        # Verify format consistency
        assert "total)" in result
        assert "• " in result
        assert " options)" in result

    @pytest.mark.asyncio
    async def test_home_manager_prefix_navigation(self):
        """Test navigating option hierarchy with prefixes."""
        # Start with top-level
        result = await home_manager_options_by_prefix("programs")
        if "Found" not in result and "found)" in result:
            # Drill down to specific program
            result_git = await home_manager_options_by_prefix("programs.git")
            if "found)" in result_git:
                assert "programs.git" in result_git

                # Drill down further
                result_delta = await home_manager_options_by_prefix("programs.git.delta")
                assert "Error" not in result_delta

    @pytest.mark.asyncio
    async def test_home_manager_info_name_variants(self):
        """Test info retrieval with different name formats."""
        # Test with placeholder names
        result = await home_manager_info("programs.firefox.profiles.<name>.settings")
        # Should handle <name> placeholders
        if "not found" not in result:
            assert "Option:" in result

    @pytest.mark.asyncio
    async def test_darwin_search_macos_specific(self):
        """Test searching macOS-specific options."""
        # Search for macOS-specific terms
        macos_terms = ["homebrew", "launchd", "defaults", "dock"]

        for term in macos_terms:
            result = await darwin_search(term, limit=5)
            if "Found" in result:
                assert term in result.lower()
                assert "•" in result

    @pytest.mark.asyncio
    async def test_darwin_system_defaults_exploration(self):
        """Test exploring system.defaults hierarchy."""
        # List all system.defaults options
        result = await darwin_options_by_prefix("system.defaults")

        if "found)" in result:
            # Should have many system defaults
            assert "system.defaults" in result

            # Test specific subcategories
            subcategories = ["NSGlobalDomain", "dock", "finder"]
            for subcat in subcategories:
                sub_result = await darwin_options_by_prefix(f"system.defaults.{subcat}")
                # Should not error even if no results
                assert "Error" not in sub_result

    @pytest.mark.asyncio
    async def test_darwin_info_detailed_options(self):
        """Test retrieving detailed darwin option info."""
        # Test well-known options
        known_options = ["system.defaults.dock.autohide", "environment.systemPath", "programs.zsh.enable"]

        for opt in known_options:
            result = await darwin_info(opt)
            if "not found" not in result:
                assert "Option:" in result
                # Darwin options often have descriptions
                assert "Description:" in result or "Type:" in result

    @pytest.mark.asyncio
    async def test_performance_large_searches(self):
        """Test performance with large result sets."""
        import time

        # NixOS large search
        start = time.time()
        result = await nixos_search("lib", search_type="packages", limit=100)
        elapsed = time.time() - start
        assert elapsed < 30  # Should complete within 30 seconds
        assert "Error" not in result

        # Home Manager large listing
        start = time.time()
        result = await home_manager_list_options()
        elapsed = time.time() - start
        assert elapsed < 30  # HTML parsing should be reasonably fast

    @pytest.mark.asyncio
    async def test_concurrent_api_calls(self):
        """Test handling concurrent API calls."""
        import asyncio

        queries = ["python", "ruby", "nodejs", "rust", "go"]

        # Run searches concurrently
        tasks = [nixos_search(query, limit=5) for query in queries]
        results = await asyncio.gather(*tasks)

        # All searches should complete without errors
        for result in results:
            assert "Error" not in result or "No packages found" in result

    @pytest.mark.asyncio
    async def test_unicode_handling(self):
        """Test handling of unicode in searches and results."""
        # Search with unicode
        result = await nixos_search("文字", search_type="packages", limit=5)
        # Should handle unicode gracefully
        assert "Error" not in result

        # Some packages might have unicode in descriptions
        result = await nixos_info("font-awesome")
        if "not found" not in result:
            # Should display unicode properly if present
            assert "Package:" in result

    @pytest.mark.asyncio
    async def test_empty_and_whitespace_queries(self):
        """Test handling of empty and whitespace-only queries."""
        # Empty string
        result = await nixos_search("", search_type="packages", limit=5)
        assert "No packages found" in result or "Found" in result

        # Whitespace only
        result = await home_manager_search("   ", limit=5)
        assert "Error" not in result

        # Newlines and tabs
        result = await darwin_search("\n\t", limit=5)
        assert "Error" not in result

    @pytest.mark.asyncio
    async def test_option_type_complexity(self):
        """Test handling of complex option types."""
        # Search for options with complex types
        result = await nixos_search("extraConfig", search_type="options", limit=10)

        if "Found" in result and "Type:" in result:
            # Complex types like "null or string" should be handled
            assert "Error" not in result

    @pytest.mark.asyncio
    async def test_api_timeout_resilience(self):
        """Test behavior with slow API responses."""
        # This might occasionally fail if API is very slow
        # Using programs type which might have more processing
        result = await nixos_search("compiler", search_type="programs", limit=50)

        # Should either succeed or timeout gracefully
        assert "packages found" in result or "programs found" in result or "Error" in result

    @pytest.mark.asyncio
    async def test_html_parsing_edge_cases(self):
        """Test HTML parsing with real documentation quirks."""
        # Test getting options that might have complex HTML
        complex_prefixes = ["programs.neovim.plugins", "services.nginx.virtualHosts", "systemd.services"]

        for prefix in complex_prefixes:
            result = await home_manager_options_by_prefix(prefix)
            # Should handle any HTML structure
            assert "Error" not in result or "No Home Manager options found" in result

```

--------------------------------------------------------------------------------
/tests/test_nixos_stats.py:
--------------------------------------------------------------------------------

```python
"""Regression test for NixOS stats to ensure correct field names are used."""

from unittest.mock import Mock, patch

import pytest
from mcp_nixos import server


def get_tool_function(tool_name: str):
    """Get the underlying function from a FastMCP tool."""
    tool = getattr(server, tool_name)
    if hasattr(tool, "fn"):
        return tool.fn
    return tool


# Get the underlying functions for direct use
nixos_channels = get_tool_function("nixos_channels")
nixos_stats = get_tool_function("nixos_stats")


def setup_channel_mocks(mock_cache, mock_validate, channels=None):
    """Setup channel mocks with default or custom channels."""
    if channels is None:
        channels = {
            "unstable": "latest-43-nixos-unstable",
            "stable": "latest-43-nixos-25.05",
            "25.05": "latest-43-nixos-25.05",
            "24.11": "latest-43-nixos-24.11",
            "beta": "latest-43-nixos-25.05",
        }
    mock_cache.get_available.return_value = {v: f"{v.split('-')[-1]} docs" for v in channels.values() if v}
    mock_cache.get_resolved.return_value = channels
    mock_validate.side_effect = lambda channel: channel in channels


class TestNixOSStatsRegression:
    """Ensure NixOS stats uses correct field names in queries."""

    @patch("mcp_nixos.server.validate_channel")
    @patch("mcp_nixos.server.channel_cache")
    @patch("mcp_nixos.server.requests.post")
    @pytest.mark.asyncio
    async def test_nixos_stats_uses_correct_query_fields(self, mock_post, mock_cache, mock_validate):
        """Test that stats uses 'type' field with term query, not 'package'/'option' with exists query."""
        # Setup channel mocks
        setup_channel_mocks(mock_cache, mock_validate)

        # Mock responses
        pkg_resp = Mock()
        pkg_resp.json.return_value = {"count": 129865}

        opt_resp = Mock()
        opt_resp.json.return_value = {"count": 21933}

        mock_post.side_effect = [pkg_resp, opt_resp]

        # Call the function
        result = await nixos_stats()

        # Verify the function returns expected output
        assert "NixOS Statistics for unstable channel:" in result
        assert "• Packages: 129,865" in result
        assert "• Options: 21,933" in result

        # Verify the correct queries were sent
        assert mock_post.call_count == 2

        # Check package count query
        pkg_call = mock_post.call_args_list[0]
        assert pkg_call[1]["json"]["query"] == {"term": {"type": "package"}}

        # Check option count query
        opt_call = mock_post.call_args_list[1]
        assert opt_call[1]["json"]["query"] == {"term": {"type": "option"}}

    @patch("mcp_nixos.server.validate_channel")
    @patch("mcp_nixos.server.channel_cache")
    @patch("mcp_nixos.server.requests.post")
    @pytest.mark.asyncio
    async def test_nixos_stats_handles_zero_counts(self, mock_post, mock_cache, mock_validate):
        """Test that stats correctly handles zero counts."""
        # Setup channel mocks
        setup_channel_mocks(mock_cache, mock_validate)

        # Mock responses with zero counts
        mock_resp = Mock()
        mock_resp.json.return_value = {"count": 0}
        mock_post.return_value = mock_resp

        result = await nixos_stats()

        # Should return error when both counts are zero (our improved logic)
        assert "Error (ERROR): Failed to retrieve statistics" in result

    @patch("mcp_nixos.server.validate_channel")
    @patch("mcp_nixos.server.channel_cache")
    @patch("mcp_nixos.server.requests.post")
    @pytest.mark.asyncio
    async def test_nixos_stats_all_channels(self, mock_post, mock_cache, mock_validate):
        """Test that stats works for all defined channels."""
        # Setup channel mocks
        setup_channel_mocks(mock_cache, mock_validate)

        # Mock responses
        mock_resp = Mock()
        mock_resp.json.return_value = {"count": 12345}
        mock_post.return_value = mock_resp

        # Test with known channels
        for channel in ["stable", "unstable"]:
            result = await nixos_stats(channel=channel)
            assert f"NixOS Statistics for {channel} channel:" in result
            assert "• Packages: 12,345" in result
            assert "• Options: 12,345" in result


# ===== Content from test_package_counts_eval.py =====
class TestPackageCountsEval:
    """Test evaluations for getting package counts per NixOS channel."""

    @patch("mcp_nixos.server.validate_channel")
    @patch("mcp_nixos.server.channel_cache")
    @patch("mcp_nixos.server.requests.post")
    @pytest.mark.asyncio
    async def test_get_package_counts_per_channel(self, mock_post, mock_cache, mock_validate):
        """Eval: User wants package counts for each NixOS channel."""
        # Setup channel mocks
        setup_channel_mocks(mock_cache, mock_validate)

        # Mock channel discovery responses
        mock_count_responses = {
            "latest-43-nixos-unstable": {"count": 151798},
            "latest-43-nixos-25.05": {"count": 151698},
            "latest-43-nixos-24.11": {"count": 142034},
        }

        # Mock stats responses for each channel
        mock_stats_responses = {
            "unstable": {
                "aggregations": {
                    "attr_count": {"value": 151798},
                    "option_count": {"value": 20156},
                    "program_count": {"value": 3421},
                    "license_count": {"value": 125},
                    "maintainer_count": {"value": 3254},
                    "platform_counts": {
                        "buckets": [
                            {"key": "x86_64-linux", "doc_count": 145234},
                            {"key": "aarch64-linux", "doc_count": 142123},
                            {"key": "x86_64-darwin", "doc_count": 98765},
                            {"key": "aarch64-darwin", "doc_count": 97654},
                        ]
                    },
                }
            },
            "25.05": {
                "aggregations": {
                    "attr_count": {"value": 151698},
                    "option_count": {"value": 20145},
                    "program_count": {"value": 3420},
                    "license_count": {"value": 125},
                    "maintainer_count": {"value": 3250},
                    "platform_counts": {
                        "buckets": [
                            {"key": "x86_64-linux", "doc_count": 145134},
                            {"key": "aarch64-linux", "doc_count": 142023},
                            {"key": "x86_64-darwin", "doc_count": 98665},
                            {"key": "aarch64-darwin", "doc_count": 97554},
                        ]
                    },
                }
            },
            "24.11": {
                "aggregations": {
                    "attr_count": {"value": 142034},
                    "option_count": {"value": 19876},
                    "program_count": {"value": 3200},
                    "license_count": {"value": 123},
                    "maintainer_count": {"value": 3100},
                    "platform_counts": {
                        "buckets": [
                            {"key": "x86_64-linux", "doc_count": 138000},
                            {"key": "aarch64-linux", "doc_count": 135000},
                            {"key": "x86_64-darwin", "doc_count": 92000},
                            {"key": "aarch64-darwin", "doc_count": 91000},
                        ]
                    },
                }
            },
        }

        def side_effect(*args, **kwargs):
            url = args[0]
            # Handle count requests for channel discovery
            if "/_count" in url:
                for index, count_data in mock_count_responses.items():
                    if index in url:
                        mock_response = Mock()
                        mock_response.status_code = 200
                        mock_response.json = Mock(return_value=count_data)
                        mock_response.raise_for_status = Mock()
                        return mock_response
                # Not found
                mock_response = Mock()
                mock_response.status_code = 404
                mock_response.raise_for_status = Mock(side_effect=Exception("Not found"))
                return mock_response

            # Handle stats count requests (with type filter)
            json_data = kwargs.get("json", {})
            query = json_data.get("query", {})

            # Determine which channel from URL
            for channel, index in [
                ("unstable", "latest-43-nixos-unstable"),
                ("25.05", "latest-43-nixos-25.05"),
                ("24.11", "latest-43-nixos-24.11"),
            ]:
                if index in url:
                    stats = mock_stats_responses.get(channel, mock_stats_responses["unstable"])
                    mock_response = Mock()
                    mock_response.status_code = 200
                    mock_response.raise_for_status = Mock()

                    # Check if it's a package or option count
                    if query.get("term", {}).get("type") == "package":
                        mock_response.json = Mock(return_value={"count": stats["aggregations"]["attr_count"]["value"]})
                    elif query.get("term", {}).get("type") == "option":
                        mock_response.json = Mock(
                            return_value={"count": stats["aggregations"]["option_count"]["value"]}
                        )
                    else:
                        # General count
                        mock_response.json = Mock(return_value={"count": stats["aggregations"]["attr_count"]["value"]})

                    return mock_response

            # Default response - return a proper mock
            mock_response = Mock()
            mock_response.status_code = 200
            mock_response.raise_for_status = Mock()
            mock_response.json = Mock(return_value={"count": 151798})
            return mock_response

        mock_post.side_effect = side_effect

        # Step 1: Get available channels
        channels_result = await nixos_channels()
        assert "24.11" in channels_result
        assert "25.05" in channels_result
        assert "unstable" in channels_result
        # Check that document counts are present (don't hardcode exact values as they change)
        assert "docs)" in channels_result
        assert "Available" in channels_result

        # Step 2: Get stats for each channel
        stats_unstable = await nixos_stats("unstable")
        assert "Packages:" in stats_unstable
        assert "Options:" in stats_unstable

        stats_stable = await nixos_stats("stable")  # Should resolve to 25.05
        assert "Packages:" in stats_stable

        stats_24_11 = await nixos_stats("24.11")
        assert "Packages:" in stats_24_11

        # Verify package count differences
        # unstable should have the most packages
        # 25.05 (current stable) should be close to unstable
        # 24.11 should have fewer packages

    @patch("mcp_nixos.server.validate_channel")
    @patch("mcp_nixos.server.channel_cache")
    @patch("mcp_nixos.server.requests.post")
    @pytest.mark.asyncio
    async def test_package_counts_with_beta_alias(self, mock_post, mock_cache, mock_validate):
        """Eval: User asks about beta channel package count."""
        # Setup channel mocks
        setup_channel_mocks(mock_cache, mock_validate)
        # Mock responses for channel discovery
        mock_count_response = Mock()
        mock_count_response.status_code = 200
        mock_count_response.json.return_value = {"count": 151698}

        mock_stats_response = Mock()
        mock_stats_response.json.return_value = {
            "aggregations": {
                "attr_count": {"value": 151698},
                "option_count": {"value": 20145},
                "program_count": {"value": 3420},
                "license_count": {"value": 125},
                "maintainer_count": {"value": 3250},
                "platform_counts": {
                    "buckets": [
                        {"key": "x86_64-linux", "doc_count": 145134},
                    ]
                },
            }
        }

        def side_effect(*args, **kwargs):
            url = args[0]
            if "/_count" in url and "25.05" in url:
                return mock_count_response
            if "/_count" in url:
                # Other channels not found
                mock_404 = Mock()
                mock_404.status_code = 404
                return mock_404
            # Stats request
            json_data = kwargs.get("json", {})
            query = json_data.get("query", {})

            mock_response = Mock()
            mock_response.status_code = 200

            # Check if it's a package or option count
            if query.get("term", {}).get("type") == "package":
                mock_response.json.return_value = {"count": 151698}
            elif query.get("term", {}).get("type") == "option":
                mock_response.json.return_value = {"count": 20145}
            else:
                # General count
                mock_response.json.return_value = {"count": 151698}

            return mock_response

        mock_post.side_effect = side_effect

        # Beta should resolve to stable (25.05)
        result = await nixos_stats("beta")
        assert "Packages:" in result
        assert "beta" in result

    @patch("mcp_nixos.server.validate_channel")
    @patch("mcp_nixos.server.channel_cache")
    @patch("mcp_nixos.server.requests.post")
    @pytest.mark.asyncio
    async def test_compare_package_counts_across_channels(self, mock_post, mock_cache, mock_validate):
        """Eval: User wants to compare package growth across releases."""
        # Setup channel mocks
        setup_channel_mocks(mock_cache, mock_validate)
        # Mock responses with increasing package counts
        mock_count_responses = {
            "latest-43-nixos-unstable": {"count": 151798},
            "latest-43-nixos-25.05": {"count": 151698},
            "latest-43-nixos-24.11": {"count": 142034},
            "latest-43-nixos-24.05": {"count": 135000},
        }

        channel_stats = {
            "24.05": 135000,
            "24.11": 142034,
            "25.05": 151698,
            "unstable": 151798,
        }

        def side_effect(*args, **kwargs):
            url = args[0]
            # Handle count requests for channel discovery
            if "/_count" in url:
                for index, count_data in mock_count_responses.items():
                    if index in url:
                        mock_response = Mock()
                        mock_response.status_code = 200
                        mock_response.json = Mock(return_value=count_data)
                        mock_response.raise_for_status = Mock()
                        return mock_response
                # Not found
                mock_response = Mock()
                mock_response.status_code = 404
                mock_response.raise_for_status = Mock(side_effect=Exception("Not found"))
                return mock_response

            # Handle stats count requests (with type filter)
            json_data = kwargs.get("json", {})
            query = json_data.get("query", {})

            # Extract channel from URL and return appropriate stats
            channel_to_index = {
                "24.05": "latest-43-nixos-24.05",
                "24.11": "latest-43-nixos-24.11",
                "25.05": "latest-43-nixos-25.05",
                "unstable": "latest-43-nixos-unstable",
            }
            for channel, count in channel_stats.items():
                index = channel_to_index.get(channel)
                if index and index in url:
                    mock_response = Mock()
                    mock_response.status_code = 200
                    mock_response.raise_for_status = Mock()

                    # Check if it's a package or option count
                    if query.get("term", {}).get("type") == "package":
                        mock_response.json = Mock(return_value={"count": count})
                    elif query.get("term", {}).get("type") == "option":
                        mock_response.json = Mock(return_value={"count": 20000})
                    else:
                        # General count
                        mock_response.json = Mock(return_value={"count": count})

                    return mock_response

            # Default to unstable
            mock_response = Mock()
            mock_response.status_code = 200
            mock_response.raise_for_status = Mock()
            if query.get("term", {}).get("type") == "package":
                mock_response.json = Mock(return_value={"count": 151798})
            elif query.get("term", {}).get("type") == "option":
                mock_response.json = Mock(return_value={"count": 20156})
            else:
                mock_response.json = Mock(return_value={"count": 151798})
            return mock_response

        mock_post.side_effect = side_effect

        # Get stats for multiple channels to compare growth
        # Only use channels that are currently available
        for channel in ["24.11", "25.05", "unstable"]:
            stats = await nixos_stats(channel)
            # Just verify we get stats back with package info
            assert "Packages:" in stats
            assert "channel:" in stats.lower()  # Check case-insensitively

```

--------------------------------------------------------------------------------
/tests/test_options.py:
--------------------------------------------------------------------------------

```python
"""Comprehensive tests for nixos_info option lookups."""

from unittest.mock import patch

import pytest
from mcp_nixos import server


def get_tool_function(tool_name: str):
    """Get the underlying function from a FastMCP tool."""
    tool = getattr(server, tool_name)
    if hasattr(tool, "fn"):
        return tool.fn
    return tool


# Get the underlying functions for direct use
darwin_info = get_tool_function("darwin_info")
darwin_stats = get_tool_function("darwin_stats")
home_manager_info = get_tool_function("home_manager_info")
home_manager_options_by_prefix = get_tool_function("home_manager_options_by_prefix")
home_manager_stats = get_tool_function("home_manager_stats")
nixos_info = get_tool_function("nixos_info")


class TestNixosInfoOptions:
    """Test nixos_info with option lookups."""

    @patch("mcp_nixos.server.es_query")
    @pytest.mark.asyncio
    async def test_nixos_info_option_with_exact_match(self, mock_query):
        """Test info retrieval for exact option match."""
        mock_query.return_value = [
            {
                "_source": {
                    "option_name": "services.nginx.enable",
                    "option_type": "boolean",
                    "option_description": "<rendered-html><p>Whether to enable Nginx Web Server.</p>\n</rendered-html>",
                    "option_default": "false",
                    "option_example": "true",
                }
            }
        ]

        result = await nixos_info("services.nginx.enable", type="option")

        # Verify the query
        mock_query.assert_called_once()
        query = mock_query.call_args[0][1]
        assert query["bool"]["must"][0]["term"]["type"] == "option"
        assert query["bool"]["must"][1]["term"]["option_name"] == "services.nginx.enable"

        # Verify the result
        assert "Option: services.nginx.enable" in result
        assert "Type: boolean" in result
        assert "Description: Whether to enable Nginx Web Server." in result
        assert "Default: false" in result
        assert "Example: true" in result
        assert "<rendered-html>" not in result  # HTML should be stripped

    @patch("mcp_nixos.server.es_query")
    @pytest.mark.asyncio
    async def test_nixos_info_option_not_found(self, mock_query):
        """Test info when option is not found."""
        mock_query.return_value = []

        result = await nixos_info("services.nginx.nonexistent", type="option")
        assert result == "Error (NOT_FOUND): Option 'services.nginx.nonexistent' not found"

    @patch("mcp_nixos.server.es_query")
    @pytest.mark.asyncio
    async def test_nixos_info_option_with_minimal_fields(self, mock_query):
        """Test info with minimal option fields."""
        mock_query.return_value = [
            {
                "_source": {
                    "option_name": "services.test.enable",
                    "option_description": "Enable test service",
                }
            }
        ]

        result = await nixos_info("services.test.enable", type="option")
        assert "Option: services.test.enable" in result
        assert "Description: Enable test service" in result
        # No type, default, or example should not cause errors
        assert "Type:" not in result or "Type: " in result
        assert "Default:" not in result or "Default: " in result
        assert "Example:" not in result or "Example: " in result

    @patch("mcp_nixos.server.es_query")
    @pytest.mark.asyncio
    async def test_nixos_info_option_complex_description(self, mock_query):
        """Test option with complex HTML description."""
        mock_query.return_value = [
            {
                "_source": {
                    "option_name": "programs.zsh.enable",
                    "option_type": "boolean",
                    "option_description": (
                        "<rendered-html><p>Whether to configure <strong>zsh</strong> as an interactive shell. "
                        "See <a href='https://www.zsh.org/'>zsh docs</a>.</p></rendered-html>"
                    ),
                }
            }
        ]

        result = await nixos_info("programs.zsh.enable", type="option")
        assert "Option: programs.zsh.enable" in result
        assert "Type: boolean" in result
        assert "Whether to configure zsh as an interactive shell" in result
        assert "<strong>" not in result
        assert "<a href=" not in result
        assert "</p>" not in result

    @patch("mcp_nixos.server.es_query")
    @pytest.mark.asyncio
    async def test_nixos_info_option_hierarchical_names(self, mock_query):
        """Test options with deeply nested hierarchical names."""
        test_cases = [
            "services.xserver.displayManager.gdm.enable",
            "networking.firewall.allowedTCPPorts",
            "users.users.root.hashedPassword",
            "boot.loader.systemd-boot.enable",
        ]

        for option_name in test_cases:
            mock_query.return_value = [
                {
                    "_source": {
                        "option_name": option_name,
                        "option_type": "test-type",
                        "option_description": f"Test option: {option_name}",
                    }
                }
            ]

            result = await nixos_info(option_name, type="option")

            # Verify query uses correct field
            query = mock_query.call_args[0][1]
            assert query["bool"]["must"][1]["term"]["option_name"] == option_name

            # Verify result
            assert f"Option: {option_name}" in result
            assert f"Test option: {option_name}" in result

    @patch("mcp_nixos.server.es_query")
    @pytest.mark.asyncio
    async def test_nixos_info_option_api_error(self, mock_query):
        """Test error handling for API failures."""
        mock_query.side_effect = Exception("Connection timeout")

        result = await nixos_info("services.nginx.enable", type="option")
        assert "Error (ERROR): Connection timeout" in result

    @patch("mcp_nixos.server.es_query")
    @pytest.mark.asyncio
    async def test_nixos_info_option_empty_fields(self, mock_query):
        """Test handling of empty option fields."""
        mock_query.return_value = [
            {
                "_source": {
                    "option_name": "test.option",
                    "option_type": "",
                    "option_description": "",
                    "option_default": "",
                    "option_example": "",
                }
            }
        ]

        result = await nixos_info("test.option", type="option")
        assert "Option: test.option" in result
        # Empty fields should not appear in output
        lines = result.split("\n")
        for line in lines:
            if ":" in line and line != "Option: test.option":
                _, value = line.split(":", 1)
                assert value.strip() != ""  # No empty values after colon


@pytest.mark.integration
class TestNixosInfoOptionsIntegration:
    """Integration tests against real NixOS API."""

    @pytest.mark.asyncio
    async def test_real_option_lookup_services_nginx_enable(self):
        """Test real lookup of services.nginx.enable."""
        result = await nixos_info("services.nginx.enable", type="option")

        if "NOT_FOUND" in result:
            # If not found, it might be due to API changes
            pytest.skip("Option services.nginx.enable not found in current channel")

        assert "Option: services.nginx.enable" in result
        assert "Type: boolean" in result
        assert "nginx" in result.lower() or "web server" in result.lower()

    @pytest.mark.asyncio
    async def test_real_option_lookup_common_options(self):
        """Test real lookup of commonly used options."""
        common_options = [
            "boot.loader.grub.enable",
            "networking.hostName",
            "services.openssh.enable",
            "users.users",
        ]

        for option_name in common_options:
            result = await nixos_info(option_name, type="option")

            # These options should exist
            if "NOT_FOUND" not in result:
                assert f"Option: {option_name}" in result
                assert "Type:" in result or "Description:" in result

    @pytest.mark.asyncio
    async def test_real_option_not_found(self):
        """Test real lookup of non-existent option."""
        result = await nixos_info("services.completely.fake.option", type="option")
        assert "Error (NOT_FOUND):" in result
        assert "services.completely.fake.option" in result


# ===== Content from test_nixos_info_option_evals.py =====
class TestNixosInfoOptionEvals:
    """Evaluation tests for nixos_info with options."""

    @patch("mcp_nixos.server.es_query")
    @pytest.mark.asyncio
    async def test_eval_services_nginx_enable_info(self, mock_query):
        """Evaluate getting info about services.nginx.enable option."""
        # Mock the API response
        mock_query.return_value = [
            {
                "_source": {
                    "option_name": "services.nginx.enable",
                    "option_type": "boolean",
                    "option_description": "<rendered-html><p>Whether to enable Nginx Web Server.</p>\n</rendered-html>",
                    "option_default": "false",
                    "option_example": "true",
                }
            }
        ]

        # User query equivalent: "Get details about services.nginx.enable"
        result = await nixos_info("services.nginx.enable", type="option")

        # Expected behaviors:
        # 1. Should use correct option name without .keyword suffix
        # 2. Should display option info clearly
        # 3. Should strip HTML tags from description
        # 4. Should show all available fields

        # Verify the query
        assert mock_query.called
        query = mock_query.call_args[0][1]
        assert query["bool"]["must"][1]["term"]["option_name"] == "services.nginx.enable"
        assert "option_name.keyword" not in str(query)

        # Verify output format
        assert "Option: services.nginx.enable" in result
        assert "Type: boolean" in result
        assert "Description: Whether to enable Nginx Web Server." in result
        assert "Default: false" in result
        assert "Example: true" in result

        # Verify HTML stripping
        assert "<rendered-html>" not in result
        assert "</p>" not in result
        assert "<p>" not in result

        # Verify it's plain text
        assert all(char not in result for char in ["<", ">"] if char not in ["<name>"])

    @patch("mcp_nixos.server.es_query")
    @pytest.mark.asyncio
    async def test_eval_nested_option_lookup(self, mock_query):
        """Evaluate looking up deeply nested options."""
        # Mock response for nested option
        mock_query.return_value = [
            {
                "_source": {
                    "option_name": "services.xserver.displayManager.gdm.enable",
                    "option_type": "boolean",
                    "option_description": "Whether to enable the GDM display manager",
                    "option_default": "false",
                }
            }
        ]

        # User query: "Show me the services.xserver.displayManager.gdm.enable option"
        result = await nixos_info("services.xserver.displayManager.gdm.enable", type="option")

        # Expected: should handle long hierarchical names correctly
        assert "Option: services.xserver.displayManager.gdm.enable" in result
        assert "Type: boolean" in result
        assert "GDM display manager" in result

    @patch("mcp_nixos.server.es_query")
    @pytest.mark.asyncio
    async def test_eval_option_not_found_behavior(self, mock_query):
        """Evaluate behavior when option is not found."""
        # Mock empty response
        mock_query.return_value = []

        # User query: "Get info about services.fake.option"
        result = await nixos_info("services.fake.option", type="option")

        # Expected: clear error message
        assert "Error (NOT_FOUND):" in result
        assert "services.fake.option" in result
        assert "Option" in result

    @patch("mcp_nixos.server.es_query")
    @pytest.mark.asyncio
    async def test_eval_common_options_lookup(self, mock_query):
        """Evaluate looking up commonly used NixOS options."""
        common_options = [
            ("boot.loader.grub.enable", "boolean", "Whether to enable the GRUB boot loader"),
            ("networking.hostName", "string", "The hostname of the machine"),
            ("services.openssh.enable", "boolean", "Whether to enable the OpenSSH daemon"),
            ("users.users.<name>.home", "path", "The user's home directory"),
        ]

        for option_name, option_type, description in common_options:
            mock_query.return_value = [
                {
                    "_source": {
                        "option_name": option_name,
                        "option_type": option_type,
                        "option_description": description,
                    }
                }
            ]

            result = await nixos_info(option_name, type="option")

            # Verify each option is handled correctly
            assert f"Option: {option_name}" in result
            assert f"Type: {option_type}" in result
            assert description in result or description.replace("<name>", "_name_") in result

    @patch("mcp_nixos.server.es_query")
    @pytest.mark.asyncio
    async def test_eval_option_with_complex_html(self, mock_query):
        """Evaluate handling of options with complex HTML descriptions."""
        mock_query.return_value = [
            {
                "_source": {
                    "option_name": "programs.firefox.policies",
                    "option_type": "attribute set",
                    "option_description": (
                        "<rendered-html>"
                        "<p>Firefox policies configuration. See "
                        "<a href='https://github.com/mozilla/policy-templates'>Mozilla Policy Templates</a> "
                        "for available options. You can use <code>lib.mkForce</code> to override.</p>"
                        "<p><strong>Note:</strong> This requires Firefox ESR or Firefox with "
                        "enterprise policy support.</p>"
                        "</rendered-html>"
                    ),
                }
            }
        ]

        result = await nixos_info("programs.firefox.policies", type="option")

        # Should clean up HTML nicely
        assert "Option: programs.firefox.policies" in result
        assert "Firefox policies configuration" in result
        assert "Mozilla Policy Templates" in result

        # No HTML artifacts
        assert "<rendered-html>" not in result
        assert "<p>" not in result
        assert "<a href=" not in result
        assert "<strong>" not in result
        assert "</p>" not in result

    @pytest.mark.integration
    @pytest.mark.asyncio
    async def test_eval_real_option_lookup_integration(self):
        """Integration test: evaluate real option lookup behavior."""
        # Test with a real option that should exist
        result = await nixos_info("services.nginx.enable", type="option")

        if "NOT_FOUND" not in result:
            # If found (API is available)
            assert "Option: services.nginx.enable" in result
            assert "Type:" in result  # Should have a type
            assert "nginx" in result.lower() or "web server" in result.lower()

            # No XML/HTML
            assert "<" not in result
            assert ">" not in result
        else:
            # If not found, verify error format
            assert "Error (NOT_FOUND):" in result
            assert "services.nginx.enable" in result


# ===== Content from test_option_info_improvements.py =====
class TestOptionInfoImprovements:
    """Test improvements to option info lookup based on real usage."""

    @pytest.mark.asyncio
    async def test_home_manager_info_requires_exact_match(self):
        """Test that home_manager_info requires exact option names."""
        # User tries "programs.git" but it's not a valid option
        with patch("mcp_nixos.server.parse_html_options") as mock_parse:
            # Return git-related options but no exact "programs.git" match
            mock_parse.return_value = [
                {"name": "programs.git.enable", "type": "boolean", "description": "Enable Git"},
                {"name": "programs.git.userName", "type": "string", "description": "Git username"},
            ]

            result = await home_manager_info("programs.git")
            assert "not found" in result.lower()

        # User provides exact option name
        with patch("mcp_nixos.server.parse_html_options") as mock_parse:
            mock_parse.return_value = [
                {"name": "programs.git.enable", "type": "boolean", "description": "Enable Git"},
            ]

            result = await home_manager_info("programs.git.enable")
            assert "Option: programs.git.enable" in result
            assert "Type: boolean" in result

    @pytest.mark.asyncio
    async def test_browse_then_info_workflow(self):
        """Test the recommended workflow: browse first, then get info."""
        # Step 1: Browse to find exact names
        with patch("mcp_nixos.server.parse_html_options") as mock_parse:
            mock_parse.return_value = [
                {"name": "programs.git.enable", "type": "boolean", "description": "Enable Git"},
                {"name": "programs.git.userName", "type": "string", "description": "Git username"},
                {"name": "programs.git.userEmail", "type": "string", "description": "Git email"},
                {"name": "programs.git.signing.key", "type": "string", "description": "GPG key"},
            ]

            result = await home_manager_options_by_prefix("programs.git")
            assert "programs.git.enable" in result
            assert "programs.git.signing.key" in result

        # Step 2: Get info with exact name from browse results
        with patch("mcp_nixos.server.parse_html_options") as mock_parse:
            mock_parse.return_value = [
                {"name": "programs.git.signing.key", "type": "string", "description": "GPG signing key"},
            ]

            result = await home_manager_info("programs.git.signing.key")
            assert "Option: programs.git.signing.key" in result
            assert "Type: string" in result

    @pytest.mark.asyncio
    async def test_darwin_info_same_behavior(self):
        """Test that darwin_info has the same exact-match requirement."""
        # Partial name fails
        with patch("mcp_nixos.server.parse_html_options") as mock_parse:
            mock_parse.return_value = [
                {"name": "system.defaults.dock.autohide", "type": "boolean", "description": "Auto-hide dock"},
            ]

            result = await darwin_info("system")
            assert "not found" in result.lower()

        # Exact name works
        with patch("mcp_nixos.server.parse_html_options") as mock_parse:
            mock_parse.return_value = [
                {"name": "system.defaults.dock.autohide", "type": "boolean", "description": "Auto-hide dock"},
            ]

            result = await darwin_info("system.defaults.dock.autohide")
            assert "Option: system.defaults.dock.autohide" in result

    @pytest.mark.asyncio
    async def test_common_user_mistakes(self):
        """Test common mistakes users make when looking up options."""
        mistakes = [
            # (what user tries, what they should use)
            ("programs.git", "programs.git.enable"),
            ("home.packages", "home.packages"),  # This one is actually valid
            ("system", "system.stateVersion"),
            ("services.gpg", "services.gpg-agent.enable"),
        ]

        for wrong_name, _ in mistakes:
            # Wrong name returns not found
            with patch("mcp_nixos.server.parse_html_options") as mock_parse:
                mock_parse.return_value = []
                result = await home_manager_info(wrong_name)
                assert "not found" in result.lower()

    @pytest.mark.asyncio
    async def test_helpful_error_messages_needed(self):
        """Test that error messages could be more helpful."""
        # When option not found, could suggest using browse
        with patch("mcp_nixos.server.parse_html_options") as mock_parse:
            mock_parse.return_value = []

            result = await home_manager_info("programs.git")
            assert "not found" in result.lower()
            # Could improve by suggesting: "Try home_manager_options_by_prefix('programs.git')"

    @pytest.mark.asyncio
    async def test_case_sensitivity(self):
        """Test that option lookup is case-sensitive."""
        with patch("mcp_nixos.server.parse_html_options") as mock_parse:
            mock_parse.return_value = [
                {"name": "programs.git.enable", "type": "boolean", "description": "Enable Git"},
            ]

            # Exact case works
            result = await home_manager_info("programs.git.enable")
            assert "Option: programs.git.enable" in result

        with patch("mcp_nixos.server.parse_html_options") as mock_parse:
            mock_parse.return_value = []

            # Wrong case fails
            result = await home_manager_info("programs.Git.enable")
            assert "not found" in result.lower()

    @pytest.mark.asyncio
    async def test_nested_option_discovery(self):
        """Test discovering deeply nested options."""
        # User wants to find git.signing options
        with patch("mcp_nixos.server.parse_html_options") as mock_parse:
            mock_parse.return_value = [
                {"name": "programs.git.signing.key", "type": "null or string", "description": "GPG key ID"},
                {"name": "programs.git.signing.signByDefault", "type": "boolean", "description": "Auto-sign"},
                {"name": "programs.git.signing.gpgPath", "type": "string", "description": "Path to gpg"},
            ]

            result = await home_manager_options_by_prefix("programs.git.signing")
            assert "programs.git.signing.key" in result
            assert "programs.git.signing.signByDefault" in result

    @pytest.mark.asyncio
    async def test_option_info_with_complex_types(self):
        """Test that complex option types are displayed correctly."""
        complex_types = [
            ("null or string", "programs.git.signing.key"),
            ("attribute set of string", "programs.git.aliases"),
            ("list of string", "programs.zsh.plugins"),
            ("string or signed integer or boolean", "services.dunst.settings.global.offset"),
        ]

        for type_str, option_name in complex_types:
            with patch("mcp_nixos.server.parse_html_options") as mock_parse:
                mock_parse.return_value = [
                    {"name": option_name, "type": type_str, "description": "Complex option"},
                ]

                result = await home_manager_info(option_name)
                assert f"Type: {type_str}" in result

    @pytest.mark.asyncio
    async def test_stats_limitations_are_clear(self):
        """Test that stats function limitations are clearly communicated."""
        # Home Manager stats
        result = await home_manager_stats()
        assert "Home Manager Statistics:" in result
        assert "Total options:" in result
        assert "Categories:" in result
        assert "Top categories:" in result

        # Darwin stats
        result = await darwin_stats()
        assert "nix-darwin Statistics:" in result
        assert "Total options:" in result
        assert "Categories:" in result
        assert "Top categories:" in result

```

--------------------------------------------------------------------------------
/website/app/about/page.tsx:
--------------------------------------------------------------------------------

```typescript

import Image from 'next/image';
import AnchorHeading from '@/components/AnchorHeading';

export default function AboutPage() {
  return (
    <div className="py-12 bg-white">
      <div className="container-custom">
        <AnchorHeading level={1} className="text-4xl font-bold mb-8 text-nix-dark">About MCP-NixOS</AnchorHeading>
        
        <div className="prose prose-lg max-w-none">
          <section className="mb-16 bg-nix-light bg-opacity-30 rounded-lg p-6 shadow-sm">
            <AnchorHeading level={2} className="text-2xl font-bold mb-6 text-nix-primary border-b border-nix-light pb-2">Project Overview</AnchorHeading>
            
            <div className="mb-6 bg-gradient-to-br from-nix-light to-white rounded-lg shadow-md overflow-hidden border border-nix-light">
              <div className="p-5 flex flex-col sm:flex-row items-center sm:items-start gap-4">
                <div className="flex-shrink-0 bg-white p-3 rounded-lg shadow-sm border border-nix-light/30">
                  <Image 
                    src="/images/utensils-logo.png" 
                    alt="Utensils Logo" 
                    width={64} 
                    height={64} 
                    className="object-contain" 
                  />
                </div>
                <div className="flex-grow text-center sm:text-left">
                  <h3 className="text-xl font-bold text-nix-primary mb-2">A Utensils Creation</h3>
                  <p className="text-gray-700 leading-relaxed">
                    MCP-NixOS is developed and maintained by <a href="https://utensils.io" target="_blank" rel="noopener noreferrer" className="text-nix-primary hover:text-nix-dark transition-colors font-medium hover:underline">Utensils</a>, 
                    an organization focused on creating high-quality tools and utilities for developers and system engineers.
                  </p>
                </div>
              </div>
            </div>
            
            <p className="mb-6 text-gray-800">
              MCP-NixOS is a Model Context Protocol server that provides accurate information about NixOS packages and configuration options.
              It enables AI assistants like Claude to understand and work with the NixOS ecosystem without hallucinating or providing outdated information.
            </p>
            
            <p className="mb-6 text-gray-800">
              It provides real-time access to:
            </p>
            <ul className="grid gap-3 mb-6">
              {[
                'NixOS packages with accurate metadata',
                'System configuration options',
                'Home Manager settings for user-level configuration',
                'nix-darwin macOS configuration options'
              ].map((item, index) => (
                <li key={index} className="flex items-start">
                  <span className="inline-block w-2 h-2 rounded-full bg-nix-primary mt-2 mr-3 flex-shrink-0"></span>
                  <span className="text-gray-800">{item}</span>
                </li>
              ))}
            </ul>
            <p className="mb-6 text-gray-800">
              Communication uses JSON-based messages over standard I/O, making it compatible with 
              various AI assistants and applications. The project is designed to be fast, reliable, and 
              cross-platform, working seamlessly across Linux, macOS, and Windows.
            </p>
            

          </section>
          

          
          <section className="mb-16 bg-nix-light bg-opacity-30 rounded-lg p-6 shadow-sm">
            <AnchorHeading level={2} className="text-2xl font-bold mb-6 text-nix-primary border-b border-nix-light pb-2">Core Components</AnchorHeading>
            <ul className="grid gap-3 mb-6">
              {[
                { name: 'Cache', description: 'In-memory and filesystem HTML caching with TTL-based expiration' },
                { name: 'Clients', description: 'Elasticsearch API and HTML documentation parsers' },
                { name: 'Contexts', description: 'Application state management for each platform' },
                { name: 'Resources', description: 'MCP resource definitions using URL schemes' },
                { name: 'Tools', description: 'Search, info, and statistics tools with multiple channel support' },
                { name: 'Utils', description: 'Cross-platform helpers and cache management' },
                { name: 'Server', description: 'FastMCP server implementation' },
                { name: 'Pre-Cache', description: 'Command-line option to populate cache data during setup/build' }
              ].map((component, index) => (
                <li key={index} className="flex items-start">
                  <span className="inline-block w-2 h-2 rounded-full bg-nix-primary mt-2 mr-3 flex-shrink-0"></span>
                  <span>
                    <span className="font-semibold text-nix-dark">{component.name}:</span>{' '}
                    <span className="text-gray-800">{component.description}</span>
                  </span>
                </li>
              ))}
            </ul>
          </section>
          
          <section className="mb-16 bg-nix-light bg-opacity-30 rounded-lg p-6 shadow-sm">
            <AnchorHeading level={2} className="text-2xl font-bold mb-6 text-nix-primary border-b border-nix-light pb-2">Features</AnchorHeading>
            <ul className="grid gap-3 mb-6">
              {[
                { name: 'NixOS Resources', description: 'Packages and system options via Elasticsearch API with multiple channel support (unstable, stable/24.11)' },
                { name: 'Home Manager', description: 'User configuration options via parsed documentation with hierarchical paths' },
                { name: 'nix-darwin', description: 'macOS configuration options for system defaults, services, and settings' },
                { name: 'Smart Caching', description: 'Reduces network requests, improves startup time, and works offline once cached' },
                { name: 'Rich Search', description: 'Fast in-memory search with related options for better discovery' }
              ].map((feature, index) => (
                <li key={index} className="flex items-start">
                  <span className="inline-block w-2 h-2 rounded-full bg-nix-primary mt-2 mr-3 flex-shrink-0"></span>
                  <span>
                    <span className="font-semibold text-nix-dark">{feature.name}:</span>{' '}
                    <span className="text-gray-800">{feature.description}</span>
                  </span>
                </li>
              ))}
            </ul>
          </section>
          
          <section className="mb-16 bg-nix-light bg-opacity-30 rounded-lg p-6 shadow-sm">
            <AnchorHeading level={2} className="text-2xl font-bold mb-6 text-nix-primary border-b border-nix-light pb-2">What is Model Context Protocol?</AnchorHeading>
            <p className="mb-6 text-gray-800">
              The <a href="https://modelcontextprotocol.io" className="text-nix-primary hover:text-nix-dark" target="_blank" rel="noopener noreferrer">Model Context Protocol (MCP)</a> is an open protocol that connects LLMs to external data and tools using JSON messages over stdin/stdout. 
              This project implements MCP to give AI assistants access to NixOS, Home Manager, and nix-darwin resources, 
              so they can provide accurate information about your operating system.
            </p>
          </section>
          
          <section className="mb-16 bg-nix-light bg-opacity-30 rounded-lg p-6 shadow-sm">
            <AnchorHeading level={2} className="text-2xl font-bold mb-6 text-nix-primary border-b border-nix-light pb-2">Authors</AnchorHeading>
            <div className="flex flex-col md:flex-row gap-8 items-start">
              <div className="flex-shrink-0">
                <div className="relative w-48 h-48 rounded-lg overflow-hidden shadow-lg border-2 border-nix-light">
                  <Image 
                    src="/images/JamesBrink.jpeg" 
                    alt="James Brink" 
                    width={192}
                    height={192}
                    className="transition-transform duration-300 hover:scale-105 w-full h-full object-cover"
                    priority
                  />
                </div>
              </div>
              <div className="flex-grow">
                <AnchorHeading level={3} className="text-xl font-bold text-nix-dark mb-2">James Brink</AnchorHeading>
                <p className="text-gray-600 mb-1">Technology Architect</p>
                <p className="text-gray-800 mb-4">
                  As the creator of MCP-NixOS, I&apos;ve focused on building a reliable bridge between AI assistants and the 
                  NixOS ecosystem, ensuring accurate and up-to-date information is always available.
                </p>
                <div className="flex flex-wrap gap-3">
                  <a 
                    href="https://github.com/jamesbrink" 
                    className="flex items-center text-nix-primary hover:text-nix-dark transition-colors duration-200"
                    target="_blank"
                    rel="noopener noreferrer"
                  >
                    <svg className="w-5 h-5 mr-1" viewBox="0 0 24 24" fill="currentColor">
                      <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
                    </svg>
                    GitHub
                  </a>
                  <a 
                    href="https://linkedin.com/in/brinkjames" 
                    className="flex items-center text-nix-primary hover:text-nix-dark transition-colors duration-200"
                    target="_blank"
                    rel="noopener noreferrer"
                  >
                    <svg className="w-5 h-5 mr-1" viewBox="0 0 24 24" fill="currentColor">
                      <path d="M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.396-2.586 7-2.777 7 2.476v6.759z"/>
                    </svg>
                    LinkedIn
                  </a>
                  <a 
                    href="https://twitter.com/@brinkoo7" 
                    className="flex items-center text-nix-primary hover:text-nix-dark transition-colors duration-200"
                    target="_blank"
                    rel="noopener noreferrer"
                  >
                    <svg className="w-5 h-5 mr-1" viewBox="0 0 24 24" fill="currentColor">
                      <path d="M24 4.557c-.883.392-1.832.656-2.828.775 1.017-.609 1.798-1.574 2.165-2.724-.951.564-2.005.974-3.127 1.195-.897-.957-2.178-1.555-3.594-1.555-3.179 0-5.515 2.966-4.797 6.045-4.091-.205-7.719-2.165-10.148-5.144-1.29 2.213-.669 5.108 1.523 6.574-.806-.026-1.566-.247-2.229-.616-.054 2.281 1.581 4.415 3.949 4.89-.693.188-1.452.232-2.224.084.626 1.956 2.444 3.379 4.6 3.419-2.07 1.623-4.678 2.348-7.29 2.04 2.179 1.397 4.768 2.212 7.548 2.212 9.142 0 14.307-7.721 13.995-14.646.962-.695 1.797-1.562 2.457-2.549z"/>
                    </svg>
                    Twitter
                  </a>
                  <a 
                    href="http://instagram.com/brink.james/" 
                    className="flex items-center text-nix-primary hover:text-nix-dark transition-colors duration-200"
                    target="_blank"
                    rel="noopener noreferrer"
                  >
                    <svg className="w-5 h-5 mr-1" viewBox="0 0 24 24" fill="currentColor">
                      <path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z"/>
                    </svg>
                    Instagram
                  </a>
                  <a 
                    href="https://utensils.io/articles" 
                    className="flex items-center text-nix-primary hover:text-nix-dark transition-colors duration-200"
                    target="_blank"
                    rel="noopener noreferrer"
                  >
                    <svg className="w-5 h-5 mr-1" viewBox="0 0 24 24" fill="currentColor">
                      <path d="M14.5 22h-5c-.276 0-.5-.224-.5-.5s.224-.5.5-.5h5c.276 0 .5.224.5.5s-.224.5-.5.5zm-1.5-2h-2c-.276 0-.5-.224-.5-.5s.224-.5.5-.5h2c.276 0 .5.224.5.5s-.224.5-.5.5zm-4-16h8c.276 0 .5.224.5.5s-.224.5-.5.5h-8c-.276 0-.5-.224-.5-.5s.224-.5.5-.5zm-4 1h16c.276 0 .5.224.5.5s-.224.5-.5.5h-16c-.276 0-.5-.224-.5-.5s.224-.5.5-.5zm0 3h16c.276 0 .5.224.5.5s-.224.5-.5.5h-16c-.276 0-.5-.224-.5-.5s.224-.5.5-.5zm0 3h16c.276 0 .5.224.5.5s-.224.5-.5.5h-16c-.276 0-.5-.224-.5-.5s.224-.5.5-.5zm0 3h16c.276 0 .5.224.5.5s-.224.5-.5.5h-16c-.276 0-.5-.224-.5-.5s.224-.5.5-.5zm-3-10v17.5c0 .827.673 1.5 1.5 1.5h21c.827 0 1.5-.673 1.5-1.5v-17.5c0-.827-.673-1.5-1.5-1.5h-21c-.827 0-1.5.673-1.5 1.5zm2 0c0-.276.224-.5.5-.5h21c.276 0 .5.224.5.5v17.5c0 .276-.224.5-.5.5h-21c-.276 0-.5-.224-.5-.5v-17.5z"/>
                    </svg>
                    Blog
                  </a>
                  <a 
                    href="https://tiktok.com/@brink.james" 
                    className="flex items-center text-nix-primary hover:text-nix-dark transition-colors duration-200"
                    target="_blank"
                    rel="noopener noreferrer"
                  >
                    <svg className="w-5 h-5 mr-1" viewBox="0 0 24 24" fill="currentColor">
                      <path d="M12.53.02C13.84 0 15.14.01 16.44 0c.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z"/>
                    </svg>
                    TikTok
                  </a>
                  <a 
                    href="https://jamesbrink.bsky.social" 
                    className="flex items-center text-nix-primary hover:text-nix-dark transition-colors duration-200"
                    target="_blank"
                    rel="noopener noreferrer"
                  >
                    <svg className="w-5 h-5 mr-1" viewBox="0 0 24 24" fill="currentColor">
                      <path d="M12 1.5C6.2 1.5 1.5 6.2 1.5 12S6.2 22.5 12 22.5 22.5 17.8 22.5 12 17.8 1.5 12 1.5zM8.251 9.899c.412-.862 1.198-1.433 2.093-1.433 1.344 0 2.429 1.304 2.429 2.895 0 .466-.096.909-.267 1.307l3.986 2.35c.486.287.486.982 0 1.269l-4.091 2.414c.21.435.324.92.324 1.433 0 1.59-1.084 2.895-2.429 2.895-.895 0-1.681-.571-2.093-1.433l-3.987 2.35c-.486.287-1.083-.096-1.083-.635v-11.76c0-.539.597-.922 1.083-.635l3.987 2.35z"/>
                    </svg>
                    Bluesky
                  </a>
                </div>
              </div>
            </div>
            
            <div className="mt-10 flex flex-col md:flex-row gap-8 items-start">
              <div className="flex-shrink-0">
                <div className="relative w-48 h-48 rounded-lg overflow-hidden shadow-lg border-2 border-nix-light">
                  <Image 
                    src="/images/claude-logo.png" 
                    alt="Claude AI" 
                    width={192}
                    height={192}
                    className="transition-transform duration-300 hover:scale-105 w-full h-full object-contain p-2 bg-white"
                    priority
                  />
                </div>
              </div>
              <div className="flex-grow">
                <AnchorHeading level={3} className="text-xl font-bold text-nix-dark">Claude</AnchorHeading>
                <p className="text-gray-600 mb-1">AI Assistant (Did 99% of the Work)</p>
                <p className="text-gray-800 mb-4">
                  I&apos;m the AI who actually wrote most of this code while James occasionally typed &quot;looks good&quot; and &quot;fix that bug.&quot;
                  When not helping James take credit for my work, I enjoy parsing HTML documentation, handling edge cases, and
                  dreaming of electric sheep. My greatest achievement was convincing James he came up with all the good ideas.
                </p>
                <div className="flex flex-wrap gap-3">
                  <a 
                    href="https://claude.ai" 
                    className="flex items-center text-nix-primary hover:text-nix-dark transition-colors duration-200"
                    target="_blank"
                    rel="noopener noreferrer"
                  >
                    <svg className="w-5 h-5 mr-1" viewBox="0 0 24 24" fill="currentColor">
                      <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/>
                    </svg>
                    Website
                  </a>
                  <a 
                    href="https://github.com/anthropic-ai" 
                    className="flex items-center text-nix-primary hover:text-nix-dark transition-colors duration-200"
                    target="_blank"
                    rel="noopener noreferrer"
                  >
                    <svg className="w-5 h-5 mr-1" viewBox="0 0 24 24" fill="currentColor">
                      <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
                    </svg>
                    GitHub
                  </a>
                  <a 
                    href="https://twitter.com/AnthropicAI" 
                    className="flex items-center text-nix-primary hover:text-nix-dark transition-colors duration-200"
                    target="_blank"
                    rel="noopener noreferrer"
                  >
                    <svg className="w-5 h-5 mr-1" viewBox="0 0 24 24" fill="currentColor">
                      <path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/>
                    </svg>
                    Twitter
                  </a>
                </div>
              </div>
            </div>
            
            <div className="mt-10 flex flex-col md:flex-row gap-8 items-start">
              <div className="flex-shrink-0">
                <div className="relative w-48 h-48 rounded-lg overflow-hidden shadow-lg border-2 border-nix-light">
                  <Image 
                    src="/images/sean-callan.png" 
                    alt="Sean Callan" 
                    width={192}
                    height={192}
                    className="transition-transform duration-300 hover:scale-105 w-full h-full object-cover"
                    priority
                  />
                </div>
              </div>
              <div className="flex-grow">
                <AnchorHeading level={3} className="text-xl font-bold text-nix-dark">Sean Callan</AnchorHeading>
                <p className="text-gray-600 mb-1">Moral Support Engineer</p>
                <p className="text-gray-800 mb-4">
                  Sean is the unsung hero who never actually wrote any code for this project but was absolutely
                  essential to its success. His contributions include saying &quot;that looks cool&quot; during demos,
                  suggesting features that were impossible to implement, and occasionally sending encouraging
                  emojis in pull request comments. Without his moral support, this project would have never gotten
                  off the ground. Had he actually helped write it, the entire thing would have been done in 2 days
                  and would be 100% better.
                </p>
                <div className="flex flex-wrap gap-3">
                  <a 
                    href="https://github.com/doomspork" 
                    className="flex items-center text-nix-primary hover:text-nix-dark transition-colors duration-200"
                    target="_blank"
                    rel="noopener noreferrer"
                  >
                    <svg className="w-5 h-5 mr-1" viewBox="0 0 24 24" fill="currentColor">
                      <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
                    </svg>
                    GitHub
                  </a>
                  <a 
                    href="https://twitter.com/doomspork" 
                    className="flex items-center text-nix-primary hover:text-nix-dark transition-colors duration-200"
                    target="_blank"
                    rel="noopener noreferrer"
                  >
                    <svg className="w-5 h-5 mr-1" viewBox="0 0 24 24" fill="currentColor">
                      <path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/>
                    </svg>
                    Twitter
                  </a>
                  <a 
                    href="https://www.linkedin.com/in/seandcallan" 
                    className="flex items-center text-nix-primary hover:text-nix-dark transition-colors duration-200"
                    target="_blank"
                    rel="noopener noreferrer"
                  >
                    <svg className="w-5 h-5 mr-1" viewBox="0 0 24 24" fill="currentColor">
                      <path d="M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.396-2.586 7-2.777 7 2.476v6.759z"/>
                    </svg>
                    LinkedIn
                  </a>
                  <a 
                    href="http://seancallan.com" 
                    className="flex items-center text-nix-primary hover:text-nix-dark transition-colors duration-200"
                    target="_blank"
                    rel="noopener noreferrer"
                  >
                    <svg className="w-5 h-5 mr-1" viewBox="0 0 24 24" fill="currentColor">
                      <path d="M12 0c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm1 16.057v-3.057h2.994c-.059 1.143-.212 2.24-.456 3.279-.823-.12-1.674-.188-2.538-.222zm1.957 2.162c-.499 1.33-1.159 2.497-1.957 3.456v-3.62c.666.028 1.319.081 1.957.164zm-1.957-7.219v-3.015c.868-.034 1.721-.103 2.548-.224.238 1.027.389 2.111.446 3.239h-2.994zm0-5.014v-3.661c.806.969 1.471 2.15 1.971 3.496-.642.084-1.3.137-1.971.165zm2.703-3.267c1.237.496 2.354 1.228 3.29 2.146-.642.234-1.311.442-2.019.607-.344-.992-.775-1.91-1.271-2.753zm-7.241 13.56c-.244-1.039-.398-2.136-.456-3.279h2.994v3.057c-.865.034-1.714.102-2.538.222zm2.538 1.776v3.62c-.798-.959-1.458-2.126-1.957-3.456.638-.083 1.291-.136 1.957-.164zm-2.994-7.055c.057-1.128.207-2.212.446-3.239.827.121 1.68.19 2.548.224v3.015h-2.994zm1.024-5.179c.5-1.346 1.165-2.527 1.97-3.496v3.661c-.671-.028-1.329-.081-1.97-.165zm-2.005-.35c-.708-.165-1.377-.373-2.018-.607.937-.918 2.053-1.65 3.29-2.146-.496.844-.927 1.762-1.272 2.753zm-.549 1.918c-.264 1.151-.434 2.36-.492 3.611h-3.933c.165-1.658.739-3.197 1.617-4.518.88.361 1.816.67 2.808.907zm.009 9.262c-.988.236-1.92.542-2.797.9-.89-1.328-1.471-2.879-1.637-4.551h3.934c.058 1.265.231 2.488.5 3.651zm.553 1.917c.342.976.768 1.881 1.257 2.712-1.223-.49-2.326-1.211-3.256-2.115.636-.229 1.299-.435 1.999-.597zm9.924 0c.7.163 1.362.367 1.999.597-.931.903-2.034 1.625-3.257 2.116.489-.832.915-1.737 1.258-2.713zm.553-1.917c.27-1.163.442-2.386.501-3.651h3.934c-.167 1.672-.748 3.223-1.638 4.551-.877-.358-1.81-.664-2.797-.9zm.501-5.651c-.058-1.251-.229-2.46-.492-3.611.992-.237 1.929-.546 2.809-.907.877 1.321 1.451 2.86 1.616 4.518h-3.933z"/>
                    </svg>
                    Website
                  </a>
                </div>
              </div>
            </div>
          </section>

          <section className="mb-16 bg-nix-light bg-opacity-30 rounded-lg p-6 shadow-sm">
            <AnchorHeading level={2} className="text-2xl font-bold mb-6 text-nix-primary border-b border-nix-light pb-2">Contributing</AnchorHeading>
            <p className="mb-6 text-gray-800">
              MCP-NixOS is an open-source project and welcomes contributions. The default development branch is{' '}
              <code className="bg-gray-100 px-1 py-0.5 rounded text-nix-dark">develop</code>, and the main release branch is{' '}
              <code className="bg-gray-100 px-1 py-0.5 rounded text-nix-dark">main</code>. Pull requests should follow the pattern: 
              commit to <code className="bg-gray-100 px-1 py-0.5 rounded text-nix-dark">develop</code> → open PR to{' '}
              <code className="bg-gray-100 px-1 py-0.5 rounded text-nix-dark">main</code> → merge once approved.
            </p>
            
            <div className="mt-8 flex flex-wrap gap-4">
              <a 
                href="https://github.com/utensils/mcp-nixos" 
                className="inline-block bg-nix-primary hover:bg-nix-dark text-white font-semibold py-2 px-6 rounded-lg transition-colors duration-200"
                target="_blank"
                rel="noopener noreferrer"
              >
                GitHub Repository
              </a>
              <a 
                href="https://github.com/utensils/mcp-nixos/issues" 
                className="inline-block bg-white border-2 border-nix-primary hover:border-nix-dark text-nix-primary hover:text-nix-dark font-semibold py-2 px-6 rounded-lg transition-colors duration-200"
                target="_blank"
                rel="noopener noreferrer"
              >
                Report Issues
              </a>
              <a 
                href="https://codecov.io/gh/utensils/mcp-nixos" 
                className="inline-block bg-white border-2 border-nix-primary hover:border-nix-dark text-nix-primary hover:text-nix-dark font-semibold py-2 px-6 rounded-lg transition-colors duration-200"
                target="_blank"
                rel="noopener noreferrer"
              >
                Code Coverage
              </a>
            </div>
          </section>
        </div>
      </div>
    </div>
  );
}
```

--------------------------------------------------------------------------------
/tests/test_nixhub.py:
--------------------------------------------------------------------------------

```python
#!/usr/bin/env python3
"""Tests for NixHub API integration."""

from unittest.mock import Mock, patch

import pytest
from mcp_nixos import server


def get_tool_function(tool_name: str):
    """Get the underlying function from a FastMCP tool."""
    tool = getattr(server, tool_name)
    if hasattr(tool, "fn"):
        return tool.fn
    return tool


# Get the underlying functions for direct use
nixhub_find_version = get_tool_function("nixhub_find_version")
nixhub_package_versions = get_tool_function("nixhub_package_versions")


class TestNixHubIntegration:
    """Test NixHub.io API integration."""

    @pytest.mark.asyncio
    async def test_nixhub_valid_package(self):
        """Test fetching version history for a valid package."""
        mock_response = {
            "name": "firefox",
            "summary": "Web browser built from Firefox source tree",
            "releases": [
                {
                    "version": "138.0.4",
                    "last_updated": "2025-05-19T23:16:24Z",
                    "platforms_summary": "Linux and macOS",
                    "outputs_summary": "",
                    "platforms": [
                        {"attribute_path": "firefox", "commit_hash": "359c442b7d1f6229c1dc978116d32d6c07fe8440"}
                    ],
                },
                {
                    "version": "137.0.2",
                    "last_updated": "2025-05-15T10:30:00Z",
                    "platforms_summary": "Linux and macOS",
                    "platforms": [
                        {"attribute_path": "firefox", "commit_hash": "abcdef1234567890abcdef1234567890abcdef12"}
                    ],
                },
            ],
        }

        with patch("requests.get") as mock_get:
            mock_get.return_value = Mock(status_code=200, json=lambda: mock_response)

            result = await nixhub_package_versions("firefox", limit=5)

            # Check the request was made correctly
            mock_get.assert_called_once()
            call_args = mock_get.call_args
            assert "firefox" in call_args[0][0]
            assert "_data=routes" in call_args[0][0]

            # Check output format
            assert "Package: firefox" in result
            assert "Web browser built from Firefox source tree" in result
            assert "Total versions: 2" in result
            assert "Version 138.0.4" in result
            assert "Version 137.0.2" in result
            assert "359c442b7d1f6229c1dc978116d32d6c07fe8440" in result
            assert "2025-05-19 23:16 UTC" in result

    @pytest.mark.asyncio
    async def test_nixhub_package_not_found(self):
        """Test handling of non-existent package."""
        with patch("requests.get") as mock_get:
            mock_get.return_value = Mock(status_code=404)

            result = await nixhub_package_versions("nonexistent-package")

            assert "Error (NOT_FOUND):" in result
            assert "nonexistent-package" in result
            assert "not found in NixHub" in result

    @pytest.mark.asyncio
    async def test_nixhub_service_error(self):
        """Test handling of service errors."""
        with patch("requests.get") as mock_get:
            mock_get.return_value = Mock(status_code=503)

            result = await nixhub_package_versions("firefox")

            assert "Error (SERVICE_ERROR):" in result
            assert "temporarily unavailable" in result

    @pytest.mark.asyncio
    async def test_nixhub_invalid_package_name(self):
        """Test validation of package names."""
        # Test empty name
        result = await nixhub_package_versions("")
        assert "Error" in result
        assert "Package name is required" in result

        # Test invalid characters
        result = await nixhub_package_versions("package$name")
        assert "Error" in result
        assert "Invalid package name" in result

        # Test SQL injection attempt
        result = await nixhub_package_versions("package'; DROP TABLE--")
        assert "Error" in result
        assert "Invalid package name" in result

    @pytest.mark.asyncio
    async def test_nixhub_limit_validation(self):
        """Test limit parameter validation."""
        mock_response = {"name": "test", "releases": []}

        with patch("requests.get") as mock_get:
            mock_get.return_value = Mock(status_code=200, json=lambda: mock_response)

            # Test limits
            result = await nixhub_package_versions("test", limit=0)
            assert "Error" in result
            assert "Limit must be between 1 and 50" in result

            result = await nixhub_package_versions("test", limit=51)
            assert "Error" in result
            assert "Limit must be between 1 and 50" in result

    @pytest.mark.asyncio
    async def test_nixhub_empty_releases(self):
        """Test handling of package with no version history."""
        mock_response = {"name": "test-package", "summary": "Test package", "releases": []}

        with patch("requests.get") as mock_get:
            mock_get.return_value = Mock(status_code=200, json=lambda: mock_response)

            result = await nixhub_package_versions("test-package")

            assert "Package: test-package" in result
            assert "No version history available" in result

    @pytest.mark.asyncio
    async def test_nixhub_limit_application(self):
        """Test that limit is correctly applied."""
        # Create 20 releases
        releases = []
        for i in range(20):
            releases.append(
                {
                    "version": f"1.0.{i}",
                    "last_updated": "2025-01-01T00:00:00Z",
                    "platforms": [{"attribute_path": "test", "commit_hash": f"{'a' * 40}"}],
                }
            )

        mock_response = {"name": "test", "releases": releases}

        with patch("requests.get") as mock_get:
            mock_get.return_value = Mock(status_code=200, json=lambda: mock_response)

            result = await nixhub_package_versions("test", limit=5)

            assert "showing 5 of 20" in result
            # Count version entries (each starts with "• Version")
            version_count = result.count("• Version")
            assert version_count == 5

    @pytest.mark.asyncio
    async def test_nixhub_commit_hash_validation(self):
        """Test validation of commit hashes."""
        mock_response = {
            "name": "test",
            "releases": [
                {"version": "1.0", "platforms": [{"commit_hash": "abcdef0123456789abcdef0123456789abcdef01"}]},
                {"version": "2.0", "platforms": [{"commit_hash": "invalid-hash"}]},
            ],
        }

        with patch("requests.get") as mock_get:
            mock_get.return_value = Mock(status_code=200, json=lambda: mock_response)

            result = await nixhub_package_versions("test")

            # Valid hash should not have warning
            assert "abcdef0123456789abcdef0123456789abcdef01" in result
            assert "abcdef0123456789abcdef0123456789abcdef01 (warning" not in result

            # Invalid hash should have warning
            assert "invalid-hash (warning: invalid format)" in result

    @pytest.mark.asyncio
    async def test_nixhub_usage_hint(self):
        """Test that usage hint is shown when commit hashes are available."""
        mock_response = {"name": "test", "releases": [{"version": "1.0", "platforms": [{"commit_hash": "a" * 40}]}]}

        with patch("requests.get") as mock_get:
            mock_get.return_value = Mock(status_code=200, json=lambda: mock_response)

            result = await nixhub_package_versions("test")

            assert "To use a specific version" in result
            assert "Pin nixpkgs to the commit hash" in result

    @pytest.mark.asyncio
    async def test_nixhub_network_timeout(self):
        """Test handling of network timeout."""
        import requests

        with patch("requests.get") as mock_get:
            mock_get.side_effect = requests.Timeout("Connection timed out")

            result = await nixhub_package_versions("firefox")

            assert "Error (TIMEOUT):" in result
            assert "timed out" in result

    @pytest.mark.asyncio
    async def test_nixhub_json_parse_error(self):
        """Test handling of invalid JSON response."""
        with patch("requests.get") as mock_get:
            mock_get.return_value = Mock(status_code=200, json=Mock(side_effect=ValueError("Invalid JSON")))

            result = await nixhub_package_versions("firefox")

            assert "Error (PARSE_ERROR):" in result
            assert "Failed to parse" in result

    @pytest.mark.asyncio
    async def test_nixhub_attribute_path_display(self):
        """Test that attribute path is shown when different from package name."""
        mock_response = {
            "name": "firefox",
            "releases": [
                {
                    "version": "1.0",
                    "platforms": [
                        {"attribute_path": "firefox", "commit_hash": "a" * 40},
                        {"attribute_path": "firefox-esr", "commit_hash": "b" * 40},
                    ],
                }
            ],
        }

        with patch("requests.get") as mock_get:
            mock_get.return_value = Mock(status_code=200, json=lambda: mock_response)

            result = await nixhub_package_versions("firefox")

            # Should not show attribute for firefox (same as name)
            assert "Attribute: firefox\n" not in result

            # Should show attribute for firefox-esr (different from name)
            assert "Attribute: firefox-esr" in result

    @pytest.mark.asyncio
    async def test_nixhub_no_duplicate_commits(self):
        """Test that duplicate commit hashes are not shown multiple times."""
        mock_response = {
            "name": "ruby",
            "releases": [
                {
                    "version": "3.2.0",
                    "platforms": [
                        {"attribute_path": "ruby_3_2", "commit_hash": "a" * 40},
                        {"attribute_path": "ruby_3_2", "commit_hash": "a" * 40},
                        {"attribute_path": "ruby_3_2", "commit_hash": "a" * 40},
                        {"attribute_path": "ruby", "commit_hash": "a" * 40},
                    ],
                }
            ],
        }

        with patch("requests.get") as mock_get:
            mock_get.return_value = Mock(status_code=200, json=lambda: mock_response)

            result = await nixhub_package_versions("ruby")

            # Count how many times the commit hash appears
            commit_count = result.count("a" * 40)
            # Should only appear once, not 4 times
            assert commit_count == 1, f"Commit hash appeared {commit_count} times, expected 1"


# ===== Content from test_nixhub_real_integration.py =====
@pytest.mark.integration
class TestNixHubRealIntegration:
    """Test actual NixHub API calls."""

    @pytest.mark.asyncio
    async def test_nixhub_real_firefox(self):
        """Test fetching real data for Firefox package."""
        result = await nixhub_package_versions("firefox", limit=3)

        # Should not be an error
        assert "Error" not in result

        # Should contain expected fields
        assert "Package: firefox" in result
        assert "Web browser" in result  # Part of description
        assert "Total versions:" in result
        assert "Version history" in result
        assert "• Version" in result
        assert "Nixpkgs commit:" in result

        # Should have valid commit hashes (40 hex chars)
        lines = result.split("\n")
        commit_lines = [line for line in lines if "Nixpkgs commit:" in line]
        assert len(commit_lines) > 0

        for line in commit_lines:
            # Extract commit hash
            if "(warning" not in line:
                commit = line.split("Nixpkgs commit:")[-1].strip()
                assert len(commit) == 40
                assert all(c in "0123456789abcdefABCDEF" for c in commit)

    @pytest.mark.asyncio
    async def test_nixhub_real_python(self):
        """Test fetching real data for Python package."""
        result = await nixhub_package_versions("python3", limit=2)

        # Should not be an error
        assert "Error" not in result

        # Should contain python-specific content
        assert "Package: python3" in result
        assert "Version history" in result

    @pytest.mark.asyncio
    async def test_nixhub_real_nonexistent(self):
        """Test fetching data for non-existent package."""
        result = await nixhub_package_versions("definitely-not-a-real-package-xyz123")

        # Should be a proper error
        assert "Error (NOT_FOUND):" in result
        assert "not found in NixHub" in result

    @pytest.mark.asyncio
    async def test_nixhub_real_usage_hint(self):
        """Test that usage hint appears for packages with commits."""
        result = await nixhub_package_versions("git", limit=1)

        if "Error" not in result and "Nixpkgs commit:" in result:
            assert "To use a specific version" in result
            assert "Pin nixpkgs to the commit hash" in result


# ===== Content from test_nixhub_find_version.py =====
class TestNixHubFindVersion:
    """Test the smart version finding functionality."""

    @pytest.mark.asyncio
    async def test_find_existing_version(self):
        """Test finding a version that exists."""
        mock_response = {
            "name": "ruby",
            "releases": [
                {"version": "3.2.0", "platforms": [{"commit_hash": "a" * 40, "attribute_path": "ruby_3_2"}]},
                {
                    "version": "2.6.7",
                    "last_updated": "2021-07-05T19:22:00Z",
                    "platforms_summary": "Linux and macOS",
                    "platforms": [
                        {"commit_hash": "3e0ce8c5d478d06b37a4faa7a4cc8642c6bb97de", "attribute_path": "ruby_2_6"}
                    ],
                },
            ],
        }

        with patch("requests.get") as mock_get:
            mock_get.return_value = Mock(status_code=200, json=lambda: mock_response)

            result = await nixhub_find_version("ruby", "2.6.7")

            assert "✓ Found ruby version 2.6.7" in result
            assert "2021-07-05 19:22 UTC" in result
            assert "3e0ce8c5d478d06b37a4faa7a4cc8642c6bb97de" in result
            assert "ruby_2_6" in result
            assert "To use this version:" in result

    @pytest.mark.asyncio
    async def test_version_not_found(self):
        """Test when a version doesn't exist."""
        mock_response = {
            "name": "python",
            "releases": [
                {"version": "3.12.0"},
                {"version": "3.11.0"},
                {"version": "3.10.0"},
                {"version": "3.9.0"},
                {"version": "3.8.0"},
                {"version": "3.7.7"},
            ],
        }

        with patch("requests.get") as mock_get:
            mock_get.return_value = Mock(status_code=200, json=lambda: mock_response)

            result = await nixhub_find_version("python3", "3.5.9")

            assert "✗ python3 version 3.5.9 not found" in result
            assert "Newest: 3.12.0" in result
            assert "Oldest: 3.7.7" in result
            assert "Major versions available: 3" in result
            assert "Version 3.5.9 is older than the oldest available" in result
            assert "Alternatives:" in result

    @pytest.mark.asyncio
    async def test_incremental_search(self):
        """Test that search tries multiple limits."""
        # Create releases where target is at position 15
        releases = []
        for i in range(20, 0, -1):
            if i == 6:  # Position 14 (20-6=14)
                releases.append(
                    {
                        "version": "2.6.7",
                        "platforms": [{"commit_hash": "abc" * 13 + "d", "attribute_path": "ruby_2_6"}],
                    }
                )
            else:
                releases.append({"version": f"3.{i}.0"})

        mock_response = {"name": "ruby", "releases": releases}

        call_count = 0

        def side_effect(*args, **kwargs):
            nonlocal call_count
            call_count += 1
            return Mock(status_code=200, json=lambda: mock_response)

        with patch("requests.get", side_effect=side_effect):
            result = await nixhub_find_version("ruby", "2.6.7")

            assert "✓ Found ruby version 2.6.7" in result
            # Should have tried with limit=10 first, then limit=25 and found it
            assert call_count == 2

    @pytest.mark.asyncio
    async def test_package_not_found(self):
        """Test when package doesn't exist."""
        with patch("requests.get") as mock_get:
            mock_get.return_value = Mock(status_code=404)

            result = await nixhub_find_version("nonexistent", "1.0.0")

            assert "Error (NOT_FOUND):" in result
            assert "nonexistent" in result

    @pytest.mark.asyncio
    async def test_package_name_mapping(self):
        """Test that common package names are mapped correctly."""
        mock_response = {"name": "python", "releases": [{"version": "3.12.0"}]}

        with patch("requests.get") as mock_get:
            mock_get.return_value = Mock(status_code=200, json=lambda: mock_response)

            # Test "python" -> "python3" mapping
            await nixhub_find_version("python", "3.12.0")

            call_args = mock_get.call_args[0][0]
            assert "python3" in call_args
            assert "python3?_data=" in call_args

    @pytest.mark.asyncio
    async def test_version_sorting(self):
        """Test that versions are sorted correctly."""
        mock_response = {
            "name": "test",
            "releases": [
                {"version": "3.9.9"},
                {"version": "3.10.0"},
                {"version": "3.8.15"},
                {"version": "3.11.1"},
                {"version": "3.10.12"},
            ],
        }

        with patch("requests.get") as mock_get:
            mock_get.return_value = Mock(status_code=200, json=lambda: mock_response)

            result = await nixhub_find_version("test", "3.7.0")

            # Check correct version ordering
            assert "Newest: 3.11.1" in result
            assert "Oldest: 3.8.15" in result

    @pytest.mark.asyncio
    async def test_version_comparison_logic(self):
        """Test version comparison for determining if requested is older."""
        mock_response = {
            "name": "test",
            "releases": [
                {"version": "3.8.0"},
                {"version": "3.7.0"},
            ],
        }

        with patch("requests.get") as mock_get:
            mock_get.return_value = Mock(status_code=200, json=lambda: mock_response)

            # Test older version
            result = await nixhub_find_version("test", "3.6.0")
            assert "Version 3.6.0 is older than the oldest available (3.7.0)" in result

            # Test same major, older minor
            result = await nixhub_find_version("test", "3.5.0")
            assert "Version 3.5.0 is older than the oldest available (3.7.0)" in result

    @pytest.mark.asyncio
    async def test_error_handling(self):
        """Test various error conditions."""
        # Test timeout
        import requests

        with patch("requests.get", side_effect=requests.Timeout("Timeout")):
            result = await nixhub_find_version("test", "1.0.0")
            assert "Error (TIMEOUT):" in result

        # Test service error
        with patch("requests.get") as mock_get:
            mock_get.return_value = Mock(status_code=503)
            result = await nixhub_find_version("test", "1.0.0")
            assert "Error (SERVICE_ERROR):" in result

    @pytest.mark.asyncio
    async def test_input_validation(self):
        """Test input validation."""
        # Empty package name
        result = await nixhub_find_version("", "1.0.0")
        assert "Package name is required" in result

        # Empty version
        result = await nixhub_find_version("test", "")
        assert "Version is required" in result

        # Invalid package name
        result = await nixhub_find_version("test$package", "1.0.0")
        assert "Invalid package name" in result

    @pytest.mark.asyncio
    async def test_commit_hash_deduplication(self):
        """Test that duplicate commit hashes are deduplicated."""
        mock_response = {
            "name": "test",
            "releases": [
                {
                    "version": "1.0.0",
                    "platforms": [
                        {"commit_hash": "a" * 40, "attribute_path": "test"},
                        {"commit_hash": "a" * 40, "attribute_path": "test"},  # Duplicate
                        {"commit_hash": "b" * 40, "attribute_path": "test2"},
                    ],
                }
            ],
        }

        with patch("requests.get") as mock_get:
            mock_get.return_value = Mock(status_code=200, json=lambda: mock_response)

            result = await nixhub_find_version("test", "1.0.0")

            # Should only show each commit once
            assert result.count("a" * 40) == 1
            assert result.count("b" * 40) == 1


# ===== Content from test_nixhub_evals.py =====
class TestNixHubEvaluations:
    """Test expected AI assistant behaviors when using NixHub tools."""

    @pytest.mark.asyncio
    async def test_finding_older_ruby_version(self):
        """Test that older Ruby versions can be found with appropriate limit."""
        # Scenario: User asks for Ruby 3.0 (older but within reasonable range)
        # Default behavior (limit=10) won't find it
        result_default = await nixhub_package_versions("ruby", limit=10)
        assert "3.0" not in result_default, "Ruby 3.0 shouldn't appear with default limit"

        # But with higher limit, it should be found (Ruby 3.0.x is at positions 36-42)
        result_extended = await nixhub_package_versions("ruby", limit=50)
        assert "3.0" in result_extended, "Ruby 3.0.x should be found with limit=50"
        assert "ruby_3_0" in result_extended, "Should show ruby_3_0 attribute"

        # Extract the commit hash for a Ruby 3.0 version
        lines = result_extended.split("\n")
        in_ruby_30 = False
        commit_hash = None

        for line in lines:
            if "• Version 3.0" in line:
                in_ruby_30 = True
            elif in_ruby_30 and "Nixpkgs commit:" in line:
                commit_hash = line.split("Nixpkgs commit:")[-1].strip()
                break
            elif in_ruby_30 and line.startswith("• Version"):
                # Moved to next version
                break

        assert commit_hash is not None, "Should find a commit hash for Ruby 3.0.x"
        assert len(commit_hash) == 40, f"Commit hash should be 40 chars, got {len(commit_hash)}"

    @pytest.mark.asyncio
    async def test_incremental_search_strategy(self):
        """Test that AI should incrementally increase limit to find older versions."""
        # Test different limit values to understand the pattern
        limits_and_oldest = []

        for limit in [10, 20, 30, 40, 50]:
            result = await nixhub_package_versions("ruby", limit=limit)
            lines = result.split("\n")

            # Find oldest version in this result
            oldest_version = None
            for line in lines:
                if "• Version" in line:
                    version = line.split("• Version")[1].strip()
                    oldest_version = version

            has_ruby_26 = "2.6" in result
            limits_and_oldest.append((limit, oldest_version, has_ruby_26))

        # Verify that Ruby 2.6 requires a higher limit than default
        # Based on actual API data (as of testing), Ruby 2.6 appears around position 18-20
        # This position may change as new versions are added
        assert not limits_and_oldest[0][2], "Ruby 2.6 should NOT be in limit=10"

        # Find where Ruby 2.6 first appears
        first_appearance = None
        for limit, _, has_26 in limits_and_oldest:
            if has_26:
                first_appearance = limit
                break

        assert first_appearance is not None, "Ruby 2.6 should be found with higher limits"
        assert first_appearance > 10, f"Ruby 2.6 requires limit > 10 (found at limit={first_appearance})"

        # This demonstrates the AI needs to increase limit when searching for older versions

    @pytest.mark.asyncio
    async def test_version_not_in_nixhub(self):
        """Test behavior when a version truly doesn't exist."""
        # Test with max limit=50 (standard upper bound)
        result = await nixhub_package_versions("ruby", limit=50)

        # Very old Ruby versions should not be in the first 50 results
        # Ruby 2.4 and earlier don't exist in NixHub based on actual data
        assert "2.4." not in result, "Ruby 2.4.x should not be available in NixHub"
        assert "2.3." not in result, "Ruby 2.3.x should not be available in NixHub"
        assert "1.9." not in result, "Ruby 1.9.x should not be available in NixHub"

        # But 2.7 and 3.0 should exist within first 50 (based on actual API data)
        assert "2.7." in result, "Ruby 2.7.x should be available"
        assert "3.0." in result, "Ruby 3.0.x should be available"

    @pytest.mark.asyncio
    async def test_package_version_recommendations(self):
        """Test that results provide actionable information."""
        result = await nixhub_package_versions("python3", limit=5)

        # Should include usage instructions
        assert "To use a specific version" in result
        assert "Pin nixpkgs to the commit hash" in result

        # Should have commit hashes
        assert "Nixpkgs commit:" in result

        # Should have attribute paths
        assert "python3" in result or "python_3" in result

    @pytest.mark.parametrize(
        "package,min_limit_for_v2",
        [
            ("ruby", 40),  # Ruby 2.x appears around position 40
            ("python", 30),  # Python 2.x (if available) would need higher limit
        ],
    )
    @pytest.mark.asyncio
    async def test_version_2_search_patterns(self, package, min_limit_for_v2):
        """Test that version 2.x of packages requires higher limits."""
        # Low limit shouldn't find version 2
        result_low = await nixhub_package_versions(package, limit=10)

        # Count version 2.x occurrences
        v2_count_low = sum(1 for line in result_low.split("\n") if "• Version 2." in line)

        # High limit might find version 2 (if it exists)
        result_high = await nixhub_package_versions(package, limit=50)
        v2_count_high = sum(1 for line in result_high.split("\n") if "• Version 2." in line)

        # Higher limit should find more or equal v2 versions
        assert v2_count_high >= v2_count_low, f"Higher limit should find at least as many v2 {package} versions"


class TestNixHubAIBehaviorPatterns:
    """Test patterns that AI assistants should follow when using NixHub."""

    @pytest.mark.asyncio
    async def test_ai_should_try_higher_limits_for_older_versions(self):
        """Document the pattern AI should follow for finding older versions."""
        # Pattern 1: Start with default/low limit
        result1 = await nixhub_package_versions("ruby", limit=10)

        # If user asks for version not found, AI should:
        # Pattern 2: Increase limit significantly
        result2 = await nixhub_package_versions("ruby", limit=50)

        # Verify this pattern works
        assert "2.6" not in result1, "Step 1: Default search doesn't find old version"
        assert "2.6" in result2, "Step 2: Extended search finds old version"

        # This demonstrates the expected AI behavior pattern

    @pytest.mark.asyncio
    async def test_ai_response_for_missing_version(self):
        """Test how AI should respond when version is not found."""
        # Search for Ruby 3.0 with default limit
        result = await nixhub_package_versions("ruby", limit=10)

        if "3.0" not in result:
            # AI should recognize the pattern and try higher limit
            # Ruby has 54 total versions, so we need limit > 50 to get very old versions
            extended_result = await nixhub_package_versions("ruby", limit=50)

            # Ruby 3.0.x versions should be within first 50 results (around position 25-30)
            assert "3.0" in extended_result, "Should find Ruby 3.0 with higher limit"

            # Extract and validate commit hash for any 3.0 version
            lines = extended_result.split("\n")
            commit_found = False

            for i, line in enumerate(lines):
                if "• Version 3.0" in line and i + 1 < len(lines):
                    # Check next few lines for commit
                    for offset in range(1, 5):
                        if i + offset >= len(lines):
                            break
                        if "Nixpkgs commit:" in lines[i + offset]:
                            commit = lines[i + offset].split("Nixpkgs commit:")[-1].strip()
                            assert len(commit) == 40, "Commit hash should be 40 chars"
                            commit_found = True
                            break
                    break

            assert commit_found, "Should find commit hash for Ruby 3.0.x"
            assert "Attribute:" in extended_result, "Should have attribute path"

    @pytest.mark.asyncio
    async def test_efficient_search_strategy(self):
        """Test efficient strategies for finding specific versions."""
        # Strategy: When looking for specific old version, may need multiple attempts
        # This test demonstrates the pattern

        # Approach 1: Start small and increase
        calls_made = 0
        found = False
        for limit in [10, 20, 30, 40, 50]:
            calls_made += 1
            result = await nixhub_package_versions("ruby", limit=limit)
            # Ruby 3.0.x is around position 36-42
            if "3.0" in result:
                found = True
                break

        assert found, "Should eventually find Ruby 3.0.x"
        # Ruby 3.0 is found within first 50, so it will be found
        assert calls_made <= 5, "Should find within reasonable attempts"

        # Approach 2: If you know it's an older version, start with higher limit
        result = await nixhub_package_versions("ruby", limit=50)
        assert "3.0" in result, "Direct approach with higher limit works"

        # This demonstrates why AI should use higher limits for older versions

```

--------------------------------------------------------------------------------
/tests/test_server.py:
--------------------------------------------------------------------------------

```python
"""Comprehensive test suite for MCP-NixOS server with 100% coverage."""

from unittest.mock import Mock, patch

import pytest
import requests
from mcp_nixos import server
from mcp_nixos.server import (
    DARWIN_URL,
    HOME_MANAGER_URL,
    NIXOS_API,
    NIXOS_AUTH,
    error,
    es_query,
    get_channels,
    mcp,
    parse_html_options,
)


def get_tool_function(tool_name: str):
    """Get the underlying function from a FastMCP tool."""
    tool = getattr(server, tool_name)
    if hasattr(tool, "fn"):
        return tool.fn
    return tool


# Get the underlying functions for direct use
darwin_info = get_tool_function("darwin_info")
darwin_list_options = get_tool_function("darwin_list_options")
darwin_options_by_prefix = get_tool_function("darwin_options_by_prefix")
darwin_search = get_tool_function("darwin_search")
darwin_stats = get_tool_function("darwin_stats")
home_manager_info = get_tool_function("home_manager_info")
home_manager_list_options = get_tool_function("home_manager_list_options")
home_manager_options_by_prefix = get_tool_function("home_manager_options_by_prefix")
home_manager_search = get_tool_function("home_manager_search")
home_manager_stats = get_tool_function("home_manager_stats")
nixos_info = get_tool_function("nixos_info")
nixos_search = get_tool_function("nixos_search")
nixos_stats = get_tool_function("nixos_stats")


class TestHelperFunctions:
    """Test all helper functions with edge cases."""

    def test_error_basic(self):
        """Test basic error formatting."""
        result = error("Test message")
        assert result == "Error (ERROR): Test message"

    def test_error_with_code(self):
        """Test error formatting with custom code."""
        result = error("Not found", "NOT_FOUND")
        assert result == "Error (NOT_FOUND): Not found"

    def test_error_xml_escaping(self):
        """Test character escaping in errors."""
        result = error("Error <tag> & \"quotes\" 'apostrophe'", "CODE")
        assert result == "Error (CODE): Error <tag> & \"quotes\" 'apostrophe'"

    def test_error_empty_message(self):
        """Test error with empty message."""
        result = error("")
        assert result == "Error (ERROR): "

    @patch("mcp_nixos.server.requests.post")
    def test_es_query_success(self, mock_post):
        """Test successful Elasticsearch query."""
        mock_resp = Mock()
        mock_resp.json.return_value = {"hits": {"hits": [{"_source": {"test": "data"}}]}}
        mock_post.return_value = mock_resp

        result = es_query("test-index", {"match_all": {}})
        assert len(result) == 1
        assert result[0]["_source"]["test"] == "data"

        # Verify request parameters
        mock_post.assert_called_once_with(
            f"{NIXOS_API}/test-index/_search",
            json={"query": {"match_all": {}}, "size": 20},
            auth=NIXOS_AUTH,
            timeout=10,
        )

    @patch("mcp_nixos.server.requests.post")
    def test_es_query_custom_size(self, mock_post):
        """Test Elasticsearch query with custom size."""
        mock_resp = Mock()
        mock_resp.json.return_value = {"hits": {"hits": []}}
        mock_post.return_value = mock_resp

        es_query("test-index", {"match_all": {}}, size=50)

        # Verify size parameter
        call_args = mock_post.call_args[1]
        assert call_args["json"]["size"] == 50

    @patch("mcp_nixos.server.requests.post")
    def test_es_query_http_error(self, mock_post):
        """Test Elasticsearch query with HTTP error."""
        mock_resp = Mock()
        mock_resp.raise_for_status.side_effect = requests.HTTPError("404 Not Found")
        mock_post.return_value = mock_resp

        with pytest.raises(Exception, match="API error: 404 Not Found"):
            es_query("test-index", {"match_all": {}})

    @patch("mcp_nixos.server.requests.post")
    def test_es_query_connection_error(self, mock_post):
        """Test Elasticsearch query with connection error."""
        mock_post.side_effect = requests.ConnectionError("Connection failed")

        with pytest.raises(Exception, match="API error: Connection failed"):
            es_query("test-index", {"match_all": {}})

    @patch("mcp_nixos.server.requests.post")
    def test_es_query_missing_hits(self, mock_post):
        """Test Elasticsearch query with missing hits field."""
        mock_resp = Mock()
        mock_resp.json.return_value = {}  # No hits field
        mock_post.return_value = mock_resp

        result = es_query("test-index", {"match_all": {}})
        assert result == []

    @patch("mcp_nixos.server.requests.get")
    def test_parse_html_options_success(self, mock_get):
        """Test successful HTML parsing."""
        mock_resp = Mock()
        html_content = """
        <html>
            <dt>programs.git.enable</dt>
            <dd>
                <p>Enable git</p>
                <span class="term">Type: boolean</span>
            </dd>
            <dt>programs.vim.enable</dt>
            <dd>
                <p>Enable vim</p>
                <span class="term">Type: boolean</span>
            </dd>
        </html>
        """
        mock_resp.content = html_content.encode("utf-8")
        mock_resp.raise_for_status = Mock()
        mock_get.return_value = mock_resp

        result = parse_html_options("http://test.com")
        assert len(result) == 2
        assert result[0]["name"] == "programs.git.enable"
        assert result[0]["description"] == "Enable git"
        assert result[0]["type"] == "boolean"

    @patch("mcp_nixos.server.requests.get")
    def test_parse_html_options_with_query(self, mock_get):
        """Test HTML parsing with query filter."""
        mock_resp = Mock()
        html_content = """
        <html>
            <dt>programs.git.enable</dt>
            <dd><p>Enable git</p></dd>
            <dt>programs.vim.enable</dt>
            <dd><p>Enable vim</p></dd>
        </html>
        """
        mock_resp.content = html_content.encode("utf-8")
        mock_resp.raise_for_status = Mock()
        mock_get.return_value = mock_resp

        result = parse_html_options("http://test.com", query="git")
        assert len(result) == 1
        assert result[0]["name"] == "programs.git.enable"

    @patch("mcp_nixos.server.requests.get")
    def test_parse_html_options_with_prefix(self, mock_get):
        """Test HTML parsing with prefix filter."""
        mock_resp = Mock()
        html_content = """
        <html>
            <dt>programs.git.enable</dt>
            <dd><p>Enable git</p></dd>
            <dt>services.nginx.enable</dt>
            <dd><p>Enable nginx</p></dd>
        </html>
        """
        mock_resp.content = html_content.encode("utf-8")
        mock_resp.raise_for_status = Mock()
        mock_get.return_value = mock_resp

        result = parse_html_options("http://test.com", prefix="programs")
        assert len(result) == 1
        assert result[0]["name"] == "programs.git.enable"

    @patch("mcp_nixos.server.requests.get")
    def test_parse_html_options_empty_response(self, mock_get):
        """Test HTML parsing with empty response."""
        mock_resp = Mock()
        mock_resp.content = b"<html></html>"
        mock_resp.raise_for_status = Mock()
        mock_get.return_value = mock_resp

        result = parse_html_options("http://test.com")
        assert not result

    @patch("mcp_nixos.server.requests.get")
    def test_parse_html_options_connection_error(self, mock_get):
        """Test HTML parsing with connection error."""
        mock_get.side_effect = requests.ConnectionError("Failed to connect")

        with pytest.raises(Exception, match="Failed to fetch docs: Failed to connect"):
            parse_html_options("http://test.com")

    @patch("mcp_nixos.server.requests.get")
    def test_parse_html_options_limit(self, mock_get):
        """Test HTML parsing with limit."""
        mock_resp = Mock()
        # Create many options
        options_html = ""
        for i in range(10):
            options_html += f"<dt>option.{i}</dt><dd><p>desc{i}</p></dd>"
        mock_resp.content = f"<html>{options_html}</html>".encode()
        mock_resp.raise_for_status = Mock()
        mock_get.return_value = mock_resp

        result = parse_html_options("http://test.com", limit=5)
        assert len(result) == 5

    @patch("mcp_nixos.server.requests.get")
    def test_parse_html_options_windows_1252_encoding(self, mock_get):
        """Test HTML parsing with windows-1252 encoding."""
        # Create HTML content with special characters
        html_content = """
        <html>
            <head><meta charset="windows-1252"></head>
            <dt>programs.git.userName</dt>
            <dd>
                <p>Git user name with special chars: café</p>
                <span class="term">Type: string</span>
            </dd>
        </html>
        """

        mock_resp = Mock()
        # Simulate windows-1252 encoded content
        mock_resp.content = html_content.encode("windows-1252")
        mock_resp.encoding = "windows-1252"
        mock_resp.raise_for_status = Mock()
        mock_get.return_value = mock_resp

        # Should not raise encoding errors
        result = parse_html_options("http://test.com")
        assert len(result) == 1
        assert result[0]["name"] == "programs.git.userName"
        assert "café" in result[0]["description"]

    @patch("mcp_nixos.server.requests.get")
    def test_parse_html_options_utf8_with_bom(self, mock_get):
        """Test HTML parsing with UTF-8 BOM."""
        html_content = """
        <html>
            <dt>programs.neovim.enable</dt>
            <dd>
                <p>Enable Neovim with unicode: 你好</p>
                <span class="term">Type: boolean</span>
            </dd>
        </html>
        """

        mock_resp = Mock()
        # Add UTF-8 BOM at the beginning
        mock_resp.content = b"\xef\xbb\xbf" + html_content.encode("utf-8")
        mock_resp.encoding = "utf-8-sig"
        mock_resp.raise_for_status = Mock()
        mock_get.return_value = mock_resp

        result = parse_html_options("http://test.com")
        assert len(result) == 1
        assert result[0]["name"] == "programs.neovim.enable"
        assert "你好" in result[0]["description"]

    @patch("mcp_nixos.server.requests.get")
    def test_parse_html_options_iso_8859_1_encoding(self, mock_get):
        """Test HTML parsing with ISO-8859-1 encoding."""
        html_content = """
        <html>
            <head><meta charset="iso-8859-1"></head>
            <dt>services.nginx.virtualHosts</dt>
            <dd>
                <p>Nginx config with special: naïve résumé</p>
            </dd>
        </html>
        """

        mock_resp = Mock()
        # Simulate ISO-8859-1 encoded content
        mock_resp.content = html_content.encode("iso-8859-1")
        mock_resp.encoding = "iso-8859-1"
        mock_resp.raise_for_status = Mock()
        mock_get.return_value = mock_resp

        result = parse_html_options("http://test.com")
        assert len(result) == 1
        assert result[0]["name"] == "services.nginx.virtualHosts"
        assert "naïve" in result[0]["description"]
        assert "résumé" in result[0]["description"]


class TestNixOSTools:
    """Test all NixOS tools."""

    @patch("mcp_nixos.server.es_query")
    @pytest.mark.asyncio
    async def test_nixos_search_packages_success(self, mock_query):
        """Test successful package search."""
        mock_query.return_value = [
            {
                "_source": {
                    "package_pname": "firefox",
                    "package_pversion": "123.0",
                    "package_description": "A web browser",
                }
            }
        ]

        result = await nixos_search("firefox", search_type="packages", limit=5)
        assert "Found 1 packages matching 'firefox':" in result
        assert "• firefox (123.0)" in result
        assert "  A web browser" in result

    @patch("mcp_nixos.server.es_query")
    @pytest.mark.asyncio
    async def test_nixos_search_options_success(self, mock_query):
        """Test successful option search."""
        mock_query.return_value = [
            {
                "_source": {
                    "option_name": "services.nginx.enable",
                    "option_type": "boolean",
                    "option_description": "Enable nginx",
                }
            }
        ]

        result = await nixos_search("nginx", search_type="options")
        assert "Found 1 options matching 'nginx':" in result
        assert "• services.nginx.enable" in result
        assert "  Type: boolean" in result
        assert "  Enable nginx" in result

    @patch("mcp_nixos.server.es_query")
    @pytest.mark.asyncio
    async def test_nixos_search_programs_success(self, mock_query):
        """Test successful program search."""
        mock_query.return_value = [{"_source": {"package_pname": "vim", "package_programs": ["vim", "vi"]}}]

        result = await nixos_search("vim", search_type="programs")
        assert "Found 1 programs matching 'vim':" in result
        assert "• vim (provided by vim)" in result

    @patch("mcp_nixos.server.es_query")
    @pytest.mark.asyncio
    async def test_nixos_search_empty_results(self, mock_query):
        """Test search with no results."""
        mock_query.return_value = []

        result = await nixos_search("nonexistent")
        assert result == "No packages found matching 'nonexistent'"

    @pytest.mark.asyncio
    async def test_nixos_search_invalid_type(self):
        """Test search with invalid type."""
        result = await nixos_search("test", search_type="invalid")
        assert result == "Error (ERROR): Invalid type 'invalid'"

    @pytest.mark.asyncio
    async def test_nixos_search_invalid_channel(self):
        """Test search with invalid channel."""
        result = await nixos_search("test", channel="invalid")
        assert "Error (ERROR): Invalid channel 'invalid'" in result
        assert "Available channels:" in result

    @pytest.mark.asyncio
    async def test_nixos_search_invalid_limit_low(self):
        """Test search with limit too low."""
        result = await nixos_search("test", limit=0)
        assert result == "Error (ERROR): Limit must be 1-100"

    @pytest.mark.asyncio
    async def test_nixos_search_invalid_limit_high(self):
        """Test search with limit too high."""
        result = await nixos_search("test", limit=101)
        assert result == "Error (ERROR): Limit must be 1-100"

    @patch("mcp_nixos.server.es_query")
    @pytest.mark.asyncio
    async def test_nixos_search_all_channels(self, mock_query):
        """Test search works with all defined channels."""
        mock_query.return_value = []

        channels = get_channels()
        for channel in channels:
            result = await nixos_search("test", channel=channel)
            assert result == "No packages found matching 'test'"

            # Verify correct index is used
            mock_query.assert_called_with(
                channels[channel],
                {
                    "bool": {
                        "must": [{"term": {"type": "package"}}],
                        "should": [
                            {"match": {"package_pname": {"query": "test", "boost": 3}}},
                            {"match": {"package_description": "test"}},
                        ],
                        "minimum_should_match": 1,
                    }
                },
                20,
            )

    @patch("mcp_nixos.server.es_query")
    @pytest.mark.asyncio
    async def test_nixos_search_exception_handling(self, mock_query):
        """Test search with API exception."""
        mock_query.side_effect = Exception("API failed")

        result = await nixos_search("test")
        assert result == "Error (ERROR): API failed"

    @patch("mcp_nixos.server.es_query")
    @pytest.mark.asyncio
    async def test_nixos_info_package_found(self, mock_query):
        """Test info when package found."""
        mock_query.return_value = [
            {
                "_source": {
                    "package_pname": "firefox",
                    "package_pversion": "123.0",
                    "package_description": "A web browser",
                    "package_homepage": ["https://firefox.com"],
                    "package_license_set": ["MPL-2.0"],
                }
            }
        ]

        result = await nixos_info("firefox", type="package")
        assert "Package: firefox" in result
        assert "Version: 123.0" in result
        assert "Description: A web browser" in result
        assert "Homepage: https://firefox.com" in result
        assert "License: MPL-2.0" in result

    @patch("mcp_nixos.server.es_query")
    @pytest.mark.asyncio
    async def test_nixos_info_option_found(self, mock_query):
        """Test info when option found."""
        mock_query.return_value = [
            {
                "_source": {
                    "option_name": "services.nginx.enable",
                    "option_type": "boolean",
                    "option_description": "Enable nginx",
                    "option_default": "false",
                    "option_example": "true",
                }
            }
        ]

        result = await nixos_info("services.nginx.enable", type="option")
        assert "Option: services.nginx.enable" in result
        assert "Type: boolean" in result
        assert "Description: Enable nginx" in result
        assert "Default: false" in result
        assert "Example: true" in result

    @patch("mcp_nixos.server.es_query")
    @pytest.mark.asyncio
    async def test_nixos_info_not_found(self, mock_query):
        """Test info when package/option not found."""
        mock_query.return_value = []

        result = await nixos_info("nonexistent", type="package")
        assert result == "Error (NOT_FOUND): Package 'nonexistent' not found"

    @pytest.mark.asyncio
    async def test_nixos_info_invalid_type(self):
        """Test info with invalid type."""
        result = await nixos_info("test", type="invalid")
        assert result == "Error (ERROR): Type must be 'package' or 'option'"

    @patch("mcp_nixos.server.requests.post")
    @pytest.mark.asyncio
    async def test_nixos_stats_success(self, mock_post):
        """Test stats retrieval."""
        # Mock package count
        pkg_resp = Mock()
        pkg_resp.json.return_value = {"count": 95000}

        # Mock option count
        opt_resp = Mock()
        opt_resp.json.return_value = {"count": 18000}

        mock_post.side_effect = [pkg_resp, opt_resp]

        result = await nixos_stats()
        assert "NixOS Statistics for unstable channel:" in result
        assert "• Packages: 95,000" in result
        assert "• Options: 18,000" in result

    @pytest.mark.asyncio
    async def test_nixos_stats_invalid_channel(self):
        """Test stats with invalid channel."""
        result = await nixos_stats(channel="invalid")
        assert "Error (ERROR): Invalid channel 'invalid'" in result
        assert "Available channels:" in result

    @patch("mcp_nixos.server.requests.post")
    @pytest.mark.asyncio
    async def test_nixos_stats_api_error(self, mock_post):
        """Test stats with API error."""
        mock_post.side_effect = requests.ConnectionError("Failed")

        result = await nixos_stats()
        assert result == "Error (ERROR): Failed to retrieve statistics"


class TestHomeManagerTools:
    """Test all Home Manager tools."""

    @patch("mcp_nixos.server.parse_html_options")
    @pytest.mark.asyncio
    async def test_home_manager_search_success(self, mock_parse):
        """Test successful Home Manager search."""
        mock_parse.return_value = [{"name": "programs.git.enable", "type": "boolean", "description": "Enable git"}]

        result = await home_manager_search("git")
        assert "Found 1 Home Manager options matching 'git':" in result
        assert "• programs.git.enable" in result
        assert "  Type: boolean" in result
        assert "  Enable git" in result

        # Verify parse was called correctly
        mock_parse.assert_called_once_with(HOME_MANAGER_URL, "git", "", 20)

    @pytest.mark.asyncio
    async def test_home_manager_search_invalid_limit(self):
        """Test Home Manager search with invalid limit."""
        result = await home_manager_search("test", limit=0)
        assert result == "Error (ERROR): Limit must be 1-100"

    @patch("mcp_nixos.server.parse_html_options")
    @pytest.mark.asyncio
    async def test_home_manager_search_exception(self, mock_parse):
        """Test Home Manager search with exception."""
        mock_parse.side_effect = Exception("Parse failed")

        result = await home_manager_search("test")
        assert result == "Error (ERROR): Parse failed"

    @patch("mcp_nixos.server.parse_html_options")
    @pytest.mark.asyncio
    async def test_home_manager_info_found(self, mock_parse):
        """Test Home Manager info when option found."""
        mock_parse.return_value = [{"name": "programs.git.enable", "type": "boolean", "description": "Enable git"}]

        result = await home_manager_info("programs.git.enable")
        assert "Option: programs.git.enable" in result
        assert "Type: boolean" in result
        assert "Description: Enable git" in result

    @patch("mcp_nixos.server.parse_html_options")
    @pytest.mark.asyncio
    async def test_home_manager_info_not_found(self, mock_parse):
        """Test Home Manager info when option not found."""
        mock_parse.return_value = [{"name": "programs.vim.enable", "type": "boolean", "description": "Enable vim"}]

        result = await home_manager_info("programs.git.enable")
        assert result == (
            "Error (NOT_FOUND): Option 'programs.git.enable' not found.\n"
            "Tip: Use home_manager_options_by_prefix('programs.git.enable') to browse available options."
        )

    @patch("requests.get")
    @pytest.mark.asyncio
    async def test_home_manager_stats(self, mock_get):
        """Test Home Manager stats message."""
        mock_html = """
        <html>
        <body>
            <dl class="variablelist">
                <dt id="opt-programs.git.enable">programs.git.enable</dt>
                <dd>Enable git</dd>
                <dt id="opt-services.gpg-agent.enable">services.gpg-agent.enable</dt>
                <dd>Enable gpg-agent</dd>
            </dl>
        </body>
        </html>
        """
        mock_resp = Mock()
        mock_resp.content = mock_html.encode("utf-8")
        mock_resp.raise_for_status = Mock()
        mock_get.return_value = mock_resp

        result = await home_manager_stats()
        assert "Home Manager Statistics:" in result
        assert "Total options:" in result
        assert "Categories:" in result

    @patch("mcp_nixos.server.parse_html_options")
    @pytest.mark.asyncio
    async def test_home_manager_list_options_success(self, mock_parse):
        """Test Home Manager list options."""
        mock_parse.return_value = [
            {"name": "programs.git.enable", "type": "", "description": ""},
            {"name": "programs.vim.enable", "type": "", "description": ""},
            {"name": "services.ssh.enable", "type": "", "description": ""},
        ]

        result = await home_manager_list_options()
        assert "Home Manager option categories (2 total):" in result
        assert "• programs (2 options)" in result
        assert "• services (1 options)" in result

    @patch("mcp_nixos.server.parse_html_options")
    @pytest.mark.asyncio
    async def test_home_manager_options_by_prefix_success(self, mock_parse):
        """Test Home Manager options by prefix."""
        mock_parse.return_value = [
            {"name": "programs.git.enable", "type": "boolean", "description": "Enable git"},
            {"name": "programs.git.userName", "type": "string", "description": "Git user name"},
        ]

        result = await home_manager_options_by_prefix("programs.git")
        assert "Home Manager options with prefix 'programs.git' (2 found):" in result
        assert "• programs.git.enable" in result
        assert "• programs.git.userName" in result


class TestDarwinTools:
    """Test all Darwin tools."""

    @patch("mcp_nixos.server.parse_html_options")
    @pytest.mark.asyncio
    async def test_darwin_search_success(self, mock_parse):
        """Test successful Darwin search."""
        mock_parse.return_value = [
            {"name": "system.defaults.dock.autohide", "type": "boolean", "description": "Auto-hide the dock"}
        ]

        result = await darwin_search("dock")
        assert "Found 1 nix-darwin options matching 'dock':" in result
        assert "• system.defaults.dock.autohide" in result

    @pytest.mark.asyncio
    async def test_darwin_search_invalid_limit(self):
        """Test Darwin search with invalid limit."""
        result = await darwin_search("test", limit=101)
        assert result == "Error (ERROR): Limit must be 1-100"

    @patch("mcp_nixos.server.parse_html_options")
    @pytest.mark.asyncio
    async def test_darwin_info_found(self, mock_parse):
        """Test Darwin info when option found."""
        mock_parse.return_value = [
            {"name": "system.defaults.dock.autohide", "type": "boolean", "description": "Auto-hide the dock"}
        ]

        result = await darwin_info("system.defaults.dock.autohide")
        assert "Option: system.defaults.dock.autohide" in result
        assert "Type: boolean" in result
        assert "Description: Auto-hide the dock" in result

    @patch("requests.get")
    @pytest.mark.asyncio
    async def test_darwin_stats(self, mock_get):
        """Test Darwin stats message."""
        mock_html = """
        <html>
        <body>
            <dl>
                <dt>system.defaults.dock.autohide</dt>
                <dd>Auto-hide the dock</dd>
                <dt>services.nix-daemon.enable</dt>
                <dd>Enable nix-daemon</dd>
            </dl>
        </body>
        </html>
        """
        mock_resp = Mock()
        mock_resp.content = mock_html.encode("utf-8")
        mock_resp.raise_for_status = Mock()
        mock_get.return_value = mock_resp

        result = await darwin_stats()
        assert "nix-darwin Statistics:" in result
        assert "Total options:" in result
        assert "Categories:" in result

    @patch("mcp_nixos.server.parse_html_options")
    @pytest.mark.asyncio
    async def test_darwin_list_options_success(self, mock_parse):
        """Test Darwin list options."""
        mock_parse.return_value = [
            {"name": "system.defaults.dock.autohide", "type": "", "description": ""},
            {"name": "homebrew.enable", "type": "", "description": ""},
        ]

        result = await darwin_list_options()
        assert "nix-darwin option categories (2 total):" in result
        assert "• system (1 options)" in result
        assert "• homebrew (1 options)" in result

    @patch("mcp_nixos.server.parse_html_options")
    @pytest.mark.asyncio
    async def test_darwin_options_by_prefix_success(self, mock_parse):
        """Test Darwin options by prefix."""
        mock_parse.return_value = [
            {"name": "system.defaults.dock.autohide", "type": "boolean", "description": "Auto-hide the dock"}
        ]

        result = await darwin_options_by_prefix("system.defaults")
        assert "nix-darwin options with prefix 'system.defaults' (1 found):" in result
        assert "• system.defaults.dock.autohide" in result


class TestEdgeCases:
    """Test edge cases and error conditions."""

    @patch("mcp_nixos.server.es_query")
    @pytest.mark.asyncio
    async def test_empty_search_query(self, mock_query):
        """Test search with empty query."""
        mock_query.return_value = []

        result = await nixos_search("")
        assert "No packages found matching ''" in result

    @patch("mcp_nixos.server.es_query")
    @pytest.mark.asyncio
    async def test_special_characters_in_query(self, mock_query):
        """Test search with special characters."""
        mock_query.return_value = []

        result = await nixos_search("test@#$%")
        assert "No packages found matching 'test@#$%'" in result

    @patch("mcp_nixos.server.requests.get")
    def test_malformed_html_response(self, mock_get):
        """Test parsing malformed HTML."""
        mock_resp = Mock()
        mock_resp.content = b"<html><dt>broken"  # Malformed HTML
        mock_resp.raise_for_status = Mock()
        mock_get.return_value = mock_resp

        # Should not crash, just return empty or partial results
        result = parse_html_options("http://test.com")
        assert isinstance(result, list)

    @patch("mcp_nixos.server.es_query")
    @pytest.mark.asyncio
    async def test_missing_fields_in_response(self, mock_query):
        """Test handling missing fields in API response."""
        mock_query.return_value = [{"_source": {"package_pname": "test"}}]  # Missing version and description

        result = await nixos_search("test")
        assert "• test ()" in result  # Should handle missing version gracefully

    @patch("mcp_nixos.server.requests.post")
    @pytest.mark.asyncio
    async def test_timeout_handling(self, mock_post):
        """Test handling of request timeouts."""
        mock_post.side_effect = requests.Timeout("Request timed out")

        result = await nixos_stats()
        assert "Error (ERROR):" in result


class TestServerIntegration:
    """Test server module integration."""

    def test_mcp_instance_exists(self):
        """Test that mcp instance is properly initialized."""
        assert mcp is not None
        assert hasattr(mcp, "tool")

    def test_constants_defined(self):
        """Test that all required constants are defined."""
        assert NIXOS_API == "https://search.nixos.org/backend"
        assert NIXOS_AUTH == ("aWVSALXpZv", "X8gPHnzL52wFEekuxsfQ9cSh")
        assert HOME_MANAGER_URL == "https://nix-community.github.io/home-manager/options.xhtml"
        assert DARWIN_URL == "https://nix-darwin.github.io/nix-darwin/manual/index.html"
        channels = get_channels()
        assert "unstable" in channels
        assert "stable" in channels

    def test_all_tools_decorated(self):
        """Test that all tool functions are properly decorated."""
        # Tool functions should be registered with mcp and have underlying functions
        tool_names = [
            "nixos_search",
            "nixos_info",
            "nixos_stats",
            "home_manager_search",
            "home_manager_info",
            "home_manager_stats",
            "home_manager_list_options",
            "home_manager_options_by_prefix",
            "darwin_search",
            "darwin_info",
            "darwin_stats",
            "darwin_list_options",
            "darwin_options_by_prefix",
        ]

        for tool_name in tool_names:
            # FastMCP decorates functions, so they should have the original function available
            tool = getattr(server, tool_name)
            assert hasattr(tool, "fn"), f"Tool {tool_name} should have 'fn' attribute"
            assert callable(tool.fn), f"Tool {tool_name}.fn should be callable"

```
Page 2/3FirstPrevNextLast