| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266 |
- package grpcclient
- import (
- "context"
- "io"
- "net"
- "sync/atomic"
- "testing"
- "time"
- pbv1 "git3.techno-world.net/lrosales/broad-announce/gen/go/broadannounce/v1"
- "google.golang.org/grpc"
- "google.golang.org/grpc/codes"
- "google.golang.org/grpc/credentials/insecure"
- "google.golang.org/grpc/metadata"
- "google.golang.org/grpc/test/bufconn"
- )
- const bufnetSize = 1 << 20 // 1 MB
- // mockIngestServer implements pbv1.IngestServer for testing.
- type mockIngestServer struct {
- pbv1.UnimplementedIngestServer
- alertsReceived atomic.Int64
- rateLimitAfter int64 // after N alerts, start rate-limiting
- authKey string
- authFail atomic.Bool
- }
- func (m *mockIngestServer) StreamAlerts(stream pbv1.Ingest_StreamAlertsServer) error {
- md, ok := metadata.FromIncomingContext(stream.Context())
- if !ok {
- return io.EOF
- }
- if m.authFail.Load() {
- return io.EOF
- }
- if len(m.authKey) > 0 {
- vals := md.Get("authorization")
- if len(vals) == 0 || vals[0] != "Bearer "+m.authKey {
- return io.EOF
- }
- }
- for {
- alert, err := stream.Recv()
- if err == io.EOF {
- return nil
- }
- if err != nil {
- return err
- }
- m.alertsReceived.Add(1)
- ack := &pbv1.Ack{
- AlertId: alert.DedupeKey,
- DedupeKey: alert.DedupeKey,
- AcceptedAtMs: time.Now().UnixMilli(),
- }
- if m.rateLimitAfter > 0 && m.alertsReceived.Load() > m.rateLimitAfter {
- ack.Result = &pbv1.Ack_Error{
- Error: &pbv1.Error{
- Code: pbv1.Error_RATE_LIMITED,
- Message: "rate limited by test server",
- RetryAfterMs: 10,
- },
- }
- } else {
- ack.Result = &pbv1.Ack_Ok{
- Ok: &pbv1.Ok{},
- }
- }
- if err := stream.Send(ack); err != nil {
- return err
- }
- }
- }
- func newTestServer(t *testing.T) (*grpc.Server, *bufconn.Listener) {
- lis := bufconn.Listen(bufnetSize)
- srv := grpc.NewServer()
- pbv1.RegisterIngestServer(srv, &mockIngestServer{authKey: "acme-001:prom:s3cret"})
- go srv.Serve(lis)
- return srv, lis
- }
- func dialBufconn(ctx context.Context, t *testing.T, lis *bufconn.Listener) *grpc.ClientConn {
- conn, err := grpc.NewClient(
- "passthrough://bufconn",
- grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) {
- return lis.Dial()
- }),
- grpc.WithTransportCredentials(insecure.NewCredentials()),
- )
- if err != nil {
- t.Fatalf("grpc.NewClient: %v", err)
- }
- return conn
- }
- func TestClient_New(t *testing.T) {
- c, err := New("localhost:9090",
- WithAPIKey("acme-001:prom:s3cret"),
- WithMaxRetries(5),
- WithInsecure(),
- )
- if err != nil {
- t.Fatalf("New() error = %v", err)
- }
- if c == nil {
- t.Fatal("New() returned nil client")
- }
- if c.maxRetries != 5 {
- t.Errorf("maxRetries = %d, want 5", c.maxRetries)
- }
- c.Close()
- }
- func TestClient_OptionDefaults(t *testing.T) {
- c, err := New("localhost:9090", WithInsecure())
- if err != nil {
- t.Fatalf("New() error = %v", err)
- }
- if c.maxRetries != 3 {
- t.Errorf("default maxRetries = %d, want 3", c.maxRetries)
- }
- c.Close()
- }
- func TestStream_SendRecv(t *testing.T) {
- ctx := context.Background()
- srv, lis := newTestServer(t)
- defer srv.Stop()
- conn := dialBufconn(ctx, t, lis)
- defer conn.Close()
- client := pbv1.NewIngestClient(conn)
- md := metadata.Pairs("authorization", "Bearer acme-001:prom:s3cret")
- sctx := metadata.NewOutgoingContext(ctx, md)
- stream, err := client.StreamAlerts(sctx)
- if err != nil {
- t.Fatalf("StreamAlerts() error = %v", err)
- }
- alert := &pbv1.Alert{
- CompanyId: "acme-001",
- SourceId: "prom",
- Severity: "critical",
- Title: "test alert",
- DedupeKey: "dk-001",
- ClientTsMs: time.Now().UnixMilli(),
- }
- if err := stream.Send(alert); err != nil {
- t.Fatalf("stream.Send() error = %v", err)
- }
- ack, err := stream.Recv()
- if err != nil {
- t.Fatalf("stream.Recv() error = %v", err)
- }
- if ack.DedupeKey != "dk-001" {
- t.Errorf("ack.DedupeKey = %q, want %q", ack.DedupeKey, "dk-001")
- }
- stream.CloseSend()
- }
- func TestStream_CloseSend(t *testing.T) {
- ctx := context.Background()
- srv, lis := newTestServer(t)
- defer srv.Stop()
- conn := dialBufconn(ctx, t, lis)
- defer conn.Close()
- client := pbv1.NewIngestClient(conn)
- md := metadata.Pairs("authorization", "Bearer acme-001:prom:s3cret")
- sctx := metadata.NewOutgoingContext(ctx, md)
- stream, err := client.StreamAlerts(sctx)
- if err != nil {
- t.Fatalf("StreamAlerts() error = %v", err)
- }
- if err := stream.CloseSend(); err != nil {
- t.Errorf("CloseSend() error = %v", err)
- }
- _, err = stream.Recv()
- if err != io.EOF {
- t.Errorf("Recv() after CloseSend = %v, want io.EOF", err)
- }
- }
- func TestRetryable(t *testing.T) {
- tests := []struct {
- code codes.Code
- want bool
- }{
- {codes.Unavailable, true},
- {codes.ResourceExhausted, true},
- {codes.Internal, true},
- {codes.OK, false},
- {codes.InvalidArgument, false},
- {codes.NotFound, false},
- {codes.Unauthenticated, false},
- }
- for _, tt := range tests {
- t.Run(tt.code.String(), func(t *testing.T) {
- if got := retryable(tt.code); got != tt.want {
- t.Errorf("retryable(%v) = %v, want %v", tt.code, got, tt.want)
- }
- })
- }
- }
- func TestStream_SendRecv_MultipleAlerts(t *testing.T) {
- ctx := context.Background()
- srv, lis := newTestServer(t)
- defer srv.Stop()
- conn := dialBufconn(ctx, t, lis)
- defer conn.Close()
- client := pbv1.NewIngestClient(conn)
- md := metadata.Pairs("authorization", "Bearer acme-001:prom:s3cret")
- sctx := metadata.NewOutgoingContext(ctx, md)
- stream, err := client.StreamAlerts(sctx)
- if err != nil {
- t.Fatalf("StreamAlerts() error = %v", err)
- }
- const n = 5
- for i := 0; i < n; i++ {
- alert := &pbv1.Alert{
- CompanyId: "acme-001",
- SourceId: "prom",
- Severity: "info",
- Title: "test alert",
- DedupeKey: "dk-multi-" + string(rune('0'+i)),
- ClientTsMs: time.Now().UnixMilli(),
- }
- if err := stream.Send(alert); err != nil {
- t.Fatalf("stream.Send() error = %v", err)
- }
- ack, err := stream.Recv()
- if err != nil {
- t.Fatalf("stream.Recv() error = %v", err)
- }
- if ack.DedupeKey != alert.DedupeKey {
- t.Errorf("ack.DedupeKey = %q, want %q", ack.DedupeKey, alert.DedupeKey)
- }
- }
- stream.CloseSend()
- }
- // Verify generated types implement the expected interfaces.
- func TestProtoInterfaces(t *testing.T) {
- var _ pbv1.IngestClient = nil
- var _ pbv1.IngestServer = nil
- }
|