| 
									
										
										
										
											2025-01-23 23:19:50 +08:00
										 |  |  | package app | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							|  |  |  | 	"errors" | 
					
						
							| 
									
										
										
										
											2025-02-07 17:57:26 +08:00
										 |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2025-01-23 23:19:50 +08:00
										 |  |  | 	"testing" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-06 19:58:29 +08:00
										 |  |  | 	"github.com/grafana/grafana-app-sdk/logging" | 
					
						
							| 
									
										
										
										
											2025-01-23 23:19:50 +08:00
										 |  |  | 	"github.com/grafana/grafana-app-sdk/resource" | 
					
						
							|  |  |  | 	advisorv0alpha1 "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/apps/advisor/pkg/app/checks" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/apimachinery/utils" | 
					
						
							|  |  |  | 	"github.com/stretchr/testify/assert" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestGetCheck(t *testing.T) { | 
					
						
							|  |  |  | 	obj := &advisorv0alpha1.Check{} | 
					
						
							| 
									
										
										
										
											2025-02-10 20:15:41 +08:00
										 |  |  | 	obj.SetLabels(map[string]string{checks.TypeLabel: "testType"}) | 
					
						
							| 
									
										
										
										
											2025-01-23 23:19:50 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	checkMap := map[string]checks.Check{ | 
					
						
							|  |  |  | 		"testType": &mockCheck{}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	check, err := getCheck(obj, checkMap) | 
					
						
							|  |  |  | 	assert.NoError(t, err) | 
					
						
							|  |  |  | 	assert.NotNil(t, check) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestGetCheck_MissingLabel(t *testing.T) { | 
					
						
							|  |  |  | 	obj := &advisorv0alpha1.Check{} | 
					
						
							|  |  |  | 	checkMap := map[string]checks.Check{} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	_, err := getCheck(obj, checkMap) | 
					
						
							|  |  |  | 	assert.Error(t, err) | 
					
						
							|  |  |  | 	assert.Equal(t, "missing check type as label", err.Error()) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestGetCheck_UnknownType(t *testing.T) { | 
					
						
							|  |  |  | 	obj := &advisorv0alpha1.Check{} | 
					
						
							| 
									
										
										
										
											2025-02-10 20:15:41 +08:00
										 |  |  | 	obj.SetLabels(map[string]string{checks.TypeLabel: "unknownType"}) | 
					
						
							| 
									
										
										
										
											2025-01-23 23:19:50 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	checkMap := map[string]checks.Check{ | 
					
						
							|  |  |  | 		"testType": &mockCheck{}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	_, err := getCheck(obj, checkMap) | 
					
						
							|  |  |  | 	assert.Error(t, err) | 
					
						
							|  |  |  | 	assert.Contains(t, err.Error(), "unknown check type unknownType") | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestProcessCheck(t *testing.T) { | 
					
						
							|  |  |  | 	obj := &advisorv0alpha1.Check{} | 
					
						
							|  |  |  | 	obj.SetAnnotations(map[string]string{}) | 
					
						
							|  |  |  | 	meta, err := utils.MetaAccessor(obj) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	meta.SetCreatedBy("user:1") | 
					
						
							|  |  |  | 	client := &mockClient{} | 
					
						
							|  |  |  | 	ctx := context.TODO() | 
					
						
							| 
									
										
										
										
											2025-02-07 17:57:26 +08:00
										 |  |  | 	check := &mockCheck{ | 
					
						
							|  |  |  | 		items: []any{"item"}, | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-01-23 23:19:50 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-06 19:58:29 +08:00
										 |  |  | 	err = processCheck(ctx, logging.DefaultLogger, client, obj, check) | 
					
						
							| 
									
										
										
										
											2025-01-23 23:19:50 +08:00
										 |  |  | 	assert.NoError(t, err) | 
					
						
							| 
									
										
										
										
											2025-02-10 20:15:41 +08:00
										 |  |  | 	assert.Equal(t, "processed", obj.GetAnnotations()[checks.StatusAnnotation]) | 
					
						
							| 
									
										
										
										
											2025-01-23 23:19:50 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-07 17:57:26 +08:00
										 |  |  | func TestProcessMultipleCheckItems(t *testing.T) { | 
					
						
							|  |  |  | 	obj := &advisorv0alpha1.Check{} | 
					
						
							|  |  |  | 	obj.SetAnnotations(map[string]string{}) | 
					
						
							|  |  |  | 	meta, err := utils.MetaAccessor(obj) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	meta.SetCreatedBy("user:1") | 
					
						
							|  |  |  | 	client := &mockClient{} | 
					
						
							|  |  |  | 	ctx := context.TODO() | 
					
						
							|  |  |  | 	items := make([]any, 100) | 
					
						
							|  |  |  | 	for i := range items { | 
					
						
							|  |  |  | 		if i%2 == 0 { | 
					
						
							|  |  |  | 			items[i] = fmt.Sprintf("item-%d", i) | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			items[i] = errors.New("error") | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	check := &mockCheck{ | 
					
						
							|  |  |  | 		items: items, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-06 19:58:29 +08:00
										 |  |  | 	err = processCheck(ctx, logging.DefaultLogger, client, obj, check) | 
					
						
							| 
									
										
										
										
											2025-02-07 17:57:26 +08:00
										 |  |  | 	assert.NoError(t, err) | 
					
						
							| 
									
										
										
										
											2025-02-10 20:15:41 +08:00
										 |  |  | 	assert.Equal(t, "processed", obj.GetAnnotations()[checks.StatusAnnotation]) | 
					
						
							| 
									
										
										
										
											2025-02-07 17:57:26 +08:00
										 |  |  | 	r := client.lastValue.(advisorv0alpha1.CheckV0alpha1StatusReport) | 
					
						
							|  |  |  | 	assert.Equal(t, r.Count, int64(100)) | 
					
						
							| 
									
										
										
										
											2025-02-07 22:48:18 +08:00
										 |  |  | 	assert.Len(t, r.Failures, 50) | 
					
						
							| 
									
										
										
										
											2025-02-07 17:57:26 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-23 23:19:50 +08:00
										 |  |  | func TestProcessCheck_AlreadyProcessed(t *testing.T) { | 
					
						
							|  |  |  | 	obj := &advisorv0alpha1.Check{} | 
					
						
							| 
									
										
										
										
											2025-02-10 20:15:41 +08:00
										 |  |  | 	obj.SetAnnotations(map[string]string{checks.StatusAnnotation: "processed"}) | 
					
						
							| 
									
										
										
										
											2025-01-23 23:19:50 +08:00
										 |  |  | 	client := &mockClient{} | 
					
						
							|  |  |  | 	ctx := context.TODO() | 
					
						
							|  |  |  | 	check := &mockCheck{} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-06 19:58:29 +08:00
										 |  |  | 	err := processCheck(ctx, logging.DefaultLogger, client, obj, check) | 
					
						
							| 
									
										
										
										
											2025-01-23 23:19:50 +08:00
										 |  |  | 	assert.NoError(t, err) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestProcessCheck_RunError(t *testing.T) { | 
					
						
							|  |  |  | 	obj := &advisorv0alpha1.Check{} | 
					
						
							|  |  |  | 	obj.SetAnnotations(map[string]string{}) | 
					
						
							|  |  |  | 	meta, err := utils.MetaAccessor(obj) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	meta.SetCreatedBy("user:1") | 
					
						
							|  |  |  | 	client := &mockClient{} | 
					
						
							|  |  |  | 	ctx := context.TODO() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	check := &mockCheck{ | 
					
						
							| 
									
										
										
										
											2025-02-07 17:57:26 +08:00
										 |  |  | 		items: []any{"item"}, | 
					
						
							|  |  |  | 		err:   errors.New("run error"), | 
					
						
							| 
									
										
										
										
											2025-01-23 23:19:50 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-06 19:58:29 +08:00
										 |  |  | 	err = processCheck(ctx, logging.DefaultLogger, client, obj, check) | 
					
						
							| 
									
										
										
										
											2025-01-23 23:19:50 +08:00
										 |  |  | 	assert.Error(t, err) | 
					
						
							| 
									
										
										
										
											2025-02-10 20:15:41 +08:00
										 |  |  | 	assert.Equal(t, "error", obj.GetAnnotations()[checks.StatusAnnotation]) | 
					
						
							| 
									
										
										
										
											2025-01-23 23:19:50 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-25 20:14:44 +08:00
										 |  |  | func TestProcessCheck_RunRecoversFromPanic(t *testing.T) { | 
					
						
							|  |  |  | 	obj := &advisorv0alpha1.Check{} | 
					
						
							|  |  |  | 	obj.SetAnnotations(map[string]string{}) | 
					
						
							|  |  |  | 	meta, err := utils.MetaAccessor(obj) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	meta.SetCreatedBy("user:1") | 
					
						
							|  |  |  | 	client := &mockClient{} | 
					
						
							|  |  |  | 	ctx := context.TODO() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	check := &mockCheck{ | 
					
						
							|  |  |  | 		items:     []any{"item"}, | 
					
						
							|  |  |  | 		runPanics: true, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-06 19:58:29 +08:00
										 |  |  | 	err = processCheck(ctx, logging.DefaultLogger, client, obj, check) | 
					
						
							| 
									
										
										
										
											2025-04-25 20:14:44 +08:00
										 |  |  | 	assert.Error(t, err) | 
					
						
							|  |  |  | 	assert.Contains(t, err.Error(), "panic recovered in step") | 
					
						
							|  |  |  | 	assert.Equal(t, "error", obj.GetAnnotations()[checks.StatusAnnotation]) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-24 18:00:32 +08:00
										 |  |  | func TestProcessCheckRetry_NoRetry(t *testing.T) { | 
					
						
							|  |  |  | 	obj := &advisorv0alpha1.Check{} | 
					
						
							|  |  |  | 	obj.SetAnnotations(map[string]string{}) | 
					
						
							|  |  |  | 	meta, err := utils.MetaAccessor(obj) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	meta.SetCreatedBy("user:1") | 
					
						
							|  |  |  | 	client := &mockClient{} | 
					
						
							|  |  |  | 	ctx := context.TODO() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	check := &mockCheck{} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-06 19:58:29 +08:00
										 |  |  | 	err = processCheckRetry(ctx, logging.DefaultLogger, client, obj, check) | 
					
						
							| 
									
										
										
										
											2025-04-24 18:00:32 +08:00
										 |  |  | 	assert.NoError(t, err) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestProcessCheckRetry_RetryError(t *testing.T) { | 
					
						
							|  |  |  | 	obj := &advisorv0alpha1.Check{} | 
					
						
							|  |  |  | 	obj.SetAnnotations(map[string]string{ | 
					
						
							|  |  |  | 		checks.RetryAnnotation:  "item", | 
					
						
							|  |  |  | 		checks.StatusAnnotation: "processed", | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	meta, err := utils.MetaAccessor(obj) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	meta.SetCreatedBy("user:1") | 
					
						
							|  |  |  | 	client := &mockClient{} | 
					
						
							|  |  |  | 	ctx := context.TODO() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	check := &mockCheck{ | 
					
						
							|  |  |  | 		items: []any{"item"}, | 
					
						
							|  |  |  | 		err:   errors.New("retry error"), | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-06 19:58:29 +08:00
										 |  |  | 	err = processCheckRetry(ctx, logging.DefaultLogger, client, obj, check) | 
					
						
							| 
									
										
										
										
											2025-04-24 18:00:32 +08:00
										 |  |  | 	assert.Error(t, err) | 
					
						
							|  |  |  | 	assert.Equal(t, "error", obj.GetAnnotations()[checks.StatusAnnotation]) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestProcessCheckRetry_Success(t *testing.T) { | 
					
						
							|  |  |  | 	obj := &advisorv0alpha1.Check{} | 
					
						
							|  |  |  | 	obj.SetAnnotations(map[string]string{ | 
					
						
							|  |  |  | 		checks.RetryAnnotation:  "item", | 
					
						
							|  |  |  | 		checks.StatusAnnotation: "processed", | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	obj.CheckStatus.Report.Failures = []advisorv0alpha1.CheckReportFailure{ | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			ItemID: "item", | 
					
						
							|  |  |  | 			StepID: "step", | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	meta, err := utils.MetaAccessor(obj) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	meta.SetCreatedBy("user:1") | 
					
						
							|  |  |  | 	client := &mockClient{} | 
					
						
							|  |  |  | 	ctx := context.TODO() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	check := &mockCheck{ | 
					
						
							|  |  |  | 		items: []any{"item"}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-06 19:58:29 +08:00
										 |  |  | 	err = processCheckRetry(ctx, logging.DefaultLogger, client, obj, check) | 
					
						
							| 
									
										
										
										
											2025-04-24 18:00:32 +08:00
										 |  |  | 	assert.NoError(t, err) | 
					
						
							|  |  |  | 	assert.Equal(t, "processed", obj.GetAnnotations()[checks.StatusAnnotation]) | 
					
						
							|  |  |  | 	assert.Empty(t, obj.GetAnnotations()[checks.RetryAnnotation]) | 
					
						
							|  |  |  | 	assert.Empty(t, obj.CheckStatus.Report.Failures) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-23 23:19:50 +08:00
										 |  |  | type mockClient struct { | 
					
						
							|  |  |  | 	resource.Client | 
					
						
							| 
									
										
										
										
											2025-02-07 17:57:26 +08:00
										 |  |  | 	lastValue any | 
					
						
							| 
									
										
										
										
											2025-01-23 23:19:50 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (m *mockClient) PatchInto(ctx context.Context, id resource.Identifier, req resource.PatchRequest, opts resource.PatchOptions, obj resource.Object) error { | 
					
						
							| 
									
										
										
										
											2025-02-07 17:57:26 +08:00
										 |  |  | 	m.lastValue = req.Operations[0].Value | 
					
						
							| 
									
										
										
										
											2025-01-23 23:19:50 +08:00
										 |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type mockCheck struct { | 
					
						
							| 
									
										
										
										
											2025-04-25 20:14:44 +08:00
										 |  |  | 	err       error | 
					
						
							|  |  |  | 	items     []any | 
					
						
							|  |  |  | 	runPanics bool | 
					
						
							| 
									
										
										
										
											2025-01-23 23:19:50 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-06 16:55:17 +08:00
										 |  |  | func (m *mockCheck) ID() string { | 
					
						
							|  |  |  | 	return "mock" | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (m *mockCheck) Items(ctx context.Context) ([]any, error) { | 
					
						
							| 
									
										
										
										
											2025-02-07 17:57:26 +08:00
										 |  |  | 	return m.items, nil | 
					
						
							| 
									
										
										
										
											2025-02-06 16:55:17 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-24 18:00:32 +08:00
										 |  |  | func (m *mockCheck) Item(ctx context.Context, id string) (any, error) { | 
					
						
							|  |  |  | 	return m.items[0], nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-06 16:55:17 +08:00
										 |  |  | func (m *mockCheck) Steps() []checks.Step { | 
					
						
							|  |  |  | 	return []checks.Step{ | 
					
						
							| 
									
										
										
										
											2025-04-25 20:14:44 +08:00
										 |  |  | 		&mockStep{err: m.err, panics: m.runPanics}, | 
					
						
							| 
									
										
										
										
											2025-02-06 16:55:17 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type mockStep struct { | 
					
						
							| 
									
										
										
										
											2025-04-25 20:14:44 +08:00
										 |  |  | 	err    error | 
					
						
							|  |  |  | 	panics bool | 
					
						
							| 
									
										
										
										
											2025-02-06 16:55:17 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-08 16:42:38 +08:00
										 |  |  | func (m *mockStep) Run(ctx context.Context, log logging.Logger, obj *advisorv0alpha1.CheckSpec, items any) ([]advisorv0alpha1.CheckReportFailure, error) { | 
					
						
							| 
									
										
										
										
											2025-04-25 20:14:44 +08:00
										 |  |  | 	if m.panics { | 
					
						
							|  |  |  | 		panic("panic") | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-02-06 16:55:17 +08:00
										 |  |  | 	if m.err != nil { | 
					
						
							|  |  |  | 		return nil, m.err | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-02-07 17:57:26 +08:00
										 |  |  | 	if _, ok := items.(error); ok { | 
					
						
							| 
									
										
										
										
											2025-05-08 16:42:38 +08:00
										 |  |  | 		return []advisorv0alpha1.CheckReportFailure{{}}, nil | 
					
						
							| 
									
										
										
										
											2025-02-07 17:57:26 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-02-06 16:55:17 +08:00
										 |  |  | 	return nil, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (m *mockStep) Title() string { | 
					
						
							|  |  |  | 	return "mock" | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (m *mockStep) Description() string { | 
					
						
							|  |  |  | 	return "mock" | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-19 22:11:55 +08:00
										 |  |  | func (m *mockStep) Resolution() string { | 
					
						
							|  |  |  | 	return "mock" | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-06 16:55:17 +08:00
										 |  |  | func (m *mockStep) ID() string { | 
					
						
							|  |  |  | 	return "mock" | 
					
						
							| 
									
										
										
										
											2025-01-23 23:19:50 +08:00
										 |  |  | } |