This repository has been archived on 2026-03-10. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
labmcp/internal/packages/parser_test.go
Torjus Håkestad ea4c69bc23 feat: add nixpkgs-search binary with package search support
Add a new nixpkgs-search CLI that combines NixOS options search with
Nix package search functionality. This provides two MCP servers from
a single binary:
- `nixpkgs-search options serve` for NixOS options
- `nixpkgs-search packages serve` for Nix packages

Key changes:
- Add packages table to database schema (version 3)
- Add Package type and search methods to database layer
- Create internal/packages/ with indexer and parser for nix-env JSON
- Add MCP server mode (options/packages) with separate tool sets
- Add package handlers: search_packages, get_package
- Create cmd/nixpkgs-search with combined indexing support
- Update flake.nix with nixpkgs-search package (now default)
- Bump version to 0.2.0

The index command can index both options and packages together, or
use --no-packages/--no-options flags for partial indexing.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 17:12:41 +01:00

216 lines
5.5 KiB
Go

package packages
import (
"strings"
"testing"
)
func TestParsePackages(t *testing.T) {
input := `{
"firefox": {
"name": "firefox-120.0",
"pname": "firefox",
"version": "120.0",
"system": "x86_64-linux",
"meta": {
"description": "A web browser built from Firefox source tree",
"homepage": "https://www.mozilla.org/firefox/",
"license": {"spdxId": "MPL-2.0", "fullName": "Mozilla Public License 2.0"},
"maintainers": [
{"name": "John Doe", "github": "johndoe", "githubId": 12345}
],
"platforms": ["x86_64-linux", "aarch64-linux"]
}
},
"python312Packages.requests": {
"name": "python3.12-requests-2.31.0",
"pname": "requests",
"version": "2.31.0",
"system": "x86_64-linux",
"meta": {
"description": "HTTP library for Python",
"homepage": ["https://requests.readthedocs.io/"],
"license": [{"spdxId": "Apache-2.0"}],
"unfree": false
}
}
}`
packages, err := ParsePackages(strings.NewReader(input))
if err != nil {
t.Fatalf("ParsePackages failed: %v", err)
}
if len(packages) != 2 {
t.Errorf("Expected 2 packages, got %d", len(packages))
}
// Check firefox
firefox, ok := packages["firefox"]
if !ok {
t.Fatal("firefox package not found")
}
if firefox.Pname != "firefox" {
t.Errorf("Expected pname 'firefox', got %q", firefox.Pname)
}
if firefox.Version != "120.0" {
t.Errorf("Expected version '120.0', got %q", firefox.Version)
}
if firefox.Homepage != "https://www.mozilla.org/firefox/" {
t.Errorf("Expected homepage 'https://www.mozilla.org/firefox/', got %q", firefox.Homepage)
}
if firefox.License != `["MPL-2.0"]` {
t.Errorf("Expected license '[\"MPL-2.0\"]', got %q", firefox.License)
}
// Check python requests
requests, ok := packages["python312Packages.requests"]
if !ok {
t.Fatal("python312Packages.requests package not found")
}
if requests.Pname != "requests" {
t.Errorf("Expected pname 'requests', got %q", requests.Pname)
}
// Homepage is array, should extract first element
if requests.Homepage != "https://requests.readthedocs.io/" {
t.Errorf("Expected homepage 'https://requests.readthedocs.io/', got %q", requests.Homepage)
}
}
func TestParsePackagesStream(t *testing.T) {
input := `{
"hello": {
"name": "hello-2.12",
"pname": "hello",
"version": "2.12",
"system": "x86_64-linux",
"meta": {
"description": "A program that produces a familiar, friendly greeting"
}
},
"world": {
"name": "world-1.0",
"pname": "world",
"version": "1.0",
"system": "x86_64-linux",
"meta": {}
}
}`
var packages []*ParsedPackage
count, err := ParsePackagesStream(strings.NewReader(input), func(pkg *ParsedPackage) error {
packages = append(packages, pkg)
return nil
})
if err != nil {
t.Fatalf("ParsePackagesStream failed: %v", err)
}
if count != 2 {
t.Errorf("Expected count 2, got %d", count)
}
if len(packages) != 2 {
t.Errorf("Expected 2 packages, got %d", len(packages))
}
}
func TestNormalizeLicense(t *testing.T) {
tests := []struct {
name string
input interface{}
expected string
}{
{"nil", nil, "[]"},
{"string", "MIT", `["MIT"]`},
{"object with spdxId", map[string]interface{}{"spdxId": "MIT"}, `["MIT"]`},
{"object with fullName", map[string]interface{}{"fullName": "MIT License"}, `["MIT License"]`},
{"array of strings", []interface{}{"MIT", "Apache-2.0"}, `["MIT","Apache-2.0"]`},
{"array of objects", []interface{}{
map[string]interface{}{"spdxId": "MIT"},
map[string]interface{}{"spdxId": "Apache-2.0"},
}, `["MIT","Apache-2.0"]`},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
result := normalizeLicense(tc.input)
if result != tc.expected {
t.Errorf("Expected %q, got %q", tc.expected, result)
}
})
}
}
func TestNormalizeHomepage(t *testing.T) {
tests := []struct {
name string
input interface{}
expected string
}{
{"nil", nil, ""},
{"string", "https://example.com", "https://example.com"},
{"array", []interface{}{"https://example.com", "https://docs.example.com"}, "https://example.com"},
{"empty array", []interface{}{}, ""},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
result := normalizeHomepage(tc.input)
if result != tc.expected {
t.Errorf("Expected %q, got %q", tc.expected, result)
}
})
}
}
func TestNormalizeMaintainers(t *testing.T) {
tests := []struct {
name string
maintainers []Maintainer
expected string
}{
{"empty", nil, "[]"},
{"with name", []Maintainer{{Name: "John Doe"}}, `["John Doe"]`},
{"with github only", []Maintainer{{Github: "johndoe"}}, `["@johndoe"]`},
{"multiple", []Maintainer{{Name: "Alice"}, {Name: "Bob"}}, `["Alice","Bob"]`},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
result := normalizeMaintainers(tc.maintainers)
if result != tc.expected {
t.Errorf("Expected %q, got %q", tc.expected, result)
}
})
}
}
func TestSplitAttrPath(t *testing.T) {
tests := []struct {
input string
expected []string
}{
{"firefox", []string{"firefox"}},
{"python312Packages.requests", []string{"python312Packages", "requests"}},
{"haskellPackages.aeson.components.library", []string{"haskellPackages", "aeson", "components", "library"}},
}
for _, tc := range tests {
t.Run(tc.input, func(t *testing.T) {
result := SplitAttrPath(tc.input)
if len(result) != len(tc.expected) {
t.Errorf("Expected %v, got %v", tc.expected, result)
return
}
for i := range result {
if result[i] != tc.expected[i] {
t.Errorf("Expected %v, got %v", tc.expected, result)
return
}
}
})
}
}