package main import ( "context" "fmt" "strings" "testing" "github.com/gosnmp/gosnmp" "codeberg.org/towerops-agent/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(context.Background(), &pb.AgentJob{JobId: "1", JobType: pb.JobType_POLL}, ch) result := <-ch if len(result.OidValues) != 0 { t.Errorf("expected empty oid values for nil device, got %d", len(result.OidValues)) } }) 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(context.Background(), &pb.AgentJob{ JobId: "1", SnmpDevice: &pb.SnmpDevice{Ip: "10.0.0.1", Port: 161}, }, ch) result := <-ch if len(result.OidValues) != 0 { t.Errorf("expected empty oid values on dial error, got %d", len(result.OidValues)) } }) 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(context.Background(), &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(context.Background(), &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(context.Background(), &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(context.Background(), &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(context.Background(), &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(context.Background(), &pb.AgentJob{ JobId: "1", SnmpDevice: &pb.SnmpDevice{Ip: "10.0.0.1", Port: 161}, }, ch) result := <-ch if len(result.OidValues) != 0 { t.Errorf("expected empty oid values on dial error, 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(context.Background(), &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(context.Background(), &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(context.Background(), &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(context.Background(), &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 TestExecuteSnmpJobCtxCancelled(t *testing.T) { orig := snmpDial defer func() { snmpDial = orig }() snmpDial = func(dev *pb.SnmpDevice) (snmpQuerier, func(), error) { return &mockSnmpQuerier{ getFunc: func(oids []string) (*gosnmp.SnmpPacket, error) { return &gosnmp.SnmpPacket{}, nil }, }, func() {}, nil } ctx, cancel := context.WithCancel(context.Background()) cancel() // cancel before queries run ch := make(chan *pb.SnmpResult, 1) executeSnmpJob(ctx, &pb.AgentJob{ JobId: "ctx-test", 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"}}, {QueryType: pb.QueryType_GET, Oids: []string{".1.3.6.1.2.1.1.2.0"}}, }, }, ch) // With cancelled context, the function should return before processing queries // (no result sent because it returns early in the ctx.Err() check) select { case <-ch: // Might get a result if the first query ran before ctx check default: // Expected — returned early } } func TestExecuteCredentialTest(t *testing.T) { t.Run("nil device", func(t *testing.T) { ch := make(chan *pb.CredentialTestResult, 1) executeCredentialTest(context.Background(), &pb.AgentJob{JobId: "1"}, ch) result := <-ch if result.Success { t.Error("expected failure for nil device") } if !strings.Contains(result.ErrorMessage, "missing device") { t.Errorf("expected 'missing device' in error, got: %s", result.ErrorMessage) } }) 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(context.Background(), &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(context.Background(), &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(context.Background(), &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(context.Background(), &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) } }) }