Skip to main content
Glama

mcp-nixos

by utensils
test_options.py24 kB
"""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

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/utensils/mcp-nixos'

If you have feedback or need assistance with the MCP directory API, please join our Discord server