github.com/grafana/synthetic-monitoring-agent@v0.64.2/internal/k6version/k6version_test.go (about)

     1  package k6version_test
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/grafana/synthetic-monitoring-agent/internal/k6runner"
    10  	"github.com/grafana/synthetic-monitoring-agent/internal/k6version"
    11  	sm "github.com/grafana/synthetic-monitoring-agent/pkg/pb/synthetic_monitoring"
    12  	"github.com/rs/zerolog"
    13  	"google.golang.org/grpc"
    14  )
    15  
    16  type fakeRunner struct {
    17  	versionsCh <-chan []string
    18  }
    19  
    20  var _ k6runner.Runner = (*fakeRunner)(nil)
    21  
    22  func (r *fakeRunner) WithLogger(_ *zerolog.Logger) k6runner.Runner { return r }
    23  
    24  func (r *fakeRunner) Run(_ context.Context, _ k6runner.Script, _ k6runner.SecretStore, _ string) (*k6runner.RunResponse, error) {
    25  	return nil, errors.New("not implemented")
    26  }
    27  
    28  func (r *fakeRunner) Versions(_ context.Context) <-chan []string {
    29  	return r.versionsCh
    30  }
    31  
    32  type fakeK6Client struct {
    33  	err   error
    34  	code  sm.StatusCode
    35  	calls chan *sm.RegisterK6VersionRequest
    36  }
    37  
    38  var _ sm.K6Client = (*fakeK6Client)(nil)
    39  
    40  func (c *fakeK6Client) RegisterK6Version(ctx context.Context, req *sm.RegisterK6VersionRequest, _ ...grpc.CallOption) (*sm.RegisterK6VersionResponse, error) {
    41  	if c.calls != nil {
    42  		select {
    43  		case c.calls <- req:
    44  		default:
    45  		}
    46  	}
    47  
    48  	if err := ctx.Err(); err != nil {
    49  		return nil, err
    50  	}
    51  
    52  	if c.err != nil {
    53  		return nil, c.err
    54  	}
    55  
    56  	return &sm.RegisterK6VersionResponse{
    57  		Status: sm.Status{Code: c.code},
    58  	}, nil
    59  }
    60  
    61  //nolint:gocyclo // Test suite.
    62  func TestHandle(t *testing.T) {
    63  	t.Parallel()
    64  
    65  	t.Run("cancelled context", func(t *testing.T) {
    66  		t.Parallel()
    67  
    68  		ch := make(chan []string) // never sends
    69  		defer close(ch)
    70  
    71  		logger := zerolog.Nop()
    72  
    73  		handler, err := k6version.NewHandler(k6version.HandlerOpts{
    74  			K6Runner: &fakeRunner{versionsCh: ch},
    75  			K6Client: &fakeK6Client{code: sm.StatusCode_OK},
    76  			Logger:   &logger,
    77  		})
    78  		if err != nil {
    79  			t.Fatalf("creating handler: %v", err)
    80  		}
    81  
    82  		ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
    83  		t.Cleanup(cancel)
    84  
    85  		err = handler.Handle(ctx)
    86  		if !errors.Is(err, context.DeadlineExceeded) {
    87  			t.Fatalf("expected %v, got %v", context.DeadlineExceeded, err)
    88  		}
    89  	})
    90  
    91  	t.Run("closed versions channel", func(t *testing.T) {
    92  		t.Parallel()
    93  
    94  		ch := make(chan []string)
    95  		close(ch)
    96  
    97  		logger := zerolog.Nop()
    98  
    99  		handler, err := k6version.NewHandler(k6version.HandlerOpts{
   100  			K6Runner: &fakeRunner{versionsCh: ch},
   101  			K6Client: &fakeK6Client{code: sm.StatusCode_OK},
   102  			Logger:   &logger,
   103  		})
   104  		if err != nil {
   105  			t.Fatalf("creating handler: %v", err)
   106  		}
   107  
   108  		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
   109  		t.Cleanup(cancel)
   110  
   111  		err = handler.Handle(ctx)
   112  		if err != ctx.Err() {
   113  			t.Fatalf("expected context error, got %v", err)
   114  		}
   115  	})
   116  
   117  	t.Run("reports versions", func(t *testing.T) {
   118  		t.Parallel()
   119  
   120  		versionsCh := make(chan []string, 1)
   121  		versionsCh <- []string{"1.2.3", "2.0.0"}
   122  
   123  		calls := make(chan *sm.RegisterK6VersionRequest, 1)
   124  		logger := zerolog.Nop()
   125  
   126  		handler, err := k6version.NewHandler(k6version.HandlerOpts{
   127  			K6Runner: &fakeRunner{versionsCh: versionsCh},
   128  			K6Client: &fakeK6Client{code: sm.StatusCode_OK, calls: calls},
   129  			Logger:   &logger,
   130  		})
   131  		if err != nil {
   132  			t.Fatalf("creating handler: %v", err)
   133  		}
   134  
   135  		ctx, cancel := context.WithCancel(context.Background())
   136  		t.Cleanup(cancel)
   137  
   138  		handleDone := make(chan error, 1)
   139  
   140  		go func() {
   141  			handleDone <- handler.Handle(ctx)
   142  		}()
   143  
   144  		var req *sm.RegisterK6VersionRequest
   145  		select {
   146  		case <-time.After(3 * time.Second):
   147  			t.Fatalf("RegisterK6Version was not called within timeout")
   148  		case req = <-calls:
   149  		}
   150  
   151  		cancel()
   152  
   153  		select {
   154  		case err := <-handleDone:
   155  			if !errors.Is(err, context.Canceled) {
   156  				t.Fatalf("expected %v, got %v", context.Canceled, err)
   157  			}
   158  		case <-time.After(3 * time.Second):
   159  			t.Fatalf("Handle did not return after context cancellation")
   160  		}
   161  
   162  		if len(req.Versions) != 2 {
   163  			t.Fatalf("expected 2 versions, got %d: %v", len(req.Versions), req.Versions)
   164  		}
   165  
   166  		got := map[string]bool{}
   167  		for _, v := range req.Versions {
   168  			got[v.Version] = true
   169  		}
   170  
   171  		for _, expected := range []string{"1.2.3", "2.0.0"} {
   172  			if !got[expected] {
   173  				t.Fatalf("expected version %q in request, got: %v", expected, req.Versions)
   174  			}
   175  		}
   176  	})
   177  
   178  	t.Run("reports two successive version updates", func(t *testing.T) {
   179  		t.Parallel()
   180  
   181  		versionsCh := make(chan []string)
   182  		calls := make(chan *sm.RegisterK6VersionRequest, 2)
   183  		logger := zerolog.Nop()
   184  
   185  		handler, err := k6version.NewHandler(k6version.HandlerOpts{
   186  			K6Runner: &fakeRunner{versionsCh: versionsCh},
   187  			K6Client: &fakeK6Client{code: sm.StatusCode_OK, calls: calls},
   188  			Logger:   &logger,
   189  		})
   190  		if err != nil {
   191  			t.Fatalf("creating handler: %v", err)
   192  		}
   193  
   194  		ctx, cancel := context.WithCancel(context.Background())
   195  		t.Cleanup(cancel)
   196  
   197  		handleDone := make(chan error, 1)
   198  
   199  		go func() {
   200  			handleDone <- handler.Handle(ctx)
   201  		}()
   202  
   203  		// Send first version update and wait for it to be reported.
   204  		select {
   205  		case versionsCh <- []string{"1.0.0"}:
   206  		case <-time.After(3 * time.Second):
   207  			t.Fatal("timed out sending first version update")
   208  		}
   209  
   210  		var req1 *sm.RegisterK6VersionRequest
   211  		select {
   212  		case req1 = <-calls:
   213  		case <-time.After(3 * time.Second):
   214  			t.Fatal("RegisterK6Version was not called for first update within timeout")
   215  		}
   216  
   217  		// Send second version update and wait for it to be reported.
   218  		select {
   219  		case versionsCh <- []string{"2.0.0"}:
   220  		case <-time.After(3 * time.Second):
   221  			t.Fatal("timed out sending second version update")
   222  		}
   223  
   224  		var req2 *sm.RegisterK6VersionRequest
   225  		select {
   226  		case req2 = <-calls:
   227  		case <-time.After(3 * time.Second):
   228  			t.Fatal("RegisterK6Version was not called for second update within timeout")
   229  		}
   230  
   231  		cancel()
   232  
   233  		select {
   234  		case err := <-handleDone:
   235  			if !errors.Is(err, context.Canceled) {
   236  				t.Fatalf("expected %v, got %v", context.Canceled, err)
   237  			}
   238  		case <-time.After(3 * time.Second):
   239  			t.Fatal("Handle did not return after context cancellation")
   240  		}
   241  
   242  		if len(req1.Versions) != 1 || req1.Versions[0].Version != "1.0.0" {
   243  			t.Fatalf("unexpected first request versions: %v", req1.Versions)
   244  		}
   245  
   246  		if len(req2.Versions) != 1 || req2.Versions[0].Version != "2.0.0" {
   247  			t.Fatalf("unexpected second request versions: %v", req2.Versions)
   248  		}
   249  	})
   250  
   251  	t.Run("retries on client error", func(t *testing.T) {
   252  		t.Parallel()
   253  
   254  		versionsCh := make(chan []string, 1)
   255  		versionsCh <- []string{"1.2.3"}
   256  
   257  		calls := make(chan *sm.RegisterK6VersionRequest, 10)
   258  		logger := zerolog.Nop()
   259  
   260  		handler, err := k6version.NewHandler(k6version.HandlerOpts{
   261  			K6Runner: &fakeRunner{versionsCh: versionsCh},
   262  			K6Client: &fakeK6Client{err: errors.New("API unavailable"), calls: calls},
   263  			Logger:   &logger,
   264  		})
   265  		if err != nil {
   266  			t.Fatalf("creating handler: %v", err)
   267  		}
   268  
   269  		// The first call fails at t=0, backoff is 1s, so a 2s timeout guarantees at least 2 calls.
   270  		ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
   271  		t.Cleanup(cancel)
   272  
   273  		err = handler.Handle(ctx)
   274  		if !errors.Is(err, context.DeadlineExceeded) {
   275  			t.Fatalf("expected %v, got %v", context.DeadlineExceeded, err)
   276  		}
   277  
   278  		if got := len(calls); got < 2 {
   279  			t.Fatalf("expected at least 2 calls to RegisterK6Version, got %d", got)
   280  		}
   281  	})
   282  }