homelabby/internal/inventory/quality_gate_test.go
Mikkel Georgsen 1f9621fcaa feat(01-04): quality gate state machine, tag sync, catalog updater
- CatalogStatus type with forward-only state machine (draft→indexed→…→complete)
- Transition() enforces valid transitions, returns error with 'invalid transition' message
- ParseCatalogStatus() validates known status strings
- HardwareRecord domain type composing netbox.Device with quality gate state
- CatalogUpdater.UpdateCatalogStatus() validates transition then PatchCustomFields
- SyncTags() normalizes tags (slug form) and ensures they exist in NetBox
- normalizeTags deduplicates across case/whitespace/space-vs-hyphen variants
- ensureTag uses go-netbox v4 NewTagRequest(name, slug) / ExtrasTagsCreate
- All 12 state machine table-driven tests pass
2026-04-10 05:22:22 +00:00

71 lines
2.2 KiB
Go

package inventory_test
import (
"strings"
"testing"
"git.georgsen.dk/hwlab/internal/inventory"
)
func TestCanTransitionTo(t *testing.T) {
tests := []struct {
from inventory.CatalogStatus
to inventory.CatalogStatus
allowed bool
}{
{inventory.StatusDraft, inventory.StatusIndexed, true},
{inventory.StatusDraft, inventory.StatusComplete, false},
{inventory.StatusDraft, inventory.StatusDraft, false},
{inventory.StatusIndexed, inventory.StatusNeedsResearch, true},
{inventory.StatusIndexed, inventory.StatusResearched, true},
{inventory.StatusIndexed, inventory.StatusDraft, false},
{inventory.StatusNeedsResearch, inventory.StatusResearched, true},
{inventory.StatusNeedsResearch, inventory.StatusIndexed, false},
{inventory.StatusResearched, inventory.StatusComplete, true},
{inventory.StatusResearched, inventory.StatusDraft, false},
{inventory.StatusComplete, inventory.StatusDraft, false},
{inventory.StatusComplete, inventory.StatusResearched, false},
}
for _, tt := range tests {
got := tt.from.CanTransitionTo(tt.to)
if got != tt.allowed {
t.Errorf("%s → %s: want %v, got %v", tt.from, tt.to, tt.allowed, got)
}
}
}
func TestTransitionValid(t *testing.T) {
got, err := inventory.Transition(inventory.StatusDraft, inventory.StatusIndexed)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != inventory.StatusIndexed {
t.Errorf("want indexed, got %s", got)
}
}
func TestTransitionInvalid(t *testing.T) {
_, err := inventory.Transition(inventory.StatusDraft, inventory.StatusComplete)
if err == nil {
t.Fatal("expected error for invalid transition")
}
if !strings.Contains(err.Error(), "invalid transition") {
t.Errorf("error should mention 'invalid transition', got: %v", err)
}
}
func TestParseCatalogStatus(t *testing.T) {
for _, s := range []string{"draft", "indexed", "needs_research", "researched", "complete"} {
cs, err := inventory.ParseCatalogStatus(s)
if err != nil {
t.Errorf("ParseCatalogStatus(%q): unexpected error: %v", s, err)
}
if string(cs) != s {
t.Errorf("ParseCatalogStatus(%q) = %q, want %q", s, cs, s)
}
}
_, err := inventory.ParseCatalogStatus("unknown_status")
if err == nil {
t.Error("expected error for unknown status")
}
}