Skip to content

Commit 4500a84

Browse files
committed
Added unit tests to refs package
Signed-off-by: jose.vazquez <[email protected]>
1 parent de704e3 commit 4500a84

File tree

6 files changed

+714
-57
lines changed

6 files changed

+714
-57
lines changed
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
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 refs
16+
17+
import (
18+
"testing"
19+
20+
"github.com/stretchr/testify/assert"
21+
"github.com/stretchr/testify/require"
22+
corev1 "k8s.io/api/core/v1"
23+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
24+
"sigs.k8s.io/controller-runtime/pkg/client"
25+
)
26+
27+
var (
28+
testNamespace = "my-namespace"
29+
mainObj = &corev1.ConfigMap{
30+
ObjectMeta: metav1.ObjectMeta{Name: "main-cm", Namespace: testNamespace},
31+
}
32+
dep1 = &corev1.ConfigMap{
33+
ObjectMeta: metav1.ObjectMeta{Name: "dep1-cm", Namespace: testNamespace},
34+
}
35+
dep2 = &corev1.ConfigMap{
36+
ObjectMeta: metav1.ObjectMeta{Name: "dep2-cm", Namespace: testNamespace},
37+
}
38+
)
39+
40+
func TestNewMapContext(t *testing.T) {
41+
ctx := newMapContext(mainObj, []client.Object{dep1, dep2})
42+
43+
require.NotNil(t, ctx, "context should not be nil")
44+
assert.Equal(t, mainObj, ctx.main, "main object should be set correctly")
45+
assert.Len(t, ctx.m, 2, "map should contain the two initial dependencies")
46+
assert.Len(t, ctx.added, 0, "added slice should be empty on initialization")
47+
48+
assert.Contains(t, ctx.m, client.ObjectKeyFromObject(dep1))
49+
assert.Equal(t, dep1, ctx.m[client.ObjectKeyFromObject(dep1)])
50+
51+
assert.Contains(t, ctx.m, client.ObjectKeyFromObject(dep2))
52+
assert.Equal(t, dep2, ctx.m[client.ObjectKeyFromObject(dep2)])
53+
}
54+
55+
func TestContext_FindAndHas(t *testing.T) {
56+
ctx := newMapContext(mainObj, []client.Object{dep1})
57+
58+
testCases := []struct {
59+
name string
60+
searchName string
61+
expectedFound client.Object
62+
expectedHasResult bool
63+
}{
64+
{
65+
name: "should find existing object",
66+
searchName: "dep1-cm",
67+
expectedFound: dep1,
68+
expectedHasResult: true,
69+
},
70+
{
71+
name: "should not find non-existent object",
72+
searchName: "non-existent-cm",
73+
expectedFound: nil,
74+
expectedHasResult: false,
75+
},
76+
{
77+
name: "should not find with empty name",
78+
searchName: "",
79+
expectedFound: nil,
80+
expectedHasResult: false,
81+
},
82+
}
83+
84+
for _, tc := range testCases {
85+
t.Run(tc.name, func(t *testing.T) {
86+
foundObj := ctx.find(tc.searchName)
87+
hasResult := ctx.has(tc.searchName)
88+
89+
assert.Equal(t, tc.expectedFound, foundObj, "find() returned unexpected object")
90+
assert.Equal(t, tc.expectedHasResult, hasResult, "has() returned unexpected result")
91+
})
92+
}
93+
}
94+
95+
func TestContext_Add(t *testing.T) {
96+
ctx := newMapContext(mainObj, []client.Object{dep1})
97+
require.Len(t, ctx.m, 1, "pre-condition failed: map should have 1 item")
98+
require.Len(t, ctx.added, 0, "pre-condition failed: added slice should be empty")
99+
100+
newDep := &corev1.ConfigMap{
101+
ObjectMeta: metav1.ObjectMeta{Name: "new-dep-cm", Namespace: testNamespace},
102+
}
103+
104+
require.False(t, ctx.has(newDep.GetName()), "pre-condition failed: new object should not exist yet")
105+
106+
ctx.add(newDep)
107+
108+
assert.Len(t, ctx.m, 2, "map length should be 2 after adding")
109+
assert.Len(t, ctx.added, 1, "added slice should have 1 item after adding")
110+
assert.True(t, ctx.has(newDep.GetName()), "has() should find the newly added object")
111+
assert.Equal(t, newDep, ctx.find(newDep.GetName()), "find() should return the newly added object")
112+
assert.Equal(t, newDep, ctx.added[0], "the newly added object should be in the 'added' slice")
113+
114+
anotherDep := &corev1.ConfigMap{
115+
ObjectMeta: metav1.ObjectMeta{Name: "another-dep-cm", Namespace: testNamespace},
116+
}
117+
ctx.add(anotherDep)
118+
119+
assert.Len(t, ctx.m, 3, "map length should be 3 after second add")
120+
assert.Len(t, ctx.added, 2, "added slice should have 2 items after second add")
121+
assert.True(t, ctx.has(anotherDep.GetName()), "has() should find the second added object")
122+
assert.Contains(t, ctx.added, newDep, "added slice should still contain the first added object")
123+
assert.Contains(t, ctx.added, anotherDep, "added slice should contain the second added object")
124+
}

internal/autogen/translate/refs/handler.go

Lines changed: 49 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const (
3030
SecretProperySelector = "$.data.#"
3131
)
3232

33-
// Handler hodls the context needed to expand or collapse the references on an
33+
// Handler holds the context needed to expand or collapse the references on an
3434
// Kubernetes object translaion to and from API data
3535
type Handler struct {
3636
*context
@@ -41,25 +41,43 @@ func NewHandler(main client.Object, deps []client.Object) *Handler {
4141
return &Handler{context: newMapContext(main, deps)}
4242
}
4343

44-
func (h *Handler) ExpandMappings(obj, mappings map[string]any, fields ...string) error {
44+
func (h *Handler) ExpandReferences(obj, mappings map[string]any, fields ...string) error {
4545
h.expand = true
46-
expandedPath := []string{"properties"}
47-
for _, field := range fields {
48-
expandedPath = append(expandedPath, field, "properties")
49-
}
50-
props, err := unstructured.AccessField[map[string]any](mappings, expandedPath...)
46+
47+
props, err := accessMappingPropsAt(mappings, fields)
5148
if errors.Is(err, unstructured.ErrNotFound) {
5249
return nil
50+
} else if err != nil {
51+
return fmt.Errorf("failed to access mappings to expand references: %w", err)
5352
}
53+
54+
field, err := unstructured.AccessField[map[string]any](obj, fields...)
5455
if err != nil {
55-
return fmt.Errorf("failed to access the API mapping properties for %v: %w", expandedPath, err)
56+
return fmt.Errorf("failed to access object's %v: %w", fields, err)
57+
}
58+
if err := h.scanProperties([]string{}, props, field); err != nil {
59+
return fmt.Errorf("failed to expand references at %v: %w", fields, err)
60+
}
61+
return nil
62+
}
63+
64+
func (h *Handler) CollapseReferences(obj, mappings map[string]any, fields ...string) error {
65+
h.expand = false
66+
67+
props, err := accessMappingPropsAt(mappings, fields)
68+
if errors.Is(err, unstructured.ErrNotFound) {
69+
return nil
70+
} else if err != nil {
71+
return fmt.Errorf("failed to access mappings to collapse references: %w", err)
5672
}
73+
5774
field, err := unstructured.AccessField[map[string]any](obj, fields...)
5875
if err != nil {
5976
return fmt.Errorf("failed to access object's %v: %w", fields, err)
6077
}
61-
if err := h.mapProperties([]string{}, props, field); err != nil {
62-
return fmt.Errorf("failed to process properties from API into %v: %w", fields, err)
78+
79+
if err := h.scanProperties([]string{}, props, field); err != nil {
80+
return fmt.Errorf("failed to collapse references at %v: %w", fields, err)
6381
}
6482
return nil
6583
}
@@ -68,33 +86,40 @@ func (h *Handler) Added() []client.Object {
6886
return h.added
6987
}
7088

71-
func (h *Handler) CollapseReferences(path []string, props, obj map[string]any) error {
72-
h.expand = false
73-
return h.mapProperties(path, props, obj)
89+
func accessMappingPropsAt(mappings map[string]any, fields []string) (map[string]any, error) {
90+
expandedPath := []string{"properties"}
91+
for _, field := range fields {
92+
expandedPath = append(expandedPath, field, "properties")
93+
}
94+
props, err := unstructured.AccessField[map[string]any](mappings, expandedPath...)
95+
if err != nil {
96+
return nil, fmt.Errorf("failed to access the API mapping properties for %v: %w", expandedPath, err)
97+
}
98+
return props, nil
7499
}
75100

76-
func (m *Handler) mapProperties(path []string, props, obj map[string]any) error {
101+
func (m *Handler) scanProperties(path []string, props, obj map[string]any) error {
77102
for key, prop := range props {
78103
mapping, ok := (prop).(map[string]any)
79104
if !ok {
80105
continue
81106
}
82107
subPath := append(path, key)
83108
if isReference(mapping) {
84-
if err := m.mapReference(subPath, key, mapping, obj); err != nil {
109+
if err := m.processReference(subPath, key, mapping, obj); err != nil {
85110
return fmt.Errorf("failed to process reference: %w", err)
86111
}
87112
continue
88113
}
89-
rawField, err := unstructured.AccessField[any](obj, key) // unstructured.NestedFieldNoCopy(obj, key)
114+
rawField, err := unstructured.AccessField[any](obj, key)
90115
if errors.Is(err, unstructured.ErrNotFound) {
91116
continue
92117
}
93118
if err != nil {
94119
return fmt.Errorf("failed to access %q: %w", key, err)
95120
}
96121
if arrayField, ok := (rawField).([]any); ok {
97-
if err := m.mapArray(subPath, mapping, arrayField); err != nil {
122+
if err := m.scanArray(subPath, mapping, arrayField); err != nil {
98123
return fmt.Errorf("failed to process array mapping %q: %w", key, err)
99124
}
100125
continue
@@ -103,14 +128,14 @@ func (m *Handler) mapProperties(path []string, props, obj map[string]any) error
103128
if !ok {
104129
return fmt.Errorf("unsupported mapping of type %T", rawField)
105130
}
106-
if err := m.mapObject(subPath, key, mapping, subSpec); err != nil {
131+
if err := m.scanObject(subPath, key, mapping, subSpec); err != nil {
107132
return fmt.Errorf("failed to process object mapping %q: %w", key, err)
108133
}
109134
}
110135
return nil
111136
}
112137

113-
func (m *Handler) mapArray(path []string, mapping map[string]any, list []any) error {
138+
func (m *Handler) scanArray(path []string, mapping map[string]any, list []any) error {
114139
mapItems, err := unstructured.AccessField[map[string]any](mapping, "items", "properties")
115140
if err != nil {
116141
return fmt.Errorf("failed to access %q: %w", unstructured.Base(path), err)
@@ -128,28 +153,28 @@ func (m *Handler) mapArray(path []string, mapping map[string]any, list []any) er
128153
continue
129154
}
130155
subPath := append(path, key)
131-
if err := m.mapObject(subPath, mapName, mapping, entry); err != nil {
156+
if err := m.scanObject(subPath, mapName, mapping, entry); err != nil {
132157
return fmt.Errorf("failed to map property from array item %q at %v: %w", key, path, err)
133158
}
134159
}
135160
return nil
136161
}
137162

138-
func (m *Handler) mapObject(path []string, mapName string, mapping, obj map[string]any) error {
163+
func (m *Handler) scanObject(path []string, mapName string, mapping, obj map[string]any) error {
139164
if mapping["properties"] != nil {
140165
props, err := unstructured.AccessField[map[string]any](mapping, "properties")
141166
if err != nil {
142167
return fmt.Errorf("failed to access properties at %q: %w", path, err)
143168
}
144-
return m.mapProperties(path, props, obj)
169+
return m.scanProperties(path, props, obj)
145170
}
146171
if isReference(mapping) {
147-
return m.mapReference(path, mapName, mapping, obj)
172+
return m.processReference(path, mapName, mapping, obj)
148173
}
149174
return fmt.Errorf("unsupported extension at %v with fields %v", path, unstructured.FieldsOf(mapping))
150175
}
151176

152-
func (m *Handler) mapReference(path []string, mappingName string, mapping, obj map[string]any) error {
177+
func (m *Handler) processReference(path []string, mappingName string, mapping, obj map[string]any) error {
153178
rm := refMapping{}
154179
if err := unstructured.FromUnstructured(&rm, mapping); err != nil {
155180
return fmt.Errorf("failed to parse a reference mapping: %w", err)

0 commit comments

Comments
 (0)