package queue import ( "context" "encoding/json" "errors" "testing" ) // mockNetBoxOpsClient is a test double for NetBoxOpsClient. type mockNetBoxOpsClient struct { createCalls []CreateDevicePayload patchCalls []PatchCustomFieldsPayload createErr error patchErr error } func (m *mockNetBoxOpsClient) CreateDevice(_ context.Context, name, assetTag string, dtID, roleID, siteID int32) (int64, error) { m.createCalls = append(m.createCalls, CreateDevicePayload{ Name: name, AssetTag: assetTag, DeviceTypeID: dtID, RoleID: roleID, SiteID: siteID, }) return 42, m.createErr } func (m *mockNetBoxOpsClient) PatchCustomFields(_ context.Context, deviceID int64, patch map[string]interface{}) error { m.patchCalls = append(m.patchCalls, PatchCustomFieldsPayload{DeviceID: deviceID, Patch: patch}) return m.patchErr } func mustMarshal(t *testing.T, v interface{}) json.RawMessage { t.Helper() data, err := json.Marshal(v) if err != nil { t.Fatalf("marshal test payload: %v", err) } return data } // TestNetBoxOpHandlerRouting: create_device op routes to CreateDevice func TestNetBoxOpHandlerRouting(t *testing.T) { mock := &mockNetBoxOpsClient{} handler := NewNetBoxOpHandler(mock) payload := CreateDevicePayload{ Name: "switch-01", AssetTag: "HW-00001", DeviceTypeID: 1, RoleID: 2, SiteID: 3, } op := PendingOp{ ID: "op-1", Type: OpNetBoxCreateDevice, Payload: mustMarshal(t, payload), } err := handler(context.Background(), op) if err != nil { t.Fatalf("expected no error, got: %v", err) } if len(mock.createCalls) != 1 { t.Fatalf("expected 1 CreateDevice call, got %d", len(mock.createCalls)) } got := mock.createCalls[0] if got.Name != "switch-01" || got.AssetTag != "HW-00001" || got.DeviceTypeID != 1 || got.RoleID != 2 || got.SiteID != 3 { t.Errorf("CreateDevice called with wrong args: %+v", got) } } // TestNetBoxOpHandlerPatchCustomFields: patch_custom_fields op routes to PatchCustomFields func TestNetBoxOpHandlerPatchCustomFields(t *testing.T) { mock := &mockNetBoxOpsClient{} handler := NewNetBoxOpHandler(mock) payload := PatchCustomFieldsPayload{ DeviceID: 99, Patch: map[string]interface{}{"hw_condition": "good", "catalog_status": "indexed"}, } op := PendingOp{ ID: "op-2", Type: OpNetBoxPatchCustomFields, Payload: mustMarshal(t, payload), } err := handler(context.Background(), op) if err != nil { t.Fatalf("expected no error, got: %v", err) } if len(mock.patchCalls) != 1 { t.Fatalf("expected 1 PatchCustomFields call, got %d", len(mock.patchCalls)) } got := mock.patchCalls[0] if got.DeviceID != 99 { t.Errorf("PatchCustomFields called with wrong device ID: %d", got.DeviceID) } } // TestNetBoxOpHandlerUnknownType: unknown op type returns non-nil error func TestNetBoxOpHandlerUnknownType(t *testing.T) { mock := &mockNetBoxOpsClient{} handler := NewNetBoxOpHandler(mock) op := PendingOp{ ID: "op-3", Type: "unknown.op", Payload: json.RawMessage(`{}`), } err := handler(context.Background(), op) if err == nil { t.Fatal("expected non-nil error for unknown op type") } } // TestNetBoxOpHandlerBadJSON: malformed payload returns non-nil error func TestNetBoxOpHandlerBadJSON(t *testing.T) { mock := &mockNetBoxOpsClient{} handler := NewNetBoxOpHandler(mock) op := PendingOp{ ID: "op-4", Type: OpNetBoxCreateDevice, Payload: json.RawMessage(`not valid json`), } err := handler(context.Background(), op) if err == nil { t.Fatal("expected non-nil error for malformed JSON payload") } } // TestCreateDevicePayloadDecode: JSON payload decodes correctly into CreateDevicePayload func TestCreateDevicePayloadDecode(t *testing.T) { raw := json.RawMessage(`{"name":"test","asset_tag":"HW-00001","device_type_id":1,"role_id":2,"site_id":3}`) var p CreateDevicePayload if err := json.Unmarshal(raw, &p); err != nil { t.Fatalf("decode failed: %v", err) } if p.Name != "test" { t.Errorf("Name: got %q, want %q", p.Name, "test") } if p.AssetTag != "HW-00001" { t.Errorf("AssetTag: got %q, want %q", p.AssetTag, "HW-00001") } if p.DeviceTypeID != 1 { t.Errorf("DeviceTypeID: got %d, want 1", p.DeviceTypeID) } if p.RoleID != 2 { t.Errorf("RoleID: got %d, want 2", p.RoleID) } if p.SiteID != 3 { t.Errorf("SiteID: got %d, want 3", p.SiteID) } } // TestNetBoxOpHandlerClientError: handler propagates NetBox client errors func TestNetBoxOpHandlerClientError(t *testing.T) { mock := &mockNetBoxOpsClient{createErr: errors.New("netbox unavailable")} handler := NewNetBoxOpHandler(mock) op := PendingOp{ ID: "op-5", Type: OpNetBoxCreateDevice, Payload: mustMarshal(t, CreateDevicePayload{Name: "router-01", AssetTag: "HW-00002", DeviceTypeID: 1, RoleID: 2, SiteID: 3}), } err := handler(context.Background(), op) if err == nil { t.Fatal("expected error propagated from NetBox client") } }