test(auth): add unit tests achieving 100% coverage for core, 100% for gRPC plugin, 96.6% for HTTP plugin

This commit is contained in:
ginuerzh
2026-05-22 22:59:30 +08:00
parent 91cae5bdeb
commit e99ef1c951
3 changed files with 1463 additions and 0 deletions
+252
View File
@@ -0,0 +1,252 @@
package auth
import (
"context"
"io"
"net"
"os"
"testing"
"github.com/go-gost/core/auth"
corelogger "github.com/go-gost/core/logger"
"github.com/go-gost/plugin/auth/proto"
xctx "github.com/go-gost/x/ctx"
"github.com/go-gost/x/internal/plugin"
xlogger "github.com/go-gost/x/logger"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/status"
)
func TestMain(m *testing.M) {
corelogger.SetDefault(xlogger.Nop())
os.Exit(m.Run())
}
func newTestGRPCConn(t *testing.T, srv proto.AuthenticatorServer) (*grpc.ClientConn, func()) {
t.Helper()
lis, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
gsrv := grpc.NewServer()
proto.RegisterAuthenticatorServer(gsrv, srv)
go gsrv.Serve(lis)
conn, err := grpc.NewClient(lis.Addr().String(),
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
require.NoError(t, err)
return conn, func() {
conn.Close()
gsrv.Stop()
}
}
func TestGRPCPlugin_NilClient(t *testing.T) {
p := &grpcPlugin{
conn: nil,
client: nil,
log: xlogger.Nop(),
}
id, ok := p.Authenticate(context.Background(), "u", "p")
assert.False(t, ok)
assert.Empty(t, id)
assert.NoError(t, p.Close())
}
func TestGRPCPlugin_Authenticate_Success(t *testing.T) {
conn, cleanup := newTestGRPCConn(t, &testAuthServer{ok: true, id: "user1"})
defer cleanup()
p := &grpcPlugin{
conn: conn,
client: proto.NewAuthenticatorClient(conn),
log: xlogger.Nop(),
}
id, ok := p.Authenticate(context.Background(), "u", "p")
assert.True(t, ok)
assert.Equal(t, "user1", id)
}
func TestGRPCPlugin_Authenticate_Fail(t *testing.T) {
conn, cleanup := newTestGRPCConn(t, &testAuthServer{ok: false, id: ""})
defer cleanup()
p := &grpcPlugin{
conn: conn,
client: proto.NewAuthenticatorClient(conn),
log: xlogger.Nop(),
}
id, ok := p.Authenticate(context.Background(), "u", "p")
assert.False(t, ok)
assert.Empty(t, id)
}
func TestGRPCPlugin_Authenticate_WithService(t *testing.T) {
server := &testAuthServer{ok: true, id: "srv"}
conn, cleanup := newTestGRPCConn(t, server)
defer cleanup()
p := &grpcPlugin{
conn: conn,
client: proto.NewAuthenticatorClient(conn),
log: xlogger.Nop(),
}
id, ok := p.Authenticate(context.Background(), "u", "p", auth.WithService("myservice"))
assert.True(t, ok)
assert.Equal(t, "srv", id)
assert.Equal(t, "myservice", server.lastService)
}
func TestGRPCPlugin_Authenticate_WithClientAddr(t *testing.T) {
server := &testAuthServer{ok: true, id: "addr"}
conn, cleanup := newTestGRPCConn(t, server)
defer cleanup()
p := &grpcPlugin{
conn: conn,
client: proto.NewAuthenticatorClient(conn),
log: xlogger.Nop(),
}
ctx := context.Background()
ctx = xctx.ContextWithSrcAddr(ctx, &net.TCPAddr{IP: net.ParseIP("10.0.0.1"), Port: 1234})
id, ok := p.Authenticate(ctx, "u", "p")
assert.True(t, ok)
assert.Equal(t, "addr", id)
assert.Equal(t, "10.0.0.1:1234", server.lastClient)
}
func TestGRPCPlugin_Authenticate_WithoutClientAddr(t *testing.T) {
server := &testAuthServer{ok: true, id: "noclient"}
conn, cleanup := newTestGRPCConn(t, server)
defer cleanup()
p := &grpcPlugin{
conn: conn,
client: proto.NewAuthenticatorClient(conn),
log: xlogger.Nop(),
}
id, ok := p.Authenticate(context.Background(), "u", "p")
assert.True(t, ok)
assert.Equal(t, "noclient", id)
assert.Empty(t, server.lastClient)
}
func TestGRPCPlugin_Authenticate_ServerError(t *testing.T) {
conn, cleanup := newTestGRPCConn(t, &errorAuthServer{})
defer cleanup()
p := &grpcPlugin{
conn: conn,
client: proto.NewAuthenticatorClient(conn),
log: xlogger.Nop(),
}
id, ok := p.Authenticate(context.Background(), "u", "p")
assert.False(t, ok)
assert.Empty(t, id)
}
func TestNewGRPCPlugin_RealConn(t *testing.T) {
lis, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
gsrv := grpc.NewServer()
proto.RegisterAuthenticatorServer(gsrv, &testAuthServer{ok: true, id: "real"})
go gsrv.Serve(lis)
defer gsrv.Stop()
p := NewGRPCPlugin("test", lis.Addr().String(), plugin.TimeoutOption(500))
require.NotNil(t, p)
id, ok := p.Authenticate(context.Background(), "u", "p")
assert.True(t, ok)
assert.Equal(t, "real", id)
assert.NoError(t, p.(io.Closer).Close())
}
func TestNewGRPCPlugin_InvalidAddr(t *testing.T) {
// Null byte causes grpc.NewClient target parsing to fail synchronously.
// This covers the log.Error(err) path.
// NOTE: depends on grpc-go's net/url control-character validation.
// If a future grpc version changes target parsing, this test may silently
// stop covering the error branch.
p := NewGRPCPlugin("test", "\x00")
require.NotNil(t, p)
// conn is nil, so client is nil, so Authenticate returns false
id, ok := p.Authenticate(context.Background(), "u", "p")
assert.False(t, ok)
assert.Empty(t, id)
}
func TestGRPCPlugin_Close_NilConn(t *testing.T) {
p := &grpcPlugin{
conn: nil,
client: nil,
}
assert.NoError(t, p.Close())
}
func TestGRPCPlugin_Close_NonCloserConn(t *testing.T) {
p := &grpcPlugin{
conn: &nonCloserConn{},
client: nil,
}
assert.NoError(t, p.Close())
}
func TestGRPCPlugin_Close_WithRealConn(t *testing.T) {
conn, cleanup := newTestGRPCConn(t, &testAuthServer{ok: true})
defer cleanup()
p := &grpcPlugin{
conn: conn,
client: nil,
log: xlogger.Nop(),
}
assert.NoError(t, p.Close())
}
// --- test helpers ---
type testAuthServer struct {
proto.UnimplementedAuthenticatorServer
ok bool
id string
lastService string
lastClient string
}
func (s *testAuthServer) Authenticate(ctx context.Context, req *proto.AuthenticateRequest) (*proto.AuthenticateReply, error) {
s.lastService = req.Service
s.lastClient = req.Client
return &proto.AuthenticateReply{Ok: s.ok, Id: s.id}, nil
}
type errorAuthServer struct {
proto.UnimplementedAuthenticatorServer
}
func (s *errorAuthServer) Authenticate(ctx context.Context, req *proto.AuthenticateRequest) (*proto.AuthenticateReply, error) {
return nil, status.Error(codes.Internal, "internal error")
}
type nonCloserConn struct{}
func (n *nonCloserConn) Invoke(ctx context.Context, method string, args any, reply any, opts ...grpc.CallOption) error {
return nil
}
func (n *nonCloserConn) NewStream(ctx context.Context, desc *grpc.StreamDesc, method string, opts ...grpc.CallOption) (grpc.ClientStream, error) {
return nil, nil
}
var _ grpc.ClientConnInterface = (*nonCloserConn)(nil)
var _ io.Closer = (*grpcPlugin)(nil)