feat(05-02): add CreateCable method and CableRecord type to netbox client
- CableRecord type added to types.go (ID, HWID, Label, TestData, CatalogStatus) - CreateCable(ctx, label, assetTag, testDataJSON) uses DcimCablesCreate - Sets test_data and catalog_status custom fields; hw_id if assetTag non-empty - Rejects empty label with sentinel error message - Unit tests use httptest.NewUnstartedServer (201 success, 422 error, empty label)
This commit is contained in:
parent
db287c238f
commit
7908d40af3
3 changed files with 113 additions and 0 deletions
|
|
@ -97,6 +97,31 @@ func (c *Client) CreateDevice(ctx context.Context, name, assetTag string, device
|
|||
return int64(result.GetId()), nil
|
||||
}
|
||||
|
||||
// CreateCable creates a new cable record in NetBox with the given label, optional asset tag,
|
||||
// and test data JSON. Returns the new cable's NetBox ID or an error.
|
||||
// The test_data and catalog_status custom fields are set from testDataJSON and "complete".
|
||||
func (c *Client) CreateCable(ctx context.Context, label, assetTag, testDataJSON string) (int64, error) {
|
||||
if label == "" {
|
||||
return 0, fmt.Errorf("cable label must not be empty")
|
||||
}
|
||||
req := nb.NewWritableCableRequest()
|
||||
req.SetLabel(label)
|
||||
customFields := map[string]interface{}{
|
||||
"test_data": testDataJSON,
|
||||
"catalog_status": "complete",
|
||||
}
|
||||
if assetTag != "" {
|
||||
customFields["hw_id"] = assetTag
|
||||
}
|
||||
req.SetCustomFields(customFields)
|
||||
result, _, err := c.api.DcimAPI.DcimCablesCreate(ctx).
|
||||
WritableCableRequest(*req).Execute()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("CreateCable %q: %w", label, err)
|
||||
}
|
||||
return int64(result.GetId()), nil
|
||||
}
|
||||
|
||||
// DeleteDevice removes a device from NetBox by its internal ID.
|
||||
// Used primarily for test cleanup after CreateDevice integration tests.
|
||||
func (c *Client) DeleteDevice(ctx context.Context, id int64) error {
|
||||
|
|
|
|||
|
|
@ -2,8 +2,12 @@ package netbox_test
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.georgsen.dk/hwlab/internal/netbox"
|
||||
|
|
@ -116,3 +120,78 @@ func TestCreateDeviceLive(t *testing.T) {
|
|||
t.Logf("cleanup warning: DeleteDevice(%d): %v", id, err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCreateCable_EmptyLabel verifies that CreateCable rejects an empty label
|
||||
// without making any NetBox API call.
|
||||
func TestCreateCable_EmptyLabel(t *testing.T) {
|
||||
c, err := netbox.NewClient("http://localhost:8001", "token")
|
||||
if err != nil {
|
||||
t.Fatalf("NewClient: %v", err)
|
||||
}
|
||||
_, err = c.CreateCable(context.Background(), "", "HW-00001", `{"cable_type":0}`)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for empty label, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "cable label must not be empty") {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCreateCable_Success verifies that CreateCable returns a positive ID on 201.
|
||||
// Uses httptest.NewUnstartedServer so srv.URL is accessible inside the handler closure.
|
||||
func TestCreateCable_Success(t *testing.T) {
|
||||
var srvURL string
|
||||
srv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodPost && r.URL.Path == "/api/dcim/cables/" {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
// Cable model requires id, url, and display fields.
|
||||
_ = json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"id": 42,
|
||||
"url": srvURL + "/api/dcim/cables/42/",
|
||||
"display": "USB-C 2m Cable",
|
||||
"label": "USB-C 2m Cable",
|
||||
})
|
||||
return
|
||||
}
|
||||
http.NotFound(w, r)
|
||||
}))
|
||||
srv.Start()
|
||||
srvURL = srv.URL
|
||||
defer srv.Close()
|
||||
|
||||
c, err := netbox.NewClient(srv.URL, "token")
|
||||
if err != nil {
|
||||
t.Fatalf("NewClient: %v", err)
|
||||
}
|
||||
id, err := c.CreateCable(context.Background(), "USB-C 2m Cable", "HW-00042", `{"cable_type":0}`)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateCable: %v", err)
|
||||
}
|
||||
if id <= 0 {
|
||||
t.Fatalf("expected id > 0, got %d", id)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCreateCable_UnprocessableEntity verifies that a 422 from NetBox returns an error.
|
||||
func TestCreateCable_UnprocessableEntity(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodPost && r.URL.Path == "/api/dcim/cables/" {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusUnprocessableEntity)
|
||||
_ = json.NewEncoder(w).Encode(map[string]string{"label": "This field may not be blank."})
|
||||
return
|
||||
}
|
||||
http.NotFound(w, r)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
c, err := netbox.NewClient(srv.URL, "token")
|
||||
if err != nil {
|
||||
t.Fatalf("NewClient: %v", err)
|
||||
}
|
||||
_, err = c.CreateCable(context.Background(), "USB-C 2m Cable", "HW-00099", `{}`)
|
||||
if err == nil {
|
||||
t.Fatal("expected error on 422, got nil")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,15 @@ type Device struct {
|
|||
LastUpdated time.Time
|
||||
}
|
||||
|
||||
// CableRecord represents a HWLab cable item backed by a NetBox cable record.
|
||||
type CableRecord struct {
|
||||
ID int
|
||||
HWID string
|
||||
Label string
|
||||
TestData string // raw JSON blob from test run
|
||||
CatalogStatus string
|
||||
}
|
||||
|
||||
// CustomFields holds all HWLab-defined NetBox custom field values for a device.
|
||||
// NetBox returns these as map[string]interface{} — we provide typed access.
|
||||
type CustomFields struct {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue