feat: MCP tools and nixpkgs indexer
- Add options.json parser with mdDoc support - Add nixpkgs indexer using nix-build - Implement all MCP tool handlers: - search_options: Full-text search with filters - get_option: Option details with children - get_file: Fetch file contents - index_revision: Build and index options - list_revisions: Show indexed versions - delete_revision: Remove indexed data - Add parser tests Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
154
internal/nixos/parser_test.go
Normal file
154
internal/nixos/parser_test.go
Normal file
@@ -0,0 +1,154 @@
|
||||
package nixos
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseOptions(t *testing.T) {
|
||||
// Sample options.json content
|
||||
input := `{
|
||||
"services.nginx.enable": {
|
||||
"declarations": ["/nix/store/xxx-source/nixos/modules/services/web-servers/nginx/default.nix"],
|
||||
"default": false,
|
||||
"description": "Whether to enable Nginx Web Server.",
|
||||
"readOnly": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"services.nginx.package": {
|
||||
"declarations": ["/nix/store/xxx-source/nixos/modules/services/web-servers/nginx/default.nix"],
|
||||
"default": {},
|
||||
"description": {"_type": "mdDoc", "text": "The nginx package to use."},
|
||||
"readOnly": false,
|
||||
"type": "package"
|
||||
},
|
||||
"services.caddy.enable": {
|
||||
"declarations": ["/nix/store/xxx-source/nixos/modules/services/web-servers/caddy/default.nix"],
|
||||
"default": false,
|
||||
"description": "Enable Caddy web server",
|
||||
"readOnly": false,
|
||||
"type": "boolean"
|
||||
}
|
||||
}`
|
||||
|
||||
options, err := ParseOptions(strings.NewReader(input))
|
||||
if err != nil {
|
||||
t.Fatalf("ParseOptions failed: %v", err)
|
||||
}
|
||||
|
||||
if len(options) != 3 {
|
||||
t.Errorf("Expected 3 options, got %d", len(options))
|
||||
}
|
||||
|
||||
// Check nginx.enable
|
||||
opt := options["services.nginx.enable"]
|
||||
if opt == nil {
|
||||
t.Fatal("Expected services.nginx.enable option")
|
||||
}
|
||||
if opt.Type != "boolean" {
|
||||
t.Errorf("Type = %q, want boolean", opt.Type)
|
||||
}
|
||||
if opt.Description != "Whether to enable Nginx Web Server." {
|
||||
t.Errorf("Description = %q", opt.Description)
|
||||
}
|
||||
if opt.Default != "false" {
|
||||
t.Errorf("Default = %q, want false", opt.Default)
|
||||
}
|
||||
if len(opt.Declarations) != 1 {
|
||||
t.Errorf("Expected 1 declaration, got %d", len(opt.Declarations))
|
||||
}
|
||||
// Check declaration path normalization
|
||||
if !strings.HasPrefix(opt.Declarations[0], "nixos/") {
|
||||
t.Errorf("Declaration path not normalized: %q", opt.Declarations[0])
|
||||
}
|
||||
|
||||
// Check nginx.package (mdDoc description)
|
||||
opt = options["services.nginx.package"]
|
||||
if opt == nil {
|
||||
t.Fatal("Expected services.nginx.package option")
|
||||
}
|
||||
if opt.Description != "The nginx package to use." {
|
||||
t.Errorf("Description = %q (mdDoc not extracted)", opt.Description)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeDeclarationPath(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expect string
|
||||
}{
|
||||
{
|
||||
"/nix/store/xxx-source/nixos/modules/services/web-servers/nginx/default.nix",
|
||||
"nixos/modules/services/web-servers/nginx/default.nix",
|
||||
},
|
||||
{
|
||||
"/nix/store/xxx/pkgs/top-level/all-packages.nix",
|
||||
"pkgs/top-level/all-packages.nix",
|
||||
},
|
||||
{
|
||||
"/nix/store/abc123/lib/types.nix",
|
||||
"lib/types.nix",
|
||||
},
|
||||
{
|
||||
"relative/path.nix",
|
||||
"relative/path.nix",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.input, func(t *testing.T) {
|
||||
got := normalizeDeclarationPath(tt.input)
|
||||
if got != tt.expect {
|
||||
t.Errorf("normalizeDeclarationPath(%q) = %q, want %q", tt.input, got, tt.expect)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractDescription(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input interface{}
|
||||
expect string
|
||||
}{
|
||||
{
|
||||
"string",
|
||||
"Simple description",
|
||||
"Simple description",
|
||||
},
|
||||
{
|
||||
"mdDoc",
|
||||
map[string]interface{}{
|
||||
"_type": "mdDoc",
|
||||
"text": "Markdown description",
|
||||
},
|
||||
"Markdown description",
|
||||
},
|
||||
{
|
||||
"description key",
|
||||
map[string]interface{}{
|
||||
"description": "Nested description",
|
||||
},
|
||||
"Nested description",
|
||||
},
|
||||
{
|
||||
"nil",
|
||||
nil,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"empty map",
|
||||
map[string]interface{}{},
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := extractDescription(tt.input)
|
||||
if got != tt.expect {
|
||||
t.Errorf("extractDescription = %q, want %q", got, tt.expect)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user