Skip to content

Commit 5c678bb

Browse files
committed
Draft test drive testing flex
Signed-off-by: jose.vazquez <[email protected]>
1 parent 1e205cd commit 5c678bb

File tree

5 files changed

+355
-1
lines changed

5 files changed

+355
-1
lines changed

internal/fakeatlas/builder.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright 2025 MongoDB Inc
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package fakeatlas
16+
17+
import (
18+
v20250312sdk "go.mongodb.org/atlas-sdk/v20250312009/admin"
19+
)
20+
21+
// AtlasClientBuilder is a builder for creating fake Atlas API clients.
22+
type AtlasClientBuilder struct {
23+
client *v20250312sdk.APIClient
24+
}
25+
26+
// NewAtlasClientBuilder creates a new AtlasClientBuilder.
27+
func NewAtlasClientBuilder() *AtlasClientBuilder {
28+
return &AtlasClientBuilder{
29+
client: &v20250312sdk.APIClient{},
30+
}
31+
}
32+
33+
// WithFakeFlexClusterClient configures the builder with a fake FlexCluster client.
34+
func (b *AtlasClientBuilder) WithFakeFlexClusterClient() *AtlasClientBuilder {
35+
b.client.FlexClustersApi = &FakeFlexClustersApi{}
36+
return b
37+
}
38+
39+
// Build returns the configured Atlas API client.
40+
func (b *AtlasClientBuilder) Build() *v20250312sdk.APIClient {
41+
return b.client
42+
}

internal/fakeatlas/fakeflex.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright 2025 MongoDB Inc
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package fakeatlas
16+
17+
import (
18+
"context"
19+
"net/http"
20+
21+
v20250312sdk "go.mongodb.org/atlas-sdk/v20250312009/admin"
22+
)
23+
24+
type FakeFlexClustersApi struct {
25+
v20250312sdk.FlexClustersApi
26+
CreateFlexClusterWithParamsFunc func(ctx context.Context, params *v20250312sdk.CreateFlexClusterApiParams) (*v20250312sdk.FlexClusterDescription20241113, *http.Response, error)
27+
// Store the last context and params for Execute to use
28+
lastCtx context.Context
29+
lastParams *v20250312sdk.CreateFlexClusterApiParams
30+
}
31+
32+
func (f *FakeFlexClustersApi) CreateFlexCluster(ctx context.Context, groupId string, flexClusterDescriptionCreate20241113 *v20250312sdk.FlexClusterDescriptionCreate20241113) v20250312sdk.CreateFlexClusterApiRequest {
33+
req := v20250312sdk.CreateFlexClusterApiRequest{
34+
ApiService: f,
35+
}
36+
return req
37+
}
38+
39+
func (f *FakeFlexClustersApi) CreateFlexClusterWithParams(ctx context.Context, params *v20250312sdk.CreateFlexClusterApiParams) v20250312sdk.CreateFlexClusterApiRequest {
40+
// Store context and params for Execute to use
41+
f.lastCtx = ctx
42+
f.lastParams = params
43+
req := v20250312sdk.CreateFlexClusterApiRequest{
44+
ApiService: f,
45+
}
46+
return req
47+
}
48+
49+
// CreateFlexClusterExecute is called by the SDK request's Execute method
50+
func (f *FakeFlexClustersApi) CreateFlexClusterExecute(request v20250312sdk.CreateFlexClusterApiRequest) (*v20250312sdk.FlexClusterDescription20241113, *http.Response, error) {
51+
if f.CreateFlexClusterWithParamsFunc == nil || f.lastParams == nil {
52+
return nil, nil, nil
53+
}
54+
return f.CreateFlexClusterWithParamsFunc(f.lastCtx, f.lastParams)
55+
}

internal/generated/controller/flexcluster/handler_v20250312.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ func (h *Handlerv20250312) getDependencies(ctx context.Context, flexcluster *ako
6262
}, group)
6363

6464
if err != nil {
65-
return nil, fmt.Errorf("failed to get group %w", err)
65+
return nil, fmt.Errorf("failed to get group %w", err)
6666
}
6767

6868
result = append(result, group)
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
// Copyright 2025 MongoDB Inc
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package flexcluster
16+
17+
import (
18+
"context"
19+
"fmt"
20+
"net/http"
21+
"testing"
22+
23+
"github.com/crd2go/crd2go/k8s"
24+
"github.com/stretchr/testify/assert"
25+
"github.com/stretchr/testify/require"
26+
v20250312sdk "go.mongodb.org/atlas-sdk/v20250312009/admin"
27+
corev1 "k8s.io/api/core/v1"
28+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29+
"k8s.io/apimachinery/pkg/runtime"
30+
"sigs.k8s.io/controller-runtime/pkg/client"
31+
reconcile "sigs.k8s.io/controller-runtime/pkg/reconcile"
32+
33+
crapi "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/crapi"
34+
"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/fakeatlas"
35+
crds "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/generated/crds"
36+
akov2generated "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/nextapi/generated/v1"
37+
"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/pointer"
38+
ctrlstate "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/controller/state"
39+
result "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/result"
40+
state "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/state"
41+
)
42+
43+
func TestHandleInitial(t *testing.T) {
44+
testCases := []struct {
45+
title string
46+
flexCluster *akov2generated.FlexCluster
47+
kubeObjects []client.Object
48+
want ctrlstate.Result
49+
wantErr string
50+
atlasCreateClusterFunc func(ctx context.Context, params *v20250312sdk.CreateFlexClusterApiParams) (*v20250312sdk.FlexClusterDescription20241113, *http.Response, error)
51+
}{
52+
{
53+
title: "successful initial creation",
54+
flexCluster: setGroupRef(defaultTestFlexCluster("test-cluster", "default"), "test-group"),
55+
kubeObjects: []client.Object{
56+
defaultTestGroup("test-group", "default", pointer.MakePtr("62b6e34b3d91647abb20e7b8")),
57+
},
58+
want: ReenqueueResult(state.StateCreating, "Creating Flex Cluster."),
59+
},
60+
{
61+
title: "missing group",
62+
flexCluster: setGroupRef(defaultTestFlexCluster("test-cluster1", "ns"), "not-found"),
63+
want: ctrlstate.Result{NextState: state.StateInitial},
64+
wantErr: "failed to get dependencies: failed to get group",
65+
},
66+
{
67+
title: "group without id",
68+
flexCluster: setGroupRef(defaultTestFlexCluster("test-cluster", "ns1"), "test-group"),
69+
kubeObjects: []client.Object{
70+
defaultTestGroup("test-group", "ns1", nil),
71+
},
72+
want: ctrlstate.Result{NextState: state.StateInitial},
73+
wantErr: "failed to fetch referenced value groupId",
74+
},
75+
{
76+
title: "atlas API error",
77+
flexCluster: setGroupRef(defaultTestFlexCluster("test-cluster2", "ns3"), "test-group"),
78+
kubeObjects: []client.Object{
79+
defaultTestGroup("test-group", "ns3", pointer.MakePtr("62b6e34b3d91647abb20e7b8")),
80+
},
81+
atlasCreateClusterFunc: func(ctx context.Context, params *v20250312sdk.CreateFlexClusterApiParams) (*v20250312sdk.FlexClusterDescription20241113, *http.Response, error) {
82+
return nil, nil, fmt.Errorf("atlas API error: cluster creation failed")
83+
},
84+
want: ctrlstate.Result{NextState: state.StateInitial},
85+
wantErr: "failed to create flex cluster",
86+
},
87+
}
88+
89+
for _, tc := range testCases {
90+
t.Run(tc.title, func(t *testing.T) {
91+
ctx := context.Background()
92+
scheme := createTestScheme(t)
93+
94+
allObjects := append([]client.Object{tc.flexCluster}, tc.kubeObjects...)
95+
kubeClient := newFakeClientWithGVK(scheme, allObjects, tc.flexCluster)
96+
97+
crd, err := crds.EmbeddedCRD("FlexCluster")
98+
require.NoError(t, err)
99+
tr, err := crapi.NewTranslator(crd, "v1", "v20250312")
100+
require.NoError(t, err)
101+
102+
atlasClientBuilder := fakeatlas.NewAtlasClientBuilder().
103+
WithFakeFlexClusterClient()
104+
105+
atlasClient := atlasClientBuilder.Build()
106+
107+
// Configure fake client with custom function if provided
108+
if tc.atlasCreateClusterFunc != nil {
109+
if fakeFlexClient, ok := atlasClient.FlexClustersApi.(*fakeatlas.FakeFlexClustersApi); ok {
110+
fakeFlexClient.CreateFlexClusterWithParamsFunc = tc.atlasCreateClusterFunc
111+
}
112+
}
113+
114+
deletionProtection := false
115+
116+
handler := NewHandlerv20250312(kubeClient, atlasClient, tr, deletionProtection)
117+
118+
result, err := handler.HandleInitial(ctx, tc.flexCluster)
119+
120+
if tc.wantErr != "" {
121+
assert.ErrorContains(t, err, tc.wantErr)
122+
} else {
123+
require.NoError(t, err)
124+
}
125+
assert.Equal(t, tc.want, result)
126+
})
127+
}
128+
}
129+
130+
// createTestScheme creates a runtime scheme for testing
131+
func createTestScheme(t *testing.T) *runtime.Scheme {
132+
scheme := runtime.NewScheme()
133+
require.NoError(t, akov2generated.AddToScheme(scheme))
134+
require.NoError(t, corev1.AddToScheme(scheme))
135+
return scheme
136+
}
137+
138+
// defaultTestFlexCluster creates a basic FlexCluster for testing
139+
func defaultTestFlexCluster(name, namespace string) *akov2generated.FlexCluster {
140+
return &akov2generated.FlexCluster{
141+
ObjectMeta: metav1.ObjectMeta{
142+
Name: name,
143+
Namespace: namespace,
144+
},
145+
Spec: akov2generated.FlexClusterSpec{
146+
V20250312: &akov2generated.FlexClusterSpecV20250312{
147+
Entry: &akov2generated.FlexClusterSpecV20250312Entry{
148+
Name: name,
149+
ProviderSettings: akov2generated.ProviderSettings{
150+
BackingProviderName: "AWS",
151+
RegionName: "us-east-1",
152+
},
153+
},
154+
},
155+
},
156+
}
157+
}
158+
159+
func setGroupRef(flexCluster *akov2generated.FlexCluster, groupRef string) *akov2generated.FlexCluster {
160+
flexCluster.Spec.V20250312.GroupRef = &k8s.LocalReference{Name: groupRef}
161+
return flexCluster
162+
}
163+
164+
// defaultTestGroup creates a basic Group for testing
165+
func defaultTestGroup(name, namespace string, id *string) *akov2generated.Group {
166+
return &akov2generated.Group{
167+
TypeMeta: metav1.TypeMeta{
168+
Kind: "Group",
169+
APIVersion: "atlas.generated.mongodb.com/v1",
170+
},
171+
ObjectMeta: metav1.ObjectMeta{
172+
Name: name,
173+
Namespace: namespace,
174+
},
175+
Status: akov2generated.GroupStatus{
176+
V20250312: &akov2generated.GroupStatusV20250312{
177+
Id: id,
178+
},
179+
},
180+
}
181+
}
182+
183+
func ReenqueueResult(state state.ResourceState, msg string) ctrlstate.Result {
184+
return ctrlstate.Result{
185+
Result: reconcile.Result{RequeueAfter: result.DefaultRequeueTIme},
186+
NextState: state,
187+
StateMsg: msg,
188+
}
189+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Copyright 2025 MongoDB Inc
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package flexcluster
16+
17+
import (
18+
"context"
19+
20+
"k8s.io/apimachinery/pkg/runtime"
21+
"k8s.io/apimachinery/pkg/runtime/schema"
22+
"sigs.k8s.io/controller-runtime/pkg/client"
23+
"sigs.k8s.io/controller-runtime/pkg/client/fake"
24+
)
25+
26+
// newFakeClientWithGVK creates a fake client that preserves GVK on retrieved objects.
27+
// The fake client from controller-runtime doesn't preserve TypeMeta by default, so we wrap it
28+
// to automatically set GVK after Get operations using the scheme.
29+
func newFakeClientWithGVK(scheme *runtime.Scheme, objects []client.Object, statusSubresource client.Object) client.Client {
30+
fakeClient := fake.NewClientBuilder().
31+
WithScheme(scheme).
32+
WithObjects(objects...).
33+
WithStatusSubresource(statusSubresource).
34+
Build()
35+
return &gvkPreservingClient{Client: fakeClient, scheme: scheme}
36+
}
37+
38+
// gvkPreservingClient wraps a client.Client to set GVK on objects after retrieval.
39+
// This is needed because the fake client doesn't preserve TypeMeta when retrieving objects.
40+
type gvkPreservingClient struct {
41+
client.Client
42+
scheme *runtime.Scheme
43+
}
44+
45+
func (c *gvkPreservingClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error {
46+
if err := c.Client.Get(ctx, key, obj, opts...); err != nil {
47+
return err
48+
}
49+
return setGVK(c.scheme, obj)
50+
}
51+
52+
// setGVK sets the GroupVersionKind on an object using the scheme.
53+
// This is necessary because fake clients don't preserve TypeMeta when retrieving objects.
54+
func setGVK(scheme *runtime.Scheme, obj runtime.Object) error {
55+
gvks, _, err := scheme.ObjectKinds(obj)
56+
if err != nil {
57+
return err
58+
}
59+
if len(gvks) == 0 {
60+
return nil
61+
}
62+
objectKind, ok := obj.(schema.ObjectKind)
63+
if !ok {
64+
return nil
65+
}
66+
objectKind.SetGroupVersionKind(gvks[0])
67+
return nil
68+
}

0 commit comments

Comments
 (0)