From 370134e8b6132de9304ec03698933d363515e2f8 Mon Sep 17 00:00:00 2001 From: CodeBeaver Demo Date: Tue, 11 Mar 2025 13:12:59 +0100 Subject: [PATCH 1/3] test: Update coverage improvement test for internal/names/generate_test.go --- internal/names/generate_test.go | 521 +++++++++++++++++++++++--------- 1 file changed, 384 insertions(+), 137 deletions(-) diff --git a/internal/names/generate_test.go b/internal/names/generate_test.go index a0bc585cf49..b7814319493 100644 --- a/internal/names/generate_test.go +++ b/internal/names/generate_test.go @@ -17,152 +17,399 @@ limitations under the License. package names import ( - "context" - "strconv" - "testing" + "context" + "strconv" + "testing" + "strings" - "github.com/google/go-cmp/cmp" - kerrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/google/go-cmp/cmp" + kerrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/client" - "github.com/crossplane/crossplane-runtime/pkg/errors" - "github.com/crossplane/crossplane-runtime/pkg/resource" - "github.com/crossplane/crossplane-runtime/pkg/resource/fake" - "github.com/crossplane/crossplane-runtime/pkg/test" + "github.com/crossplane/crossplane-runtime/pkg/errors" + "github.com/crossplane/crossplane-runtime/pkg/resource" + "github.com/crossplane/crossplane-runtime/pkg/resource/fake" + "github.com/crossplane/crossplane-runtime/pkg/test" + "github.com/crossplane/crossplane/internal/xresource/unstructured/composite" ) +// fakeGVKComposed is a helper type to override GetObjectKind for testing purposes. +type fakeGVKComposed struct { + *fake.Composed +} +// GetObjectKind returns a fixed GroupVersionKind. +func (f *fakeGVKComposed) GetObjectKind() schema.ObjectKind { + return &metav1.TypeMeta{APIVersion: "test.group/v1", Kind: "TestKind"} +} +// fakeEmptyGVKComposed is a fake composed resource that returns an empty GroupVersionKind. +type fakeEmptyGVKComposed struct { + *fake.Composed +} +// GetObjectKind returns an empty TypeMeta. +func (f *fakeEmptyGVKComposed) GetObjectKind() schema.ObjectKind { + return &metav1.TypeMeta{} +} func TestGenerateName(t *testing.T) { - errBoom := errors.New("boom") + errBoom := errors.New("boom") - type args struct { - ctx context.Context - cd resource.Composed - } - type want struct { - cd resource.Composed - err error - } - cases := map[string]struct { - reason string - client client.Client - args - want - }{ - "SkipGenerateNamedResources": { - reason: "We should not try naming a resource that already have a name", - // We must be returning early, or else we'd hit this error. - client: &test.MockClient{MockCreate: test.NewMockCreateFn(errBoom)}, - args: args{ - cd: &fake.Composed{ObjectMeta: metav1.ObjectMeta{ - Name: "already-has-a-cool-name", - }}, - }, - want: want{ - cd: &fake.Composed{ObjectMeta: metav1.ObjectMeta{ - Name: "already-has-a-cool-name", - }}, - err: nil, - }, - }, - "SkipGenerateNameForResourcesWithoutGenerateName": { - reason: "We should not try to name resources that don't have a generate name (though that should never happen)", - // We must be returning early, or else we'd hit this error. - client: &test.MockClient{MockCreate: test.NewMockCreateFn(errBoom)}, - args: args{ - cd: &fake.Composed{}, // Conspicously missing a generate name. - }, - want: want{ - cd: &fake.Composed{}, - err: nil, - }, - }, - "NameGeneratorClientError": { - reason: "Client error finding a free name for a composed resource", - client: &test.MockClient{MockGet: test.NewMockGetFn(errBoom)}, - args: args{ - cd: &fake.Composed{ObjectMeta: metav1.ObjectMeta{ - GenerateName: "cool-resource-", - }}, - }, - want: want{ - cd: &fake.Composed{ObjectMeta: metav1.ObjectMeta{ - GenerateName: "cool-resource-", - }}, - err: errBoom, - }, - }, - "Success": { - reason: "Name is found on first try", - client: &test.MockClient{MockGet: test.NewMockGetFn(kerrors.NewNotFound(schema.GroupResource{Resource: "CoolResource"}, "cool-resource-42"))}, - args: args{ - cd: &fake.Composed{ObjectMeta: metav1.ObjectMeta{ - GenerateName: "cool-resource-", - }}, - }, - want: want{ - cd: &fake.Composed{ObjectMeta: metav1.ObjectMeta{ - GenerateName: "cool-resource-", - Name: "cool-resource-42", - }}, - }, - }, - "SuccessAfterConflict": { - reason: "Name is found on second try", - client: &test.MockClient{MockGet: func(_ context.Context, key client.ObjectKey, _ client.Object) error { - if key.Name == "cool-resource-42" { - return nil - } - return kerrors.NewNotFound(schema.GroupResource{Resource: "CoolResource"}, key.Name) - }}, - args: args{ - cd: &fake.Composed{ObjectMeta: metav1.ObjectMeta{ - GenerateName: "cool-resource-", - }}, - }, - want: want{ - cd: &fake.Composed{ObjectMeta: metav1.ObjectMeta{ - GenerateName: "cool-resource-", - Name: "cool-resource-43", - }}, - }, - }, - "AlwaysConflict": { - reason: "Name cannot be found", - client: &test.MockClient{MockGet: test.NewMockGetFn(nil)}, - args: args{ - cd: &fake.Composed{ObjectMeta: metav1.ObjectMeta{ - GenerateName: "cool-resource-", - }}, - }, - want: want{ - cd: &fake.Composed{ObjectMeta: metav1.ObjectMeta{ - GenerateName: "cool-resource-", - }}, - err: errors.New(errGenerateName), - }, - }, - } - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - r := &nameGenerator{reader: tc.client, namer: &mockNameGenerator{last: 41}} - err := r.GenerateName(tc.args.ctx, tc.args.cd) - if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" { - t.Errorf("\n%s\nDryRunRender(...): -want, +got:\n%s", tc.reason, diff) - } - if diff := cmp.Diff(tc.want.cd, tc.args.cd); diff != "" { - t.Errorf("\n%s\nDryRunRender(...): -want, +got:\n%s", tc.reason, diff) - } - }) - } + type args struct { + ctx context.Context + cd resource.Composed + } + type want struct { + cd resource.Composed + err error + } + cases := map[string]struct { + reason string + client client.Client + args + want + }{ + "SkipGenerateNamedResources": { + reason: "We should not try naming a resource that already have a name", + // We must be returning early, or else we'd hit this error. + client: &test.MockClient{MockCreate: test.NewMockCreateFn(errBoom)}, + args: args{ + cd: &fake.Composed{ObjectMeta: metav1.ObjectMeta{ + Name: "already-has-a-cool-name", + }}, + }, + want: want{ + cd: &fake.Composed{ObjectMeta: metav1.ObjectMeta{ + Name: "already-has-a-cool-name", + }}, + err: nil, + }, + }, + "SkipGenerateNameForResourcesWithoutGenerateName": { + reason: "We should not try to name resources that don't have a generate name (though that should never happen)", + // We must be returning early, or else we'd hit this error. + client: &test.MockClient{MockCreate: test.NewMockCreateFn(errBoom)}, + args: args{ + cd: &fake.Composed{}, // Conspicously missing a generate name. + }, + want: want{ + cd: &fake.Composed{}, + err: nil, + }, + }, + "NameGeneratorClientError": { + reason: "Client error finding a free name for a composed resource", + client: &test.MockClient{MockGet: test.NewMockGetFn(errBoom)}, + args: args{ + cd: &fake.Composed{ObjectMeta: metav1.ObjectMeta{ + GenerateName: "cool-resource-", + }}, + }, + want: want{ + cd: &fake.Composed{ObjectMeta: metav1.ObjectMeta{ + GenerateName: "cool-resource-", + }}, + err: errBoom, + }, + }, + "Success": { + reason: "Name is found on first try", + client: &test.MockClient{MockGet: test.NewMockGetFn(kerrors.NewNotFound(schema.GroupResource{Resource: "CoolResource"}, "cool-resource-42"))}, + args: args{ + cd: &fake.Composed{ObjectMeta: metav1.ObjectMeta{ + GenerateName: "cool-resource-", + }}, + }, + want: want{ + cd: &fake.Composed{ObjectMeta: metav1.ObjectMeta{ + GenerateName: "cool-resource-", + Name: "cool-resource-42", + }}, + }, + }, + "SuccessAfterConflict": { + reason: "Name is found on second try", + client: &test.MockClient{MockGet: func(_ context.Context, key client.ObjectKey, _ client.Object) error { + if key.Name == "cool-resource-42" { + return nil + } + return kerrors.NewNotFound(schema.GroupResource{Resource: "CoolResource"}, key.Name) + }}, + args: args{ + cd: &fake.Composed{ObjectMeta: metav1.ObjectMeta{ + GenerateName: "cool-resource-", + }}, + }, + want: want{ + cd: &fake.Composed{ObjectMeta: metav1.ObjectMeta{ + GenerateName: "cool-resource-", + Name: "cool-resource-43", + }}, + }, + }, + "AlwaysConflict": { + reason: "Name cannot be found", + client: &test.MockClient{MockGet: test.NewMockGetFn(nil)}, + args: args{ + cd: &fake.Composed{ObjectMeta: metav1.ObjectMeta{ + GenerateName: "cool-resource-", + }}, + }, + want: want{ + cd: &fake.Composed{ObjectMeta: metav1.ObjectMeta{ + GenerateName: "cool-resource-", + }}, + err: errors.New(errGenerateName), + }, + }, + "ContextCanceled": { + reason: "should return error if context is canceled", + client: &test.MockClient{ + MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error { + return ctx.Err() + }, + }, + args: args{ + ctx: func() context.Context { + ctx, cancel := context.WithCancel(context.Background()) + cancel() + return ctx + }(), + cd: &fake.Composed{ObjectMeta: metav1.ObjectMeta{ + GenerateName: "cool-resource-", + }}, + }, + want: want{ + cd: &fake.Composed{ObjectMeta: metav1.ObjectMeta{ + GenerateName: "cool-resource-", + }}, + err: context.Canceled, + }, + }, + "SuccessAfterMultipleConflicts": { + reason: "Name is found after multiple conflicts", + client: &test.MockClient{ + MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error { + if key.Name == "cool-resource-42" || key.Name == "cool-resource-43" { + return nil + } + return kerrors.NewNotFound(schema.GroupResource{Resource: "CoolResource"}, key.Name) + }, + }, + args: args{ + ctx: context.Background(), + cd: &fake.Composed{ObjectMeta: metav1.ObjectMeta{ + GenerateName: "cool-resource-", + }}, + }, + want: want{ + cd: &fake.Composed{ObjectMeta: metav1.ObjectMeta{ + GenerateName: "cool-resource-", + Name: "cool-resource-44", + }}, + err: nil, + }, + }, + "ContextDeadlineExceeded": { + reason: "should return DeadlineExceeded if context deadline is exceeded", + client: &test.MockClient{MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error { + return context.DeadlineExceeded + }}, + args: args{ + ctx: func() context.Context { + ctx, cancel := context.WithTimeout(context.Background(), 0) + defer cancel() + return ctx + }(), + cd: &fake.Composed{ObjectMeta: metav1.ObjectMeta{ + GenerateName: "cool-resource-", + }}, + }, + want: want{ + cd: &fake.Composed{ObjectMeta: metav1.ObjectMeta{ + GenerateName: "cool-resource-", + }}, + err: context.DeadlineExceeded, + }, + }, + "SetGroupVersionKind": { + reason: "ensures the composite object gets the expected GroupVersionKind when generating a name", + client: &test.MockClient{MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error { + un, ok := obj.(*composite.Unstructured) + if !ok { + return errors.New("unexpected type") + } + expectedGVK := schema.GroupVersionKind{Group: "test.group", Version: "v1", Kind: "TestKind"} + if got := un.GetObjectKind().GroupVersionKind(); got != expectedGVK { + return errors.New("incorrect GVK") + } + return kerrors.NewNotFound(schema.GroupResource{Resource: "CoolResource"}, key.Name) + }}, + args: args{ + cd: &fakeGVKComposed{&fake.Composed{ObjectMeta: metav1.ObjectMeta{ + GenerateName: "test-gvk-", + }}}, + }, + want: want{ + cd: &fakeGVKComposed{&fake.Composed{ObjectMeta: metav1.ObjectMeta{ + GenerateName: "test-gvk-", + Name: "test-gvk-42", + }}}, + err: nil, + }, + }, + "ErrorAfterConflict": { + reason: "Client returns error on retry after an initial conflict", + client: &test.MockClient{MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error { + if key.Name == "cool-resource-42" { + // Simulate that the first generated name is taken. + return nil + } + if key.Name == "cool-resource-43" { + // Return a non-NotFound error on the next try. + return errors.New("boom2") + } + return kerrors.NewNotFound(schema.GroupResource{Resource: "CoolResource"}, key.Name) + }}, + args: args{ + cd: &fake.Composed{ObjectMeta: metav1.ObjectMeta{ + GenerateName: "cool-resource-", + }}, + }, + want: want{ + cd: &fake.Composed{ObjectMeta: metav1.ObjectMeta{ + GenerateName: "cool-resource-", + }}, + err: errors.New("boom2"), + }, + }, + } + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + r := &nameGenerator{reader: tc.client, namer: &mockNameGenerator{last: 41}} + err := r.GenerateName(tc.args.ctx, tc.args.cd) + if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" { + t.Errorf("\n%s\nDryRunRender(...): -want, +got:\n%s", tc.reason, diff) + } + if diff := cmp.Diff(tc.want.cd, tc.args.cd); diff != "" { + t.Errorf("\n%s\nDryRunRender(...): -want, +got:\n%s", tc.reason, diff) + } + }) + } +} +func TestNameGeneratorFn(t *testing.T) { + // This test verifies that the NameGeneratorFn type correctly implements the NameGenerator interface. + // It provides a function that sets the name on a resource and ensures that the name is updated. + fakeObj := &fake.Composed{ObjectMeta: metav1.ObjectMeta{GenerateName: "test-"}} + // Define a NameGeneratorFn that sets a specific name. + fn := NameGeneratorFn(func(ctx context.Context, cd resource.Object) error { + cd.SetName("test-42") + return nil + }) + if err := fn.GenerateName(context.Background(), fakeObj); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got, want := fakeObj.GetName(), "test-42"; got != want { + t.Errorf("unexpected name; got %s, want %s", got, want) + } +} +func TestNameGeneratorFnError(t *testing.T) { + // TestNameGeneratorFnError verifies that a NameGeneratorFn returns an error when its function returns one. + expectedErr := errors.New("test error") + fn := NameGeneratorFn(func(ctx context.Context, cd resource.Object) error { + return expectedErr + }) + fakeObj := &fake.Composed{ObjectMeta: metav1.ObjectMeta{GenerateName: "test-"}} + err := fn.GenerateName(context.Background(), fakeObj) + if err != expectedErr { + t.Errorf("expected error %v, got %v", expectedErr, err) + } } - type mockNameGenerator struct { - last int + last int } func (m *mockNameGenerator) GenerateName(prefix string) string { - m.last++ - return prefix + strconv.Itoa(m.last) + m.last++ + return prefix + strconv.Itoa(m.last) } +// TestNewNameGenerator_Success tests that NewNameGenerator correctly generates a name by using the default SimpleNameGenerator. +func TestNewNameGenerator_Success(t *testing.T) { + ctx := context.Background() + // Create a composed object with GenerateName but no Name. + fakeObj := &fake.Composed{ObjectMeta: metav1.ObjectMeta{GenerateName: "newtest-"}} + + // Create a mock client that always returns NotFound so that any generated name is available. + c := &test.MockClient{ + MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error { + return kerrors.NewNotFound(schema.GroupResource{Resource: "TestResource"}, key.Name) + }, + } + + // Create a new name generator using the default SimpleNameGenerator. + gen := NewNameGenerator(c) + + // Call GenerateName and verify no error is returned. + if err := gen.GenerateName(ctx, fakeObj); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // Check that the generated name is non-empty and starts with the given prefix. + got := fakeObj.GetName() + if got == "" { + t.Errorf("expected non-empty name, got empty") + } else if !strings.HasPrefix(got, "newtest-") { + t.Errorf("expected name to start with %q, got %q", "newtest-", got) + } +} +// TestGenerateName_SkipAlreadyNamed_NoClientCall ensures that when an object already has a name, +// GenerateName does not attempt to generate one and therefore never calls the client's Get method. +func TestGenerateName_SkipAlreadyNamed_NoClientCall(t *testing.T) { + called := false + mockClient := &test.MockClient{ + MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error { + called = true + return nil + }, + } + gen := NewNameGenerator(mockClient) + obj := &fake.Composed{ObjectMeta: metav1.ObjectMeta{ + Name: "preset-name", + GenerateName: "unused-", + }} + if err := gen.GenerateName(context.Background(), obj); err != nil { + t.Errorf("unexpected error: %v", err) + } + if called { + t.Errorf("client.Get was called unexpectedly") + } +} +// TestGenerateName_EmptyGVK verifies that a resource with an empty GroupVersionKind +// still gets a name generated. This test increases coverage on the code path where +// the composite.Unstructured object's GVK is set using the value from cd.GetObjectKind(). +func TestGenerateName_EmptyGVK(t *testing.T) { + ctx := context.Background() + // Create a resource that returns an empty GroupVersionKind. + fakeObj := &fakeEmptyGVKComposed{ + Composed: &fake.Composed{ObjectMeta: metav1.ObjectMeta{ + GenerateName: "emptygvk-", + + }}, + } + // Create a mock client that always returns a NotFound error so that any name is considered available. + c := &test.MockClient{ + MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error { + return kerrors.NewNotFound(schema.GroupResource{Resource: "TestResource"}, key.Name) + }, + } + // Use the mockNameGenerator to produce a predictable name. + gen := &nameGenerator{reader: c, namer: &mockNameGenerator{last: 200}} + err := gen.GenerateName(ctx, fakeObj) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got := fakeObj.GetName(); got == "" { + t.Error("expected generated name to be non-empty, but got empty") + } else if !strings.HasPrefix(got, "emptygvk-") { + t.Errorf("expected generated name to start with %q, got %q", "emptygvk-", got) + } +} \ No newline at end of file From 35448f868c88209ea493459eb9a506dd42cb3235 Mon Sep 17 00:00:00 2001 From: CodeBeaver Demo Date: Tue, 11 Mar 2025 13:13:01 +0100 Subject: [PATCH 2/3] test: Add coverage improvement test for internal/features/features_test.go --- internal/features/features_test.go | 418 +++++++++++++++++++++++++++++ 1 file changed, 418 insertions(+) create mode 100644 internal/features/features_test.go diff --git a/internal/features/features_test.go b/internal/features/features_test.go new file mode 100644 index 00000000000..1e260ba9c25 --- /dev/null +++ b/internal/features/features_test.go @@ -0,0 +1,418 @@ +package features + +import ( + "testing" + + "github.com/crossplane/crossplane-runtime/pkg/feature" + "encoding/json" + "fmt" +) + +// TestAlphaFeatureFlags verifies that the alpha feature flags have the expected string values. +func TestAlphaFeatureFlags(t *testing.T) { + tests := []struct { + flag feature.Flag + want string + }{ + {EnableAlphaRealtimeCompositions, "EnableAlphaRealtimeCompositions"}, + {EnableAlphaDependencyVersionUpgrades, "EnableAlphaDependencyVersionUpgrades"}, + {EnableAlphaSignatureVerification, "EnableAlphaSignatureVerification"}, + } + for _, tt := range tests { + if string(tt.flag) != tt.want { + t.Errorf("expected flag %q, got %q", tt.want, tt.flag) + } + } +} + +// TestBetaFeatureFlags verifies that the beta feature flags have the expected string values. +func TestBetaFeatureFlags(t *testing.T) { + tests := []struct { + flag feature.Flag + want string + }{ + {EnableBetaDeploymentRuntimeConfigs, "EnableBetaDeploymentRuntimeConfigs"}, + {EnableBetaUsages, "EnableBetaUsages"}, + {EnableBetaClaimSSA, "EnableBetaClaimSSA"}, + } + for _, tt := range tests { + if string(tt.flag) != tt.want { + t.Errorf("expected flag %q, got %q", tt.want, tt.flag) + } + } +} + +// TestUniqueFlags checks that all feature flags have unique string values. +func TestUniqueFlags(t *testing.T) { + flagMap := map[string]bool{ + string(EnableAlphaRealtimeCompositions): true, + string(EnableAlphaDependencyVersionUpgrades): true, + string(EnableAlphaSignatureVerification): true, + string(EnableBetaDeploymentRuntimeConfigs): true, + string(EnableBetaUsages): true, + string(EnableBetaClaimSSA): true, + } + if len(flagMap) != 6 { + t.Errorf("expected 6 unique flag values, got %d", len(flagMap)) + } +} + +// TestFlagBehavior is a dummy test that shows usage of a feature flag check. +func TestFlagBehavior(t *testing.T) { + // Although our flags are constants, we simulate a check. + if EnableAlphaRealtimeCompositions != "EnableAlphaRealtimeCompositions" { + t.Errorf("Flag value mismatch: expected %v", "EnableAlphaRealtimeCompositions") + } +} +// TestJSONMarshalling verifies that each feature flag can be marshalled to JSON and then unmarshalled back. +func TestJSONMarshalling(t *testing.T) { + tests := []feature.Flag{ + EnableAlphaRealtimeCompositions, + EnableAlphaDependencyVersionUpgrades, + EnableAlphaSignatureVerification, + EnableBetaDeploymentRuntimeConfigs, + EnableBetaUsages, + EnableBetaClaimSSA, + } + for _, flag := range tests { + jsonData, err := json.Marshal(flag) + if err != nil { + t.Errorf("failed to marshal flag %v: %v", flag, err) + } + var unmarshalled string + if err := json.Unmarshal(jsonData, &unmarshalled); err != nil { + t.Errorf("failed to unmarshal json data %v: %v", string(jsonData), err) + } + if unmarshalled != string(flag) { + t.Errorf("expected unmarshalled flag %q, got %q", flag, unmarshalled) + } + } +} + +// TestFlagInStructJSONMarshalling verifies that feature flags embedded in a struct are correctly encoded to JSON. +func TestFlagInStructJSONMarshalling(t *testing.T) { + type testStruct struct { + Feature feature.Flag `json:"feature"` + Desc string `json:"desc"` + } + s := testStruct{ + Feature: EnableAlphaSignatureVerification, + Desc: "test description", + } + b, err := json.Marshal(s) + if err != nil { + t.Errorf("failed to marshal test struct: %v", err) + } + want := `{"feature":"EnableAlphaSignatureVerification","desc":"test description"}` + if string(b) != want { + t.Errorf("expected JSON %q, got %q", want, string(b)) + } +} +// TestInvalidJSONUnmarshalling verifies that invalid JSON input for feature.Flag returns an error. +func TestInvalidJSONUnmarshalling(t *testing.T) { + var flag feature.Flag + invalidJSON := []byte(`123`) // invalid JSON: non-string value + err := json.Unmarshal(invalidJSON, &flag) + if err == nil { + t.Error("expected error when unmarshalling invalid JSON into feature.Flag, got nil") + } +} + +// TestFlagPointerMarshalling verifies that a feature.Flag embedded as a pointer in a struct is correctly marshalled and unmarshalled. +func TestFlagPointerMarshalling(t *testing.T) { + type pointerStruct struct { + Feature *feature.Flag `json:"feature,omitempty"` + Desc string `json:"desc"` + } + flag := EnableBetaUsages + s := pointerStruct{ + Feature: &flag, + Desc: "pointer test", + } + data, err := json.Marshal(s) + if err != nil { + t.Errorf("failed to marshal pointer struct: %v", err) + } + expected := `{"feature":"EnableBetaUsages","desc":"pointer test"}` + if string(data) != expected { + t.Errorf("expected JSON %q, got %q", expected, string(data)) + } + var s2 pointerStruct + if err := json.Unmarshal(data, &s2); err != nil { + t.Errorf("failed to unmarshal JSON into struct: %v", err) + } + if s2.Feature == nil || *s2.Feature != EnableBetaUsages { + t.Errorf("expected Feature %q after unmarshal, got %v", "EnableBetaUsages", s2.Feature) + } +} + +// TestEmptyFlagMarshalling verifies that an empty feature.Flag can be marshalled and unmarshalled correctly. +func TestEmptyFlagMarshalling(t *testing.T) { + emptyFlag := feature.Flag("") + data, err := json.Marshal(emptyFlag) + if err != nil { + t.Errorf("failed to marshal empty flag: %v", err) + } + if string(data) != `""` { + t.Errorf("expected empty JSON string, got %q", string(data)) + } + var unmarshalled feature.Flag + if err := json.Unmarshal(data, &unmarshalled); err != nil { + t.Errorf("failed to unmarshal empty flag: %v", err) + } + if unmarshalled != emptyFlag { + t.Errorf("expected empty flag %q after unmarshal, got %q", emptyFlag, unmarshalled) + } +} + +// TestFlagEquality verifies that feature.Flag comparisons work as expected. +func TestFlagEquality(t *testing.T) { + // Compare constant flag with expected string literal. + if EnableAlphaRealtimeCompositions != feature.Flag("EnableAlphaRealtimeCompositions") { + t.Errorf("flag equality check failed for EnableAlphaRealtimeCompositions") + } + if EnableBetaClaimSSA != feature.Flag("EnableBetaClaimSSA") { + t.Errorf("flag equality check failed for EnableBetaClaimSSA") + } +} +// TestNilFlagPointerMarshalling verifies that if a pointer feature.Flag is nil, it is omitted in JSON marshalling. +func TestNilFlagPointerMarshalling(t *testing.T) { + type pointerStruct struct { + Feature *feature.Flag `json:"feature,omitempty"` + Desc string `json:"desc"` + } + s := pointerStruct{ + Feature: nil, + Desc: "nil pointer test", + } + data, err := json.Marshal(s) + if err != nil { + t.Errorf("failed to marshal struct with nil feature flag: %v", err) + } + expected := `{"desc":"nil pointer test"}` + if string(data) != expected { + t.Errorf("expected JSON %q, got %q", expected, string(data)) + } + + var s2 pointerStruct + if err := json.Unmarshal(data, &s2); err != nil { + t.Errorf("failed to unmarshal JSON into struct with nil feature flag: %v", err) + } + if s2.Feature != nil { + t.Errorf("expected nil feature flag after unmarshal, got %v", *s2.Feature) + } +} + +// TestDirectJSONUnmarshalling verifies that unmarshalling a valid JSON string directly into a feature.Flag works correctly. +func TestDirectJSONUnmarshalling(t *testing.T) { + var f feature.Flag + jsonData := []byte(`"CustomFlagValue"`) + if err := json.Unmarshal(jsonData, &f); err != nil { + t.Errorf("failed to unmarshal JSON into feature.Flag: %v", err) + } + if f != feature.Flag("CustomFlagValue") { + t.Errorf("expected feature flag %q, got %q", "CustomFlagValue", f) + } +} +// TestFlagPointerJSONNullUnmarshal verifies that unmarshalling a JSON object with a null feature pointer sets the feature field to nil. +func TestFlagPointerJSONNullUnmarshal(t *testing.T) { + type pointerStruct struct { + Feature *feature.Flag `json:"feature,omitempty"` + Desc string `json:"desc"` + } + // JSON with feature explicitly set to null. + jsonStr := `{"feature": null, "desc": "null feature test"}` + var s pointerStruct + if err := json.Unmarshal([]byte(jsonStr), &s); err != nil { + t.Errorf("failed to unmarshal JSON with null feature: %v", err) + } + if s.Feature != nil { + t.Errorf("expected nil feature flag after unmarshalling JSON null, got: %v", *s.Feature) + } +} + +// TestSliceOfFlagsJSONMarshalling verifies that a slice of feature.Flags is marshalled to a valid JSON array of strings. +func TestSliceOfFlagsJSONMarshalling(t *testing.T) { + flags := []feature.Flag{ + EnableAlphaRealtimeCompositions, + EnableAlphaDependencyVersionUpgrades, + EnableAlphaSignatureVerification, + EnableBetaDeploymentRuntimeConfigs, + EnableBetaUsages, + EnableBetaClaimSSA, + } + jsonData, err := json.Marshal(flags) + if err != nil { + t.Fatalf("failed to marshal slice of flags: %v", err) + } + // Build expected JSON array. + expected := `["EnableAlphaRealtimeCompositions","EnableAlphaDependencyVersionUpgrades","EnableAlphaSignatureVerification",` + + `"EnableBetaDeploymentRuntimeConfigs","EnableBetaUsages","EnableBetaClaimSSA"]` + if string(jsonData) != expected { + t.Errorf("expected JSON %q, got %q", expected, string(jsonData)) + } +} +// TestNestedStructJSONMarshalling verifies that a nested struct containing feature.Flags in slices, maps, and nested structs +// is correctly encoded to JSON and then decoded with the values remaining unchanged. +func TestNestedStructJSONMarshalling(t *testing.T) { + type Nested struct { + Title string `json:"title"` + Flag feature.Flag `json:"flag"` + } + type ComplexStruct struct { + Flags []feature.Flag `json:"flags"` + FlagMap map[string]feature.Flag `json:"flag_map"` + Nested Nested `json:"nested"` + } + + cs := ComplexStruct{ + Flags: []feature.Flag{ + EnableAlphaRealtimeCompositions, + EnableBetaUsages, + }, + FlagMap: map[string]feature.Flag{ + "alpha": EnableAlphaSignatureVerification, + "beta": EnableBetaClaimSSA, + }, + Nested: Nested{ + Title: "Nested Test", + Flag: EnableBetaDeploymentRuntimeConfigs, + }, + } + + data, err := json.Marshal(cs) + if err != nil { + t.Fatalf("failed to marshal complex struct: %v", err) + } + + var cs2 ComplexStruct + if err := json.Unmarshal(data, &cs2); err != nil { + t.Fatalf("failed to unmarshal complex struct: %v", err) + } + + if len(cs2.Flags) != 2 { + t.Errorf("expected 2 flags in slice, got %d", len(cs2.Flags)) + } else { + if cs2.Flags[0] != EnableAlphaRealtimeCompositions { + t.Errorf("expected first flag %q, got %q", EnableAlphaRealtimeCompositions, cs2.Flags[0]) + } + if cs2.Flags[1] != EnableBetaUsages { + t.Errorf("expected second flag %q, got %q", EnableBetaUsages, cs2.Flags[1]) + } + } + + if len(cs2.FlagMap) != 2 || + cs2.FlagMap["alpha"] != EnableAlphaSignatureVerification || + cs2.FlagMap["beta"] != EnableBetaClaimSSA { + t.Errorf("unexpected flag map contents: %v", cs2.FlagMap) + } + + if cs2.Nested.Title != "Nested Test" || cs2.Nested.Flag != EnableBetaDeploymentRuntimeConfigs { + t.Errorf("unexpected nested struct: %+v", cs2.Nested) + } +} +// TestFlagCaseSensitivity verifies that flag comparisons are case-sensitive. +func TestFlagCaseSensitivity(t *testing.T) { + // Create a lower-case version of the constant. + lower := feature.Flag("enablealpharealtimecompositions") + if EnableAlphaRealtimeCompositions == lower { + t.Errorf("expected flag comparison to be case-sensitive and not equal; got %q equal to %q", EnableAlphaRealtimeCompositions, lower) + } +} + +// TestSpecialCharacterFlag verifies that JSON marshalling and unmarshalling work correctly for a flag containing special characters. +func TestSpecialCharacterFlag(t *testing.T) { + // Create a custom flag with quotes and newline characters. + special := feature.Flag("Test\"Special\nFlag") + jsonData, err := json.Marshal(special) + if err != nil { + t.Fatalf("failed to marshal special flag: %v", err) + } + var unmarshalled feature.Flag + if err := json.Unmarshal(jsonData, &unmarshalled); err != nil { + t.Fatalf("failed to unmarshal special flag: %v", err) + } + if special != unmarshalled { + t.Errorf("expected special flag %q, got %q", special, unmarshalled) + } +} + +// TestFlagFormat verifies that the string representation obtained via fmt.Sprintf matches the flag value. +func TestFlagFormat(t *testing.T) { + flagVal := EnableBetaClaimSSA + formatted := fmt.Sprintf("%s", flagVal) + if formatted != string(flagVal) { + t.Errorf("expected formatted flag %q to equal %q", formatted, flagVal) + } +} +// TestFlagAsMapKey verifies that feature.Flag constants can be used as map keys and retrieved correctly. +func TestFlagAsMapKey(t *testing.T) { + m := map[feature.Flag]int{ + EnableAlphaRealtimeCompositions: 1, + EnableBetaClaimSSA: 2, + } + + if m[EnableAlphaRealtimeCompositions] != 1 { + t.Errorf("expected value 1 for flag %q, got %d", EnableAlphaRealtimeCompositions, m[EnableAlphaRealtimeCompositions]) + } + + if m[EnableBetaClaimSSA] != 2 { + t.Errorf("expected value 2 for flag %q, got %d", EnableBetaClaimSSA, m[EnableBetaClaimSSA]) + } +} + +// TestFlagMapKeyMarshalling verifies that a map with feature.Flag keys is correctly marshalled to JSON, +// and that the resulting JSON keys match the string representations of those feature flags. +func TestFlagMapKeyMarshalling(t *testing.T) { + m := map[feature.Flag]string{ + EnableAlphaDependencyVersionUpgrades: "alpha-dep", + EnableBetaUsages: "beta-usages", + } + + jsonData, err := json.Marshal(m) + if err != nil { + t.Fatalf("failed to marshal map with feature.Flag keys: %v", err) + } + + // Unmarshal the JSON data into a generic map[string]string because JSON object keys are strings. + var m2 map[string]string + if err := json.Unmarshal(jsonData, &m2); err != nil { + t.Fatalf("failed to unmarshal JSON data into map[string]string: %v", err) + } + + if v, ok := m2[string(EnableAlphaDependencyVersionUpgrades)]; !ok || v != "alpha-dep" { + t.Errorf("expected key %q with value %q, got %q", EnableAlphaDependencyVersionUpgrades, "alpha-dep", v) + } + + if v, ok := m2[string(EnableBetaUsages)]; !ok || v != "beta-usages" { + t.Errorf("expected key %q with value %q, got %q", EnableBetaUsages, "beta-usages", v) + } +} +// TestFlagRoundTripStringConversion verifies that converting a feature.Flag to a string and back retains equality. +func TestFlagRoundTripStringConversion(t *testing.T) { + original := feature.Flag("TestRoundTrip") + str := string(original) + roundTrip := feature.Flag(str) + if original != roundTrip { + t.Errorf("expected round trip conversion to yield same flag, got original %q and roundTrip %q", original, roundTrip) + } +} + +// TestRepeatedJSONMarshalling verifies that repeated JSON marshalling and unmarshalling of a feature.Flag remains consistent. +func TestRepeatedJSONMarshalling(t *testing.T) { + flagVal := EnableAlphaRealtimeCompositions + for i := 0; i < 10; i++ { + data, err := json.Marshal(flagVal) + if err != nil { + t.Fatalf("iteration %d: failed to marshal flag: %v", i, err) + } + var unmarshalled feature.Flag + err = json.Unmarshal(data, &unmarshalled) + if err != nil { + t.Fatalf("iteration %d: failed to unmarshal flag: %v", i, err) + } + if flagVal != unmarshalled { + t.Errorf("iteration %d: expected marshalled value %q, got %q", i, flagVal, unmarshalled) + } + } +} \ No newline at end of file From 5f9fe688fa8cd6ded08061dc6e02c65fae013693 Mon Sep 17 00:00:00 2001 From: CodeBeaver Demo Date: Tue, 11 Mar 2025 13:13:03 +0100 Subject: [PATCH 3/3] --- codebeaver.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 codebeaver.yml diff --git a/codebeaver.yml b/codebeaver.yml new file mode 100644 index 00000000000..98279961aaa --- /dev/null +++ b/codebeaver.yml @@ -0,0 +1,2 @@ +from: go-1.22 +# This file was generated automatically by CodeBeaver based on your repository. Learn how to customize it here: https://docs.codebeaver.ai/configuration/ \ No newline at end of file