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 }