blob: a4c98be6bbf3bc4f07e72f56ef00fbcaa57d4c28 [file] [log] [blame]
package grpclogging
import (
"bytes"
"context"
"math"
"testing"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/encoding/protojson"
pb "go.skia.org/infra/cabe/go/grpclogging/proto"
cpb "go.skia.org/infra/cabe/go/proto"
"go.skia.org/infra/go/now"
"go.skia.org/infra/kube/go/authproxy"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var (
startTime = time.Date(2022, time.January, 31, 2, 2, 3, 0, time.FixedZone("UTC+1", 60*60))
)
func testSetupLogger(t *testing.T) (*now.TimeTravelCtx, *GRPCLogger, *bytes.Buffer) {
ttCtx := now.TimeTravelingContext(startTime)
buf := &bytes.Buffer{}
l := New(buf)
return ttCtx, l, buf
}
func entryFromBuf(t *testing.T, buf *bytes.Buffer) *pb.Entry {
entry := &pb.Entry{}
err := protojson.Unmarshal(buf.Bytes(), entry)
require.NoError(t, err)
return entry
}
func assertLoggedServerUnary(t *testing.T, entry *pb.Entry, req *cpb.GetAnalysisRequest) {
loggedReq := &cpb.GetAnalysisRequest{}
err := entry.ServerUnary.Request.UnmarshalTo(loggedReq)
require.NoError(t, err)
assert.Equal(t, loggedReq.PinpointJobId, req.PinpointJobId)
if entry.StatusCode == int32(codes.OK) {
loggedResp := &cpb.GetAnalysisResponse{}
err = entry.ServerUnary.Response.UnmarshalTo(loggedResp)
require.NoError(t, err)
}
}
func TestServerUnaryLoggingInterceptor_noError(t *testing.T) {
ttCtx, l, buf := testSetupLogger(t)
req := &cpb.GetAnalysisRequest{
PinpointJobId: "d3c4f84d",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
ttCtx.SetTime(startTime.Add(3 * time.Second))
return &cpb.GetAnalysisResponse{}, nil
}
resp, err := l.ServerUnaryLoggingInterceptor(ttCtx, req, &grpc.UnaryServerInfo{FullMethod: "test.service/TestMethod"}, handler)
require.NoError(t, err)
assert.NotNil(t, resp)
entry := entryFromBuf(t, buf)
assert.Equal(t, entry.ServerUnary.FullMethod, "test.service/TestMethod")
assert.Equal(t, int64(3), entry.Elapsed.Seconds)
assertLoggedServerUnary(t, entry, req)
}
func TestServerUnaryLoggingInterceptor_withNaNs(t *testing.T) {
ttCtx, l, buf := testSetupLogger(t)
req := &cpb.GetAnalysisRequest{
PinpointJobId: "d3c4f84d",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
ttCtx.SetTime(startTime.Add(3 * time.Second))
return &cpb.GetAnalysisResponse{
Results: []*cpb.AnalysisResult{
{
Statistic: &cpb.Statistic{
Upper: math.NaN(),
},
},
},
}, nil
}
resp, err := l.ServerUnaryLoggingInterceptor(ttCtx, req, &grpc.UnaryServerInfo{FullMethod: "test.service/TestMethod"}, handler)
require.NoError(t, err)
assert.NotNil(t, resp)
entry := entryFromBuf(t, buf)
assert.Equal(t, entry.ServerUnary.FullMethod, "test.service/TestMethod")
assert.Equal(t, int64(3), entry.Elapsed.Seconds)
assertLoggedServerUnary(t, entry, req)
}
func TestServerUnaryLoggingInterceptor_withAuthProxyUser(t *testing.T) {
ttCtx, l, buf := testSetupLogger(t)
req := &cpb.GetAnalysisRequest{
PinpointJobId: "d3c4f84d",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
ttCtx.SetTime(startTime.Add(3 * time.Second))
return &cpb.GetAnalysisResponse{}, nil
}
md := metadata.New(map[string]string{
authproxy.WebAuthHeaderName: "user@domain.com",
})
resp, err := l.ServerUnaryLoggingInterceptor(
metadata.NewIncomingContext(ttCtx.Context, md),
req, &grpc.UnaryServerInfo{FullMethod: "test.service/TestMethod"}, handler)
require.NoError(t, err)
assert.NotNil(t, resp)
entry := entryFromBuf(t, buf)
assert.Equal(t, entry.ServerUnary.FullMethod, "test.service/TestMethod")
assert.Equal(t, int64(3), entry.Elapsed.Seconds)
assert.Equal(t, "user@domain.com", entry.ServerUnary.User)
assertLoggedServerUnary(t, entry, req)
}
func TestServerUnaryLoggingInterceptor_error(t *testing.T) {
ttCtx, l, buf := testSetupLogger(t)
req := &cpb.GetAnalysisRequest{
PinpointJobId: "d3c4f84d",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
ttCtx.SetTime(startTime.Add(1 * time.Second))
return nil, status.Errorf(codes.InvalidArgument, "forced error response")
}
resp, err := l.ServerUnaryLoggingInterceptor(ttCtx, req, &grpc.UnaryServerInfo{FullMethod: "test.service/TestMethod"}, handler)
require.Error(t, err)
assert.Nil(t, resp)
entry := entryFromBuf(t, buf)
assert.Equal(t, int32(codes.InvalidArgument), entry.StatusCode)
assert.Equal(t, int64(1), entry.Elapsed.Seconds)
assertLoggedServerUnary(t, entry, req)
}
func TestClientUnaryLoggingInterceptor_noError(t *testing.T) {
ttCtx, l, buf := testSetupLogger(t)
req := &cpb.GetAnalysisRequest{
PinpointJobId: "d3c4f84d",
}
invoker := func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, opts ...grpc.CallOption) error {
ttCtx.SetTime(startTime.Add(3 * time.Second))
pb, _ := reply.(*cpb.GetAnalysisResponse)
pb.Results = []*cpb.AnalysisResult{}
return nil
}
resp := &cpb.GetAnalysisResponse{}
err := l.ClientUnaryLoggingInterceptor(ttCtx, "test.service/TestMethod", req, resp, nil, invoker, nil)
require.NoError(t, err)
assert.NotNil(t, resp)
entry := entryFromBuf(t, buf)
assert.Equal(t, entry.ClientUnary.Method, "test.service/TestMethod")
assert.Equal(t, int64(3), entry.Elapsed.Seconds)
}
func TestClientUnaryLoggingInterceptor_error(t *testing.T) {
ttCtx, l, buf := testSetupLogger(t)
req := &cpb.GetAnalysisRequest{
PinpointJobId: "d3c4f84d",
}
invoker := func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, opts ...grpc.CallOption) error {
ttCtx.SetTime(startTime.Add(3 * time.Second))
return status.Errorf(codes.InvalidArgument, "forced error response")
}
resp := &cpb.GetAnalysisResponse{}
err := l.ClientUnaryLoggingInterceptor(ttCtx, "test.service/TestMethod", req, resp, nil, invoker, nil)
require.Error(t, err)
entry := entryFromBuf(t, buf)
assert.Equal(t, entry.ClientUnary.Method, "test.service/TestMethod")
assert.Equal(t, int64(3), entry.Elapsed.Seconds)
assert.Equal(t, int32(codes.InvalidArgument), entry.StatusCode)
}
func TestClientStreamLoggingInterceptor_noError(t *testing.T) {
ttCtx, l, buf := testSetupLogger(t)
streamer := func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, opts ...grpc.CallOption) (grpc.ClientStream, error) {
ttCtx.SetTime(startTime.Add(3 * time.Second))
return nil, nil
}
desc := &grpc.StreamDesc{
StreamName: "test/stream",
}
_, err := l.ClientStreamLoggingInterceptor(ttCtx, desc, nil, "test.service/TestMethod", streamer)
require.NoError(t, err)
entry := entryFromBuf(t, buf)
assert.Equal(t, entry.ClientStream.Method, "test.service/TestMethod")
assert.Equal(t, int64(3), entry.Elapsed.Seconds)
}
func TestClientStreamLoggingInterceptor_error(t *testing.T) {
ttCtx, l, buf := testSetupLogger(t)
streamer := func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, opts ...grpc.CallOption) (grpc.ClientStream, error) {
ttCtx.SetTime(startTime.Add(3 * time.Second))
return nil, status.Errorf(codes.InvalidArgument, "forced error response")
}
desc := &grpc.StreamDesc{
StreamName: "test/stream",
}
_, err := l.ClientStreamLoggingInterceptor(ttCtx, desc, nil, "test.service/TestMethod", streamer)
require.Error(t, err)
entry := entryFromBuf(t, buf)
assert.Equal(t, entry.ClientStream.Method, "test.service/TestMethod")
assert.Equal(t, int64(3), entry.Elapsed.Seconds)
assert.Equal(t, int32(codes.InvalidArgument), entry.StatusCode)
}