towerops-agent/snmp_test.go
Graham McIntire 82df96cd59
batch SNMP GETs into chunks of 60 OIDs per PDU
Previously each OID was fetched individually, meaning 1000 OIDs
required 1000 round trips. Now OIDs are batched into groups of 60
(the SNMP max PDU size), reducing round trips by ~50x. Also
pre-sizes the OID value map to avoid repeated rehashing.
2026-02-12 09:41:48 -06:00

776 lines
20 KiB
Go

package main
import (
"fmt"
"testing"
"github.com/gosnmp/gosnmp"
"github.com/towerops-app/towerops-agent/pb"
)
func TestSnmpValueToString(t *testing.T) {
tests := []struct {
name string
pdu gosnmp.SnmpPDU
want string
}{
{
name: "integer",
pdu: gosnmp.SnmpPDU{Type: gosnmp.Integer, Value: 42},
want: "42",
},
{
name: "string",
pdu: gosnmp.SnmpPDU{Type: gosnmp.OctetString, Value: []byte("Linux router")},
want: "Linux router",
},
{
name: "hex bytes",
pdu: gosnmp.SnmpPDU{Type: gosnmp.OctetString, Value: []byte{0x00, 0x1a, 0x2b}},
want: "00:1a:2b",
},
{
name: "oid",
pdu: gosnmp.SnmpPDU{Type: gosnmp.ObjectIdentifier, Value: "1.3.6.1.2.1.1.1.0"},
want: "1.3.6.1.2.1.1.1.0",
},
{
name: "counter32",
pdu: gosnmp.SnmpPDU{Type: gosnmp.Counter32, Value: uint(12345)},
want: "12345",
},
{
name: "counter64",
pdu: gosnmp.SnmpPDU{Type: gosnmp.Counter64, Value: uint64(9876543210)},
want: "9876543210",
},
{
name: "gauge32",
pdu: gosnmp.SnmpPDU{Type: gosnmp.Gauge32, Value: uint(999)},
want: "999",
},
{
name: "timeticks",
pdu: gosnmp.SnmpPDU{Type: gosnmp.TimeTicks, Value: uint32(12345678)},
want: "12345678",
},
{
name: "ip address",
pdu: gosnmp.SnmpPDU{Type: gosnmp.IPAddress, Value: "192.168.1.1"},
want: "192.168.1.1",
},
{
name: "null",
pdu: gosnmp.SnmpPDU{Type: gosnmp.Null, Value: nil},
want: "null",
},
{
name: "no such object",
pdu: gosnmp.SnmpPDU{Type: gosnmp.NoSuchObject, Value: nil},
want: "null",
},
{
name: "invalid utf8",
pdu: gosnmp.SnmpPDU{Type: gosnmp.OctetString, Value: []byte{0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x80, 0xFE}},
want: "48:65:6c:6c:6f:80:fe",
},
{
name: "opaque",
pdu: gosnmp.SnmpPDU{Type: gosnmp.Opaque, Value: []byte{0xDE, 0xAD}},
want: "de:ad",
},
{
name: "end of mib view",
pdu: gosnmp.SnmpPDU{Type: gosnmp.EndOfMibView, Value: nil},
want: "null",
},
{
name: "no such instance",
pdu: gosnmp.SnmpPDU{Type: gosnmp.NoSuchInstance, Value: nil},
want: "null",
},
{
name: "unknown type",
pdu: gosnmp.SnmpPDU{Type: gosnmp.Asn1BER(0xFF), Value: "something"},
want: "something",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := snmpValueToString(tt.pdu)
if got != tt.want {
t.Errorf("got %q, want %q", got, tt.want)
}
})
}
}
func TestMapAuthProtocol(t *testing.T) {
tests := []struct {
input string
want gosnmp.SnmpV3AuthProtocol
}{
{"MD5", gosnmp.MD5},
{"SHA", gosnmp.SHA},
{"SHA-1", gosnmp.SHA},
{"SHA-224", gosnmp.SHA224},
{"SHA-256", gosnmp.SHA256},
{"SHA-384", gosnmp.SHA384},
{"SHA-512", gosnmp.SHA512},
{"unknown", gosnmp.SHA},
}
for _, tt := range tests {
got := mapAuthProtocol(tt.input)
if got != tt.want {
t.Errorf("mapAuthProtocol(%q) = %v, want %v", tt.input, got, tt.want)
}
}
}
func TestMapPrivProtocol(t *testing.T) {
tests := []struct {
input string
want gosnmp.SnmpV3PrivProtocol
}{
{"DES", gosnmp.DES},
{"AES", gosnmp.AES},
{"AES-128", gosnmp.AES},
{"AES-192", gosnmp.AES192},
{"AES-256", gosnmp.AES256},
{"AES-192-C", gosnmp.AES192C},
{"AES-256-C", gosnmp.AES256C},
{"unknown", gosnmp.AES},
}
for _, tt := range tests {
got := mapPrivProtocol(tt.input)
if got != tt.want {
t.Errorf("mapPrivProtocol(%q) = %v, want %v", tt.input, got, tt.want)
}
}
}
func TestFormatHex(t *testing.T) {
tests := []struct {
input []byte
want string
}{
{nil, ""},
{[]byte{}, ""},
{[]byte{0xAB}, "ab"},
{[]byte{0x00, 0xFF, 0x1A}, "00:ff:1a"},
}
for _, tt := range tests {
got := formatHex(tt.input)
if got != tt.want {
t.Errorf("formatHex(%v) = %q, want %q", tt.input, got, tt.want)
}
}
}
func TestNewSnmpConn(t *testing.T) {
tests := []struct {
name string
dev *pb.SnmpDevice
}{
{
name: "v1",
dev: &pb.SnmpDevice{Ip: "127.0.0.1", Port: 0, Version: "v1", Community: "public"},
},
{
name: "v2c default",
dev: &pb.SnmpDevice{Ip: "127.0.0.1", Port: 0, Version: "2c", Community: "public"},
},
{
name: "v2c empty version",
dev: &pb.SnmpDevice{Ip: "127.0.0.1", Port: 0, Community: "public"},
},
{
name: "v3 noAuthNoPriv",
dev: &pb.SnmpDevice{Ip: "127.0.0.1", Port: 0, Version: "v3", V3Username: "user", V3SecurityLevel: "noAuthNoPriv"},
},
{
name: "v3 authNoPriv",
dev: &pb.SnmpDevice{
Ip: "127.0.0.1", Port: 0, Version: "v3",
V3Username: "user", V3SecurityLevel: "authNoPriv",
V3AuthProtocol: "SHA-256", V3AuthPassword: "pass1234",
},
},
{
name: "v3 authPriv",
dev: &pb.SnmpDevice{
Ip: "127.0.0.1", Port: 0, Version: "v3",
V3Username: "user", V3SecurityLevel: "authPriv",
V3AuthProtocol: "SHA-256", V3AuthPassword: "pass1234",
V3PrivProtocol: "AES-256", V3PrivPassword: "priv1234",
},
},
{
name: "tcp transport",
dev: &pb.SnmpDevice{Ip: "127.0.0.1", Port: 0, Version: "2c", Community: "public", Transport: "tcp"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
conn, err := newSnmpConn(tt.dev)
if err != nil {
return
}
defer func() { _ = conn.Conn.Close() }()
// Verify version was set correctly
switch tt.dev.Version {
case "1", "v1":
if conn.Version != gosnmp.Version1 {
t.Errorf("expected Version1, got %v", conn.Version)
}
case "3", "v3":
if conn.Version != gosnmp.Version3 {
t.Errorf("expected Version3, got %v", conn.Version)
}
default:
if conn.Version != gosnmp.Version2c {
t.Errorf("expected Version2c, got %v", conn.Version)
}
}
})
}
}
func TestNewSnmpConnTCPError(t *testing.T) {
// TCP transport on port 1 should fail to connect
_, err := newSnmpConn(&pb.SnmpDevice{Ip: "127.0.0.1", Port: 1, Version: "2c", Community: "public", Transport: "tcp"})
if err == nil {
t.Error("expected connection error on TCP port 1")
}
}
func TestSnmpDialDefault(t *testing.T) {
// Test the default snmpDial function variable (wraps newSnmpConn)
origDial := snmpDial
defer func() { snmpDial = origDial }()
// Reset to default behavior
snmpDial = func(dev *pb.SnmpDevice) (snmpQuerier, func(), error) {
conn, err := newSnmpConn(dev)
if err != nil {
return nil, nil, err
}
return conn, func() { _ = conn.Conn.Close() }, nil
}
q, closeFn, err := snmpDial(&pb.SnmpDevice{Ip: "127.0.0.1", Port: 16100, Version: "2c", Community: "public"})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
defer closeFn()
if q == nil {
t.Error("expected non-nil querier")
}
}
// mockSnmpQuerier implements snmpQuerier for testing.
type mockSnmpQuerier struct {
getFunc func(oids []string) (*gosnmp.SnmpPacket, error)
walkFunc func(rootOid string) ([]gosnmp.SnmpPDU, error)
bulkWalkFunc func(rootOid string) ([]gosnmp.SnmpPDU, error)
closeCalled bool
walkAllCalled bool
bulkWalkCalled bool
}
func (m *mockSnmpQuerier) Get(oids []string) (*gosnmp.SnmpPacket, error) {
return m.getFunc(oids)
}
func (m *mockSnmpQuerier) WalkAll(rootOid string) ([]gosnmp.SnmpPDU, error) {
m.walkAllCalled = true
return m.walkFunc(rootOid)
}
func (m *mockSnmpQuerier) BulkWalkAll(rootOid string) ([]gosnmp.SnmpPDU, error) {
m.bulkWalkCalled = true
if m.bulkWalkFunc != nil {
return m.bulkWalkFunc(rootOid)
}
return m.walkFunc(rootOid)
}
func TestExecuteSnmpJob(t *testing.T) {
t.Run("nil device", func(t *testing.T) {
ch := make(chan *pb.SnmpResult, 1)
executeSnmpJob(&pb.AgentJob{JobId: "1"}, ch)
if len(ch) != 0 {
t.Error("expected no result for nil device")
}
})
t.Run("dial error", func(t *testing.T) {
orig := snmpDial
defer func() { snmpDial = orig }()
snmpDial = func(dev *pb.SnmpDevice) (snmpQuerier, func(), error) {
return nil, nil, fmt.Errorf("connection refused")
}
ch := make(chan *pb.SnmpResult, 1)
executeSnmpJob(&pb.AgentJob{
JobId: "1",
SnmpDevice: &pb.SnmpDevice{Ip: "10.0.0.1", Port: 161},
}, ch)
if len(ch) != 0 {
t.Error("expected no result on dial error")
}
})
t.Run("GET success", func(t *testing.T) {
orig := snmpDial
defer func() { snmpDial = orig }()
mock := &mockSnmpQuerier{
getFunc: func(oids []string) (*gosnmp.SnmpPacket, error) {
return &gosnmp.SnmpPacket{
Variables: []gosnmp.SnmpPDU{
{Name: ".1.3.6.1.2.1.1.1.0", Type: gosnmp.OctetString, Value: []byte("Linux")},
},
}, nil
},
}
snmpDial = func(dev *pb.SnmpDevice) (snmpQuerier, func(), error) {
return mock, func() { mock.closeCalled = true }, nil
}
ch := make(chan *pb.SnmpResult, 1)
executeSnmpJob(&pb.AgentJob{
JobId: "1",
DeviceId: "dev-1",
SnmpDevice: &pb.SnmpDevice{Ip: "10.0.0.1", Port: 161},
Queries: []*pb.SnmpQuery{
{QueryType: pb.QueryType_GET, Oids: []string{".1.3.6.1.2.1.1.1.0"}},
},
}, ch)
if len(ch) != 1 {
t.Fatal("expected one result")
}
result := <-ch
if result.OidValues[".1.3.6.1.2.1.1.1.0"] != "Linux" {
t.Errorf("got %q, want Linux", result.OidValues[".1.3.6.1.2.1.1.1.0"])
}
if !mock.closeCalled {
t.Error("expected close to be called")
}
})
t.Run("WALK success", func(t *testing.T) {
orig := snmpDial
defer func() { snmpDial = orig }()
mock := &mockSnmpQuerier{
walkFunc: func(rootOid string) ([]gosnmp.SnmpPDU, error) {
return []gosnmp.SnmpPDU{
{Name: ".1.3.6.1.2.1.2.2.1.1.1", Type: gosnmp.Integer, Value: 1},
{Name: ".1.3.6.1.2.1.2.2.1.1.2", Type: gosnmp.Integer, Value: 2},
}, nil
},
}
snmpDial = func(dev *pb.SnmpDevice) (snmpQuerier, func(), error) {
return mock, func() {}, nil
}
ch := make(chan *pb.SnmpResult, 1)
executeSnmpJob(&pb.AgentJob{
JobId: "1",
SnmpDevice: &pb.SnmpDevice{Ip: "10.0.0.1"},
Queries: []*pb.SnmpQuery{
{QueryType: pb.QueryType_WALK, Oids: []string{".1.3.6.1.2.1.2.2.1.1"}},
},
}, ch)
result := <-ch
if len(result.OidValues) != 2 {
t.Errorf("got %d oid values, want 2", len(result.OidValues))
}
})
t.Run("GET error continues", func(t *testing.T) {
orig := snmpDial
defer func() { snmpDial = orig }()
mock := &mockSnmpQuerier{
getFunc: func(oids []string) (*gosnmp.SnmpPacket, error) {
return nil, fmt.Errorf("timeout")
},
}
snmpDial = func(dev *pb.SnmpDevice) (snmpQuerier, func(), error) {
return mock, func() {}, nil
}
ch := make(chan *pb.SnmpResult, 1)
executeSnmpJob(&pb.AgentJob{
JobId: "1",
SnmpDevice: &pb.SnmpDevice{Ip: "10.0.0.1"},
Queries: []*pb.SnmpQuery{
{QueryType: pb.QueryType_GET, Oids: []string{".1.3.6.1.2.1.1.1.0"}},
},
}, ch)
result := <-ch
if len(result.OidValues) != 0 {
t.Errorf("got %d oid values, want 0 on error", len(result.OidValues))
}
})
t.Run("WALK error continues", func(t *testing.T) {
orig := snmpDial
defer func() { snmpDial = orig }()
mock := &mockSnmpQuerier{
walkFunc: func(rootOid string) ([]gosnmp.SnmpPDU, error) {
return nil, fmt.Errorf("timeout")
},
}
snmpDial = func(dev *pb.SnmpDevice) (snmpQuerier, func(), error) {
return mock, func() {}, nil
}
ch := make(chan *pb.SnmpResult, 1)
executeSnmpJob(&pb.AgentJob{
JobId: "1",
SnmpDevice: &pb.SnmpDevice{Ip: "10.0.0.1"},
Queries: []*pb.SnmpQuery{
{QueryType: pb.QueryType_WALK, Oids: []string{".1.3.6.1.2.1.2"}},
},
}, ch)
result := <-ch
if len(result.OidValues) != 0 {
t.Errorf("got %d oid values, want 0 on error", len(result.OidValues))
}
})
t.Run("NoSuchObject skipped", func(t *testing.T) {
orig := snmpDial
defer func() { snmpDial = orig }()
mock := &mockSnmpQuerier{
getFunc: func(oids []string) (*gosnmp.SnmpPacket, error) {
return &gosnmp.SnmpPacket{
Variables: []gosnmp.SnmpPDU{
{Name: ".1.3.6.1.2.1.1.1.0", Type: gosnmp.NoSuchObject, Value: nil},
},
}, nil
},
}
snmpDial = func(dev *pb.SnmpDevice) (snmpQuerier, func(), error) {
return mock, func() {}, nil
}
ch := make(chan *pb.SnmpResult, 1)
executeSnmpJob(&pb.AgentJob{
JobId: "1",
SnmpDevice: &pb.SnmpDevice{Ip: "10.0.0.1"},
Queries: []*pb.SnmpQuery{
{QueryType: pb.QueryType_GET, Oids: []string{".1.3.6.1.2.1.1.1.0"}},
},
}, ch)
result := <-ch
if len(result.OidValues) != 0 {
t.Errorf("NoSuchObject should be skipped, got %d oid values", len(result.OidValues))
}
})
t.Run("WALK NoSuchObject skipped", func(t *testing.T) {
orig := snmpDial
defer func() { snmpDial = orig }()
mock := &mockSnmpQuerier{
walkFunc: func(rootOid string) ([]gosnmp.SnmpPDU, error) {
return []gosnmp.SnmpPDU{
{Name: ".1.3.6.1.2.1.2.2.1.1.1", Type: gosnmp.Integer, Value: 1},
{Name: ".1.3.6.1.2.1.2.2.1.1.2", Type: gosnmp.NoSuchInstance, Value: nil},
{Name: ".1.3.6.1.2.1.2.2.1.1.3", Type: gosnmp.EndOfMibView, Value: nil},
}, nil
},
}
snmpDial = func(dev *pb.SnmpDevice) (snmpQuerier, func(), error) {
return mock, func() {}, nil
}
ch := make(chan *pb.SnmpResult, 1)
executeSnmpJob(&pb.AgentJob{
JobId: "1",
SnmpDevice: &pb.SnmpDevice{Ip: "10.0.0.1"},
Queries: []*pb.SnmpQuery{
{QueryType: pb.QueryType_WALK, Oids: []string{".1.3.6.1.2.1.2.2.1.1"}},
},
}, ch)
result := <-ch
if len(result.OidValues) != 1 {
t.Errorf("expected 1 value (others skipped), got %d", len(result.OidValues))
}
})
t.Run("WALK v1 uses WalkAll", func(t *testing.T) {
orig := snmpDial
defer func() { snmpDial = orig }()
mock := &mockSnmpQuerier{
walkFunc: func(rootOid string) ([]gosnmp.SnmpPDU, error) {
return []gosnmp.SnmpPDU{
{Name: ".1.3.6.1.2.1.2.2.1.1.1", Type: gosnmp.Integer, Value: 1},
}, nil
},
}
snmpDial = func(dev *pb.SnmpDevice) (snmpQuerier, func(), error) {
return mock, func() {}, nil
}
ch := make(chan *pb.SnmpResult, 1)
executeSnmpJob(&pb.AgentJob{
JobId: "1",
SnmpDevice: &pb.SnmpDevice{Ip: "10.0.0.1", Version: "1"},
Queries: []*pb.SnmpQuery{
{QueryType: pb.QueryType_WALK, Oids: []string{".1.3.6.1.2.1.2.2.1.1"}},
},
}, ch)
result := <-ch
if len(result.OidValues) != 1 {
t.Errorf("got %d oid values, want 1", len(result.OidValues))
}
if !mock.walkAllCalled {
t.Error("expected WalkAll to be called for v1")
}
if mock.bulkWalkCalled {
t.Error("BulkWalkAll should not be called for v1")
}
})
t.Run("WALK v2c uses BulkWalkAll", func(t *testing.T) {
orig := snmpDial
defer func() { snmpDial = orig }()
mock := &mockSnmpQuerier{
walkFunc: func(rootOid string) ([]gosnmp.SnmpPDU, error) {
return []gosnmp.SnmpPDU{
{Name: ".1.3.6.1.2.1.2.2.1.1.1", Type: gosnmp.Integer, Value: 1},
}, nil
},
}
snmpDial = func(dev *pb.SnmpDevice) (snmpQuerier, func(), error) {
return mock, func() {}, nil
}
ch := make(chan *pb.SnmpResult, 1)
executeSnmpJob(&pb.AgentJob{
JobId: "1",
SnmpDevice: &pb.SnmpDevice{Ip: "10.0.0.1", Version: "2c"},
Queries: []*pb.SnmpQuery{
{QueryType: pb.QueryType_WALK, Oids: []string{".1.3.6.1.2.1.2.2.1.1"}},
},
}, ch)
result := <-ch
if len(result.OidValues) != 1 {
t.Errorf("got %d oid values, want 1", len(result.OidValues))
}
if !mock.bulkWalkCalled {
t.Error("expected BulkWalkAll to be called for v2c")
}
if mock.walkAllCalled {
t.Error("WalkAll should not be called for v2c")
}
})
t.Run("channel full drops", func(t *testing.T) {
orig := snmpDial
defer func() { snmpDial = orig }()
mock := &mockSnmpQuerier{
getFunc: func(oids []string) (*gosnmp.SnmpPacket, error) {
return &gosnmp.SnmpPacket{}, nil
},
}
snmpDial = func(dev *pb.SnmpDevice) (snmpQuerier, func(), error) {
return mock, func() {}, nil
}
ch := make(chan *pb.SnmpResult) // unbuffered, no reader — will be full
executeSnmpJob(&pb.AgentJob{
JobId: "1",
SnmpDevice: &pb.SnmpDevice{Ip: "10.0.0.1"},
}, ch)
// Should not block — the result is dropped
})
}
func TestExecuteSnmpJobBatchesGets(t *testing.T) {
orig := snmpDial
defer func() { snmpDial = orig }()
var getCalls [][]string
mock := &mockSnmpQuerier{
getFunc: func(oids []string) (*gosnmp.SnmpPacket, error) {
getCalls = append(getCalls, oids)
var vars []gosnmp.SnmpPDU
for _, oid := range oids {
vars = append(vars, gosnmp.SnmpPDU{Name: oid, Type: gosnmp.Integer, Value: 42})
}
return &gosnmp.SnmpPacket{Variables: vars}, nil
},
}
snmpDial = func(dev *pb.SnmpDevice) (snmpQuerier, func(), error) {
return mock, func() {}, nil
}
// Create 150 OIDs — should be split into 3 batches of 60, 60, 30
oids := make([]string, 150)
for i := range oids {
oids[i] = fmt.Sprintf(".1.3.6.1.2.1.1.%d.0", i)
}
ch := make(chan *pb.SnmpResult, 1)
executeSnmpJob(&pb.AgentJob{
JobId: "batch-test",
DeviceId: "dev-1",
SnmpDevice: &pb.SnmpDevice{Ip: "10.0.0.1", Port: 161},
Queries: []*pb.SnmpQuery{
{QueryType: pb.QueryType_GET, Oids: oids},
},
}, ch)
result := <-ch
if len(result.OidValues) != 150 {
t.Errorf("got %d oid values, want 150", len(result.OidValues))
}
if len(getCalls) != 3 {
t.Errorf("got %d GET calls, want 3 (batches of 60)", len(getCalls))
}
for i, call := range getCalls {
if i < 2 && len(call) != 60 {
t.Errorf("batch %d: got %d oids, want 60", i, len(call))
}
if i == 2 && len(call) != 30 {
t.Errorf("batch %d: got %d oids, want 30", i, len(call))
}
}
}
func TestExecuteCredentialTest(t *testing.T) {
t.Run("nil device", func(t *testing.T) {
ch := make(chan *pb.CredentialTestResult, 1)
executeCredentialTest(&pb.AgentJob{JobId: "1"}, ch)
if len(ch) != 0 {
t.Error("expected no result for nil device")
}
})
t.Run("dial error", func(t *testing.T) {
orig := snmpDial
defer func() { snmpDial = orig }()
snmpDial = func(dev *pb.SnmpDevice) (snmpQuerier, func(), error) {
return nil, nil, fmt.Errorf("connection refused")
}
ch := make(chan *pb.CredentialTestResult, 1)
executeCredentialTest(&pb.AgentJob{
JobId: "test-1",
SnmpDevice: &pb.SnmpDevice{Ip: "10.0.0.1", Port: 161},
}, ch)
result := <-ch
if result.Success {
t.Error("expected failure")
}
if result.ErrorMessage == "" {
t.Error("expected error message")
}
})
t.Run("get error", func(t *testing.T) {
orig := snmpDial
defer func() { snmpDial = orig }()
mock := &mockSnmpQuerier{
getFunc: func(oids []string) (*gosnmp.SnmpPacket, error) {
return nil, fmt.Errorf("timeout")
},
}
snmpDial = func(dev *pb.SnmpDevice) (snmpQuerier, func(), error) {
return mock, func() {}, nil
}
ch := make(chan *pb.CredentialTestResult, 1)
executeCredentialTest(&pb.AgentJob{
JobId: "test-1",
SnmpDevice: &pb.SnmpDevice{Ip: "10.0.0.1"},
}, ch)
result := <-ch
if result.Success {
t.Error("expected failure on get error")
}
})
t.Run("success", func(t *testing.T) {
orig := snmpDial
defer func() { snmpDial = orig }()
mock := &mockSnmpQuerier{
getFunc: func(oids []string) (*gosnmp.SnmpPacket, error) {
return &gosnmp.SnmpPacket{
Variables: []gosnmp.SnmpPDU{
{Name: ".1.3.6.1.2.1.1.1.0", Type: gosnmp.OctetString, Value: []byte("RouterOS 7.1")},
},
}, nil
},
}
snmpDial = func(dev *pb.SnmpDevice) (snmpQuerier, func(), error) {
return mock, func() {}, nil
}
ch := make(chan *pb.CredentialTestResult, 1)
executeCredentialTest(&pb.AgentJob{
JobId: "test-1",
SnmpDevice: &pb.SnmpDevice{Ip: "10.0.0.1"},
}, ch)
result := <-ch
if !result.Success {
t.Error("expected success")
}
if result.SystemDescription != "RouterOS 7.1" {
t.Errorf("sysDescr: got %q, want %q", result.SystemDescription, "RouterOS 7.1")
}
})
t.Run("success no variables", func(t *testing.T) {
orig := snmpDial
defer func() { snmpDial = orig }()
mock := &mockSnmpQuerier{
getFunc: func(oids []string) (*gosnmp.SnmpPacket, error) {
return &gosnmp.SnmpPacket{Variables: nil}, nil
},
}
snmpDial = func(dev *pb.SnmpDevice) (snmpQuerier, func(), error) {
return mock, func() {}, nil
}
ch := make(chan *pb.CredentialTestResult, 1)
executeCredentialTest(&pb.AgentJob{
JobId: "test-1",
SnmpDevice: &pb.SnmpDevice{Ip: "10.0.0.1"},
}, ch)
result := <-ch
if !result.Success {
t.Error("expected success even with no variables")
}
if result.SystemDescription != "" {
t.Errorf("expected empty sysDescr, got %q", result.SystemDescription)
}
})
}