package inventory import ( "bytes" "encoding/json" "os" "path/filepath" "testing" "time" ) func TestLoadInventory(t *testing.T) { tests := []struct { name string inventoryJSON string wantErr bool validate func(*Inventory) bool }{ { name: "valid inventory", inventoryJSON: `{ "timestamp": "2024-01-01T12:00:00Z", "system": { "os": "Darwin", "kernel_release": "23.0.0", "macos_version": "14.0", "hostname": "macbook", "username": "user" }, "homebrew": [ {"name": "curl", "version": "8.0.0", "source": "homebrew"} ], "python": [], "node": [], "go": [], "applications": [] }`, wantErr: false, validate: func(inv *Inventory) bool { return inv.System.OS == "Darwin" && len(inv.Homebrew) == 1 && inv.Homebrew[0].Name == "curl" }, }, { name: "empty inventory", inventoryJSON: `{ "timestamp": "2024-01-01T12:00:00Z", "system": { "os": "Darwin", "kernel_release": "23.0.0", "macos_version": "14.0", "hostname": "macbook", "username": "user" }, "homebrew": [], "python": [], "node": [], "go": [], "applications": [] }`, wantErr: false, validate: func(inv *Inventory) bool { return inv.CountPackages() == 0 }, }, { name: "invalid JSON", inventoryJSON: `{invalid json}`, wantErr: true, validate: nil, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Create temporary file tmpFile := filepath.Join(t.TempDir(), "inventory.json") if err := os.WriteFile(tmpFile, []byte(tt.inventoryJSON), 0o644); err != nil { t.Fatalf("failed to create temp file: %v", err) } inv, err := LoadInventory(tmpFile) if (err != nil) != tt.wantErr { t.Errorf("LoadInventory() error = %v, wantErr %v", err, tt.wantErr) return } if err == nil && tt.validate != nil { if !tt.validate(inv) { t.Errorf("LoadInventory() validation failed") } } }) } } func TestInventoryAllPackages(t *testing.T) { inv := &Inventory{ Timestamp: time.Now(), System: SystemInfo{OS: "Darwin"}, Homebrew: []Package{ {Name: "curl", Version: "8.0", Source: "homebrew"}, }, Python: []Package{ {Name: "requests", Version: "2.28.0", Source: "pip"}, }, Node: []Package{ {Name: "lodash", Version: "4.17.0", Source: "npm"}, }, Go: []Package{}, Applications: []Package{ {Name: "Chrome", Version: "120.0", Source: "application"}, }, } all := inv.AllPackages() if len(all) != 4 { t.Errorf("AllPackages() returned %d packages, want 4", len(all)) } names := make(map[string]bool) for _, pkg := range all { names[pkg.Name] = true } expectedNames := []string{"curl", "requests", "lodash", "Chrome"} for _, name := range expectedNames { if !names[name] { t.Errorf("AllPackages() missing package: %s", name) } } } func TestInventoryCountPackages(t *testing.T) { inv := &Inventory{ Homebrew: make([]Package, 5), Python: make([]Package, 3), Node: make([]Package, 2), Go: make([]Package, 0), Applications: make([]Package, 10), } if count := inv.CountPackages(); count != 20 { t.Errorf("CountPackages() = %d, want 20", count) } } func TestInventoryCountBySource(t *testing.T) { inv := &Inventory{ Homebrew: []Package{ {Name: "curl", Version: "8.0", Source: "homebrew"}, {Name: "git", Version: "2.40", Source: "homebrew"}, }, Python: []Package{ {Name: "requests", Version: "2.28.0", Source: "pip"}, }, Node: []Package{ {Name: "lodash", Version: "4.17.0", Source: "npm"}, }, Go: []Package{}, Applications: []Package{}, } counts := inv.CountBySource() tests := []struct { source string want int }{ {"homebrew", 2}, {"pip", 1}, {"npm", 1}, {"go", 0}, } for _, tt := range tests { if got := counts[tt.source]; got != tt.want { t.Errorf("CountBySource()[%s] = %d, want %d", tt.source, got, tt.want) } } } func TestPackageMarshalJSON(t *testing.T) { pkg := Package{ Name: "curl", Version: "8.0.0", Source: "homebrew", Path: "/usr/local/opt/curl", BundleID: "com.example.curl", } // Should marshal without error data, err := json.Marshal(pkg) if err != nil { t.Errorf("Marshal() error = %v", err) } // Should unmarshal back to same values var unmarshaled Package if err := json.Unmarshal(data, &unmarshaled); err != nil { t.Errorf("Unmarshal() error = %v", err) } if unmarshaled != pkg { t.Errorf("Round-trip marshal/unmarshal failed") } } func TestSystemInfoFields(t *testing.T) { si := SystemInfo{ OS: "Darwin", KernelRelease: "23.0.0", MacOSVersion: "14.0", Hostname: "macbook", Username: "testuser", } data, err := json.Marshal(si) if err != nil { t.Errorf("Marshal() error = %v", err) return } // Verify all fields are present in JSON var raw map[string]interface{} if err := json.Unmarshal(data, &raw); err != nil { t.Errorf("Unmarshal() error = %v", err) return } expectedFields := []string{"os", "kernel_release", "macos_version", "hostname", "username"} for _, field := range expectedFields { if _, ok := raw[field]; !ok { t.Errorf("Missing field in JSON: %s", field) } } } func BenchmarkCountPackages(b *testing.B) { inv := &Inventory{ Homebrew: make([]Package, 50), Python: make([]Package, 100), Node: make([]Package, 75), Go: make([]Package, 25), Applications: make([]Package, 200), } b.ResetTimer() for i := 0; i < b.N; i++ { _ = inv.CountPackages() } } func BenchmarkCountBySource(b *testing.B) { inv := &Inventory{ Homebrew: make([]Package, 50), Python: make([]Package, 100), Node: make([]Package, 75), Go: make([]Package, 25), Applications: make([]Package, 200), } b.ResetTimer() for i := 0; i < b.N; i++ { _ = inv.CountBySource() } } func TestLoadInventoryFileNotFound(t *testing.T) { _, err := LoadInventory("/nonexistent/path/inventory.json") if err == nil { t.Error("LoadInventory() should error for nonexistent file") } } func TestInventoryTimestamp(t *testing.T) { now := time.Now() inv := &Inventory{ Timestamp: now, } data, err := json.Marshal(inv) if err != nil { t.Fatalf("Marshal() error = %v", err) } var unmarshaled Inventory if err := json.Unmarshal(data, &unmarshaled); err != nil { t.Fatalf("Unmarshal() error = %v", err) } // Timestamps should be equal (within millisecond precision) if !inv.Timestamp.Equal(unmarshaled.Timestamp) { t.Errorf("Timestamp mismatch: %v != %v", inv.Timestamp, unmarshaled.Timestamp) } } func TestLargeInventory(t *testing.T) { // Test with realistic inventory size var packages []Package for i := 0; i < 1000; i++ { packages = append(packages, Package{ Name: "pkg" + string(rune(i)), Version: "1.0.0", Source: "homebrew", }) } inv := &Inventory{ Homebrew: packages, } if count := inv.CountPackages(); count != 1000 { t.Errorf("Large inventory: CountPackages() = %d, want 1000", count) } // Should be fast var buf bytes.Buffer enc := json.NewEncoder(&buf) if err := enc.Encode(inv); err != nil { t.Errorf("Large inventory encoding error: %v", err) } }