homelabby/internal/netbox/custom_fields_test.go
Mikkel Georgsen 4fc9362519 feat(02-03): POST /api/intake handler with orchestrator and NetBox wiring
- IntakeHandler with IntakeOrchestrator/IntakeNetBoxClient/IntakeCatalogUpdater/IntakeWAQ interfaces
- Validates 1-3 photos, base64-encodes, calls Analyze, allocates HW-ID
- Quick-add mode: confidence >= threshold skips review, creates NetBox record immediately
- WAQ enqueue on NetBox failure returns 202 with queued=true
- nil WAQ + NetBox down returns 503
- Six unit tests: reject-0, reject-4, high-confidence, low-confidence, quick-add, netbox-down
- [Rule 1 - Bug] PatchCustomFields signature changed int -> int64 to match NetBoxOpsClient interface
- [Rule 1 - Bug] UpdateCatalogStatus signature changed int -> int64 for consistency with CreateDevice return type
2026-04-10 05:54:33 +00:00

105 lines
3 KiB
Go

package netbox_test
import (
"context"
"fmt"
"os"
"testing"
"git.georgsen.dk/hwlab/internal/netbox"
)
func TestParseCustomFieldsNil(t *testing.T) {
cf := netbox.ParseCustomFields(nil)
if cf.HWID != "" {
t.Error("expected empty HWID for nil map")
}
}
func TestParseCustomFieldsHWID(t *testing.T) {
raw := map[string]interface{}{
"hw_id": "HW-00001",
"catalog_status": "draft",
}
cf := netbox.ParseCustomFields(raw)
if cf.HWID != "HW-00001" {
t.Errorf("want HW-00001, got %s", cf.HWID)
}
if cf.CatalogStatus != "draft" {
t.Errorf("want draft, got %s", cf.CatalogStatus)
}
}
func TestParseCustomFieldsPhotoURLs(t *testing.T) {
raw := map[string]interface{}{
"photo_urls": []interface{}{"http://a.com/1.jpg", "http://a.com/2.jpg"},
}
cf := netbox.ParseCustomFields(raw)
if len(cf.PhotoURLs) != 2 {
t.Errorf("want 2 photo urls, got %d", len(cf.PhotoURLs))
}
}
func TestBuildCustomFieldsPatch(t *testing.T) {
patch := netbox.BuildCustomFieldsPatch("HW-00001", "draft", nil)
if patch["hw_id"] != "HW-00001" {
t.Errorf("hw_id: want HW-00001, got %v", patch["hw_id"])
}
if patch["catalog_status"] != "draft" {
t.Errorf("catalog_status: want draft, got %v", patch["catalog_status"])
}
if _, ok := patch["photo_urls"]; ok {
t.Error("photo_urls should not be present when nil passed")
}
}
func TestBuildCustomFieldsPatchWithURLs(t *testing.T) {
patch := netbox.BuildCustomFieldsPatch("HW-00001", "indexed", []string{"http://a.com/1.jpg"})
urls, ok := patch["photo_urls"].([]string)
if !ok {
t.Fatal("photo_urls should be []string")
}
if len(urls) != 1 {
t.Errorf("want 1 url, got %d", len(urls))
}
}
// TestPatchCustomFieldsRoundTrip is an integration test that writes and reads back custom fields.
// It requires a real NetBox token and a pre-existing device with a known ID.
func TestPatchCustomFieldsRoundTrip(t *testing.T) {
token := os.Getenv("HWLAB_NETBOX_TOKEN")
if len(token) != 40 {
t.Skip("HWLAB_NETBOX_TOKEN is not a real 40-char token — skipping integration test")
}
deviceIDStr := os.Getenv("HWLAB_TEST_DEVICE_ID")
if deviceIDStr == "" {
t.Skip("HWLAB_TEST_DEVICE_ID not set — skipping round-trip integration test")
}
var deviceID int64
if _, err := fmt.Sscanf(deviceIDStr, "%d", &deviceID); err != nil {
t.Fatalf("HWLAB_TEST_DEVICE_ID must be an integer: %v", err)
}
c, err := netbox.NewClient("http://10.5.0.130:8000/api", token)
if err != nil {
t.Fatalf("NewClient: %v", err)
}
patch := netbox.BuildCustomFieldsPatch("HW-99999", "draft", nil)
if err := c.PatchCustomFields(context.Background(), deviceID, patch); err != nil {
t.Fatalf("PatchCustomFields: %v", err)
}
device, err := c.GetDevice(context.Background(), int(deviceID))
if err != nil {
t.Fatalf("GetDevice: %v", err)
}
if device.CustomFields.HWID != "HW-99999" {
t.Errorf("round-trip hw_id: want HW-99999, got %q", device.CustomFields.HWID)
}
if device.CustomFields.CatalogStatus != "draft" {
t.Errorf("round-trip catalog_status: want draft, got %q", device.CustomFields.CatalogStatus)
}
}