Files
nmon/internal/api/profiles_test.go
jleung 345daaa773 Profiles for EdgeSwitch, UniFi switch/AP and VyOS; profile pinning and re-match; whole-row navigation
Discovery re-matches unpinned devices every run and at startup; an admin can pin a
profile (PUT /devices/{slug} profile, GET /profiles, DeviceView profile_pinned and
profile_match). Rows on the device list, interface search and poller health navigate on
click and Enter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lx9FNLG2CqeBbVGGd9bFsP
2026-10-23 05:03:17 +00:00

80 lines
2.7 KiB
Go

package api_test
import (
"context"
"net/http"
"testing"
"github.com/v10networks/nmon/internal/api"
"github.com/v10networks/nmon/internal/profile"
"github.com/v10networks/nmon/internal/store"
)
func profileEnv(t *testing.T) *env {
t.Helper()
set, err := profile.Load("")
if err != nil {
t.Fatal(err)
}
return newEnvDeps(t, 0, func(d *api.Deps) { d.Profiles = set })
}
func TestListProfiles(t *testing.T) {
e := profileEnv(t)
var got []api.ProfileView
e.getJSON(t, "/api/v1/profiles", &got)
by := map[string]api.ProfileView{}
for _, p := range got {
by[p.Name] = p
}
es, ok := by["ubiquiti-edgeswitch"]
if !ok || es.Source != "shipped" || es.Priority != 30 || len(es.Match.SysObjectID) != 1 || es.Extends != "generic" {
t.Fatalf("edgeswitch = %+v", es)
}
if _, ok := by["generic"]; !ok || len(by) < 15 {
t.Fatalf("%d profiles", len(by))
}
}
func TestPutDeviceProfile(t *testing.T) {
e := profileEnv(t)
d := e.device(t, "sw", "", "", func(d *store.Device) { d.SysObjectID, d.SysDescr = "1.3.6.1.4.1.4413", "EdgeSwitch 12 Fiber" })
var v api.DeviceView
e.getJSON(t, "/api/v1/devices/"+d.Slug, &v)
if v.ProfilePinned || v.ProfileMatch == nil || v.ProfileMatch.MatchedBy != "sysObjectID" {
t.Fatalf("view %+v %+v", v, v.ProfileMatch)
}
if rec := e.do(http.MethodPut, "/api/v1/devices/"+d.Slug, `{"profile":"nope"}`); rec.Code != http.StatusUnprocessableEntity {
t.Fatalf("unknown profile: %d %s", rec.Code, rec.Body)
}
if rec := e.do(http.MethodPut, "/api/v1/devices/"+d.Slug, `{"profile":"vyos"}`); rec.Code != http.StatusOK {
t.Fatalf("pin: %d %s", rec.Code, rec.Body)
}
e.getJSON(t, "/api/v1/devices/"+d.Slug, &v)
if v.Profile != "vyos" || !v.ProfilePinned || v.ProfileMatch.MatchedBy != "pinned" {
t.Fatalf("after pin %+v", v)
}
if rec := e.do(http.MethodPut, "/api/v1/devices/"+d.Slug, `{"profile":""}`); rec.Code != http.StatusOK {
t.Fatalf("auto: %d %s", rec.Code, rec.Body)
}
e.getJSON(t, "/api/v1/devices/"+d.Slug, &v)
if v.Profile != "ubiquiti-edgeswitch" || v.ProfilePinned {
t.Fatalf("after unpin %+v", v)
}
audits, _, err := e.st.ListAudit(context.Background(), store.Page{Limit: 10})
if err != nil || len(audits) < 2 || audits[0].Action != "device.edit" {
t.Fatalf("audit rows %d, %v", len(audits), err)
}
}
func TestGenericDeviceExplainsWhy(t *testing.T) {
e := profileEnv(t)
d := e.device(t, "odd", "", "", func(d *store.Device) { d.SysObjectID, d.SysDescr = "1.3.6.1.4.1.99999.1", "mystery box" })
var v api.DeviceView
e.getJSON(t, "/api/v1/devices/"+d.Slug, &v)
if v.ProfileMatch == nil || v.ProfileMatch.MatchedBy != "default" ||
v.ProfileMatch.GenericReason != "no vendor profile matched sysObjectID 1.3.6.1.4.1.99999.1 or sysDescr" {
t.Fatalf("%+v", v.ProfileMatch)
}
}