689ba36e92
The WrapUDPConn constructor returns nil when either arg is nil, so c.stats is always non-nil in all udpConn methods. Remove 8 redundant nil guards that were inconsistent with packetConn (which already skipped them). Add 65 unit tests across plugin, stats, and wrapper packages covering nil safety, race conditions, double-close, event marshaling, and reset-traffic semantics. Replace magic numbers in tests with named Kind constants.
204 lines
5.1 KiB
Go
204 lines
5.1 KiB
Go
package observer
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"os"
|
|
"testing"
|
|
|
|
corelogger "github.com/go-gost/core/logger"
|
|
"github.com/go-gost/core/observer"
|
|
"github.com/go-gost/plugin/observer/proto"
|
|
xlogger "github.com/go-gost/x/logger"
|
|
xstats "github.com/go-gost/x/observer/stats"
|
|
"github.com/go-gost/x/service"
|
|
"google.golang.org/grpc"
|
|
)
|
|
|
|
func TestMain(m *testing.M) {
|
|
corelogger.SetDefault(xlogger.Nop())
|
|
os.Exit(m.Run())
|
|
}
|
|
|
|
// mockGrpcClient implements proto.ObserverClient for testing.
|
|
type mockGrpcClient struct {
|
|
observeReply *proto.ObserveReply
|
|
observeErr error
|
|
}
|
|
|
|
func (m *mockGrpcClient) Observe(ctx context.Context, in *proto.ObserveRequest, opts ...grpc.CallOption) (*proto.ObserveReply, error) {
|
|
if m.observeErr != nil {
|
|
return nil, m.observeErr
|
|
}
|
|
return m.observeReply, nil
|
|
}
|
|
|
|
type mockGrpcConn struct {
|
|
closed bool
|
|
}
|
|
|
|
func (m *mockGrpcConn) Invoke(ctx context.Context, method string, args, reply interface{}, opts ...grpc.CallOption) error {
|
|
return nil
|
|
}
|
|
|
|
func (m *mockGrpcConn) NewStream(ctx context.Context, desc *grpc.StreamDesc, method string, opts ...grpc.CallOption) (grpc.ClientStream, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (m *mockGrpcConn) Close() error {
|
|
m.closed = true
|
|
return nil
|
|
}
|
|
|
|
func TestNewGRPCPlugin_ReturnsNonNil(t *testing.T) {
|
|
// NewGRPCPlugin should always return a non-nil observer. Even if the
|
|
// underlying gRPC dial would eventually fail, modern gRPC uses lazy
|
|
// connect so the constructor always returns a valid instance.
|
|
p := NewGRPCPlugin("test", "127.0.0.1:65535")
|
|
if p == nil {
|
|
t.Fatal("NewGRPCPlugin should never return nil")
|
|
}
|
|
}
|
|
|
|
func TestGrpcPlugin_Observe_NoOpClient_DoesNotPanic(t *testing.T) {
|
|
// When client is nil (e.g., from connection error or deliberate no-op),
|
|
// Observe should return nil without panicking.
|
|
p := &grpcPlugin{client: nil}
|
|
err := p.Observe(context.Background(), []observer.Event{
|
|
service.ServiceEvent{Kind: "service", Service: "test"},
|
|
})
|
|
if err != nil {
|
|
t.Errorf("no-op observer should return nil on Observe, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestGrpcPlugin_Observe_NilClient(t *testing.T) {
|
|
p := &grpcPlugin{
|
|
conn: nil,
|
|
client: nil,
|
|
}
|
|
err := p.Observe(context.Background(), []observer.Event{
|
|
service.ServiceEvent{Kind: "service", Service: "test"},
|
|
})
|
|
if err != nil {
|
|
t.Errorf("nil client Observe should return nil, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestGrpcPlugin_Observe_EmptyEvents(t *testing.T) {
|
|
p := &grpcPlugin{
|
|
client: &mockGrpcClient{},
|
|
}
|
|
// Empty events should return nil without calling the client
|
|
err := p.Observe(context.Background(), nil)
|
|
if err != nil {
|
|
t.Errorf("empty events should return nil, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestGrpcPlugin_Observe_StatusEvent(t *testing.T) {
|
|
p := &grpcPlugin{
|
|
client: &mockGrpcClient{
|
|
observeReply: &proto.ObserveReply{Ok: true},
|
|
},
|
|
}
|
|
err := p.Observe(context.Background(), []observer.Event{
|
|
service.ServiceEvent{
|
|
Kind: "service",
|
|
Service: "test-svc",
|
|
State: "running",
|
|
Msg: "started",
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Errorf("Observe with status event failed: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestGrpcPlugin_Observe_StatsEvent(t *testing.T) {
|
|
p := &grpcPlugin{
|
|
client: &mockGrpcClient{
|
|
observeReply: &proto.ObserveReply{Ok: true},
|
|
},
|
|
}
|
|
err := p.Observe(context.Background(), []observer.Event{
|
|
xstats.StatsEvent{
|
|
Kind: "service",
|
|
Service: "test-svc",
|
|
Client: "client1",
|
|
TotalConns: 10,
|
|
CurrentConns: 3,
|
|
InputBytes: 1024,
|
|
OutputBytes: 2048,
|
|
TotalErrs: 1,
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Errorf("Observe with stats event failed: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestGrpcPlugin_Observe_ServerError(t *testing.T) {
|
|
p := &grpcPlugin{
|
|
client: &mockGrpcClient{
|
|
observeErr: io.ErrUnexpectedEOF,
|
|
},
|
|
log: xlogger.Nop(),
|
|
}
|
|
err := p.Observe(context.Background(), []observer.Event{
|
|
service.ServiceEvent{Kind: "service", Service: "test"},
|
|
})
|
|
if err == nil {
|
|
t.Error("Observe should return error when server fails")
|
|
}
|
|
}
|
|
|
|
func TestGrpcPlugin_Observe_ReplyNotOk(t *testing.T) {
|
|
p := &grpcPlugin{
|
|
client: &mockGrpcClient{
|
|
observeReply: &proto.ObserveReply{Ok: false},
|
|
},
|
|
}
|
|
err := p.Observe(context.Background(), []observer.Event{
|
|
service.ServiceEvent{Kind: "service", Service: "test"},
|
|
})
|
|
if err == nil {
|
|
t.Error("Observe should return error when reply is not ok")
|
|
}
|
|
}
|
|
|
|
func TestGrpcPlugin_Observe_NilReply(t *testing.T) {
|
|
p := &grpcPlugin{
|
|
client: &mockGrpcClient{
|
|
observeReply: nil,
|
|
},
|
|
}
|
|
err := p.Observe(context.Background(), []observer.Event{
|
|
service.ServiceEvent{Kind: "service", Service: "test"},
|
|
})
|
|
if err == nil {
|
|
t.Error("Observe should return error when reply is nil")
|
|
}
|
|
}
|
|
|
|
func TestGrpcPlugin_Close_NilConn(t *testing.T) {
|
|
p := &grpcPlugin{conn: nil}
|
|
if err := p.Close(); err != nil {
|
|
t.Errorf("Close with nil conn should return nil, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestGrpcPlugin_Close_WithConn(t *testing.T) {
|
|
conn := &mockGrpcConn{}
|
|
p := &grpcPlugin{conn: conn}
|
|
if err := p.Close(); err != nil {
|
|
t.Errorf("Close failed: %v", err)
|
|
}
|
|
if !conn.closed {
|
|
t.Error("Close should close the underlying connection")
|
|
}
|
|
}
|
|
|
|
// Test that grpcPlugin satisfies observer.Observer
|
|
var _ observer.Observer = (*grpcPlugin)(nil)
|