Skip to content

Commit

Permalink
Add unit tests for the conversion package
Browse files Browse the repository at this point in the history
Signed-off-by: Alper Rifat Ulucinar <[email protected]>
  • Loading branch information
ulucinar committed Jan 25, 2024
1 parent 3999690 commit 45b7a7c
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 0 deletions.
32 changes: 32 additions & 0 deletions pkg/config/conversion/conversions.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,34 @@ import (
)

const (
// AllVersions denotes that a Conversion is applicable for all versions
// of an API with which the Conversion is registered. It can be used for
// both the conversion source or target API versions.
AllVersions = "*"
)

// Conversion is the interface for the API version converters.
// Conversion implementations registered for a source, target
// pair are called in chain so Conversion implementations can be modular, e.g.,
// a Conversion implementation registered for a specific source and target
// versions does not have to contain all the needed API conversions between
// these two versions.
type Conversion interface {
// Applicable should return true if this Conversion is applicable while
// converting the API of the `src` object to the API of the `dst` object.
Applicable(src, dst runtime.Object) bool
}

// PavedConversion is an optimized Conversion between two fieldpath.Paved
// objects. PavedConversion implementations for a specific source and target
// version pair are chained together and the source and the destination objects
// are paved once at the beginning of the chained PavedConversion.ConvertPaved
// calls. The target fieldpath.Paved object is then converted into the original
// resource.Terraformed object at the end of the chained calls. This prevents
// the intermediate conversions between fieldpath.Paved and
// the resource.Terraformed representations of the same object, and the
// fieldpath.Paved representation is convenient for writing generic
// Conversion implementations not bound to a specific type.
type PavedConversion interface {
Conversion
// ConvertPaved converts from the `src` paved object to the `dst`
Expand All @@ -28,6 +49,12 @@ type PavedConversion interface {
ConvertPaved(src, target *fieldpath.Paved) (bool, error)
}

// TerraformedConversion defines a Conversion from a specific source
// resource.Terraformed type to a target one. Generic Conversion
// implementations may prefer to implement the PavedConversion interface.
// Implementations of TerraformedConversion can do type assertions to
// specific source and target types and so they are expected to be
// strongly typed.
type TerraformedConversion interface {
Conversion
// ConvertTerraformed converts from the `src` managed resource to the `dst`
Expand Down Expand Up @@ -77,6 +104,11 @@ func (f *fieldCopy) ConvertPaved(src, target *fieldpath.Paved) (bool, error) {
return true, errors.Wrapf(target.SetValue(f.targetField, v), "failed to set the field %q of the conversion target object", f.targetField)
}

// NewFieldRenameConversion returns a new Conversion that implements a
// field renaming conversion from the specified `sourceVersion` to the specified
// `targetVersion` of an API. The field's name in the `sourceVersion` is given
// with the `sourceField` parameter and its name in the `targetVersion` is
// given with `targetField` parameter.
func NewFieldRenameConversion(sourceVersion, sourceField, targetVersion, targetField string) Conversion {
return &fieldCopy{
baseConversion: newBaseConversion(sourceVersion, targetVersion),
Expand Down
149 changes: 149 additions & 0 deletions pkg/config/conversion/conversions_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// SPDX-FileCopyrightText: 2023 The Crossplane Authors <https://crossplane.io>
//
// SPDX-License-Identifier: Apache-2.0

package conversion

import (
"fmt"
"testing"

"github.com/crossplane/crossplane-runtime/pkg/test"
"github.com/google/go-cmp/cmp"
"k8s.io/utils/ptr"

"github.com/crossplane/crossplane-runtime/pkg/fieldpath"
)

const (
sourceVersion = "v1beta1"
sourceField = "testSourceField"
targetVersion = "v1beta2"
targetField = "testTargetField"
)

func TestConvertPaved(t *testing.T) {
type args struct {
sourceVersion string
sourceField string
targetVersion string
targetField string
sourceObj *fieldpath.Paved
targetObj *fieldpath.Paved
}
type want struct {
converted bool
err error
targetObj *fieldpath.Paved
}
tests := map[string]struct {
reason string
args args
want want
}{
"SuccessfulConversion": {
reason: "Source field in source version is successfully converted to the target field in target version.",
args: args{
sourceVersion: sourceVersion,
sourceField: sourceField,
targetVersion: targetVersion,
targetField: targetField,
sourceObj: getPaved(sourceVersion, sourceField, ptr.To("testValue")),
targetObj: getPaved(targetVersion, targetField, nil),
},
want: want{
converted: true,
targetObj: getPaved(targetVersion, targetField, ptr.To("testValue")),
},
},
"SuccessfulConversionAllVersions": {
reason: "Source field in source version is successfully converted to the target field in target version when the conversion specifies wildcard version for both of the source and the target.",
args: args{
sourceVersion: AllVersions,
sourceField: sourceField,
targetVersion: AllVersions,
targetField: targetField,
sourceObj: getPaved(sourceVersion, sourceField, ptr.To("testValue")),
targetObj: getPaved(targetVersion, targetField, nil),
},
want: want{
converted: true,
targetObj: getPaved(targetVersion, targetField, ptr.To("testValue")),
},
},
"SourceVersionMismatch": {
reason: "Conversion is not done if the source version of the object does not match the conversion's source version.",
args: args{
sourceVersion: "mismatch",
sourceField: sourceField,
targetVersion: AllVersions,
targetField: targetField,
sourceObj: getPaved(sourceVersion, sourceField, ptr.To("testValue")),
targetObj: getPaved(targetVersion, targetField, nil),
},
want: want{
converted: false,
targetObj: getPaved(targetVersion, targetField, nil),
},
},
"TargetVersionMismatch": {
reason: "Conversion is not done if the target version of the object does not match the conversion's target version.",
args: args{
sourceVersion: AllVersions,
sourceField: sourceField,
targetVersion: "mismatch",
targetField: targetField,
sourceObj: getPaved(sourceVersion, sourceField, ptr.To("testValue")),
targetObj: getPaved(targetVersion, targetField, nil),
},
want: want{
converted: false,
targetObj: getPaved(targetVersion, targetField, nil),
},
},
"SourceFieldNotFound": {
reason: "Conversion is not done if the source field is not found in the source object.",
args: args{
sourceVersion: sourceVersion,
sourceField: sourceField,
targetVersion: targetVersion,
targetField: targetField,
sourceObj: getPaved(sourceVersion, sourceField, nil),
targetObj: getPaved(targetVersion, targetField, ptr.To("test")),
},
want: want{
converted: false,
targetObj: getPaved(targetVersion, targetField, ptr.To("test")),
},
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
c := NewFieldRenameConversion(tc.args.sourceVersion, tc.args.sourceField, tc.args.targetVersion, tc.args.targetField)
converted, err := c.(*fieldCopy).ConvertPaved(tc.args.sourceObj, tc.args.targetObj)
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Errorf("\n%s\nConvertPaved(sourceObj, targetObj): -wantErr, +gotErr:\n%s", tc.reason, diff)
}
if tc.want.err != nil {
return
}
if diff := cmp.Diff(tc.want.converted, converted); diff != "" {
t.Errorf("\n%s\nConvertPaved(sourceObj, targetObj): -wantConverted, +gotConverted:\n%s", tc.reason, diff)
}
if diff := cmp.Diff(tc.want.targetObj.UnstructuredContent(), tc.args.targetObj.UnstructuredContent()); diff != "" {
t.Errorf("\n%s\nConvertPaved(sourceObj, targetObj): -wantTargetObj, +gotTargetObj:\n%s", tc.reason, diff)
}
})
}
}

func getPaved(version, field string, value *string) *fieldpath.Paved {
m := map[string]any{
"apiVersion": fmt.Sprintf("mockgroup/%s", version),
"kind": "mockkind",
}
if value != nil {
m[field] = *value
}
return fieldpath.Pave(m)
}

0 comments on commit 45b7a7c

Please sign in to comment.