Files
argo-cd/util/argo/diff/diff_test.go
Matthieu MOREL 847b8b203c chore(util): Fix modernize linter (#26344)
Signed-off-by: Matthieu MOREL <matthieu.morel35@gmail.com>
2026-02-09 10:59:32 -05:00

251 lines
8.2 KiB
Go

package diff_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
testutil "github.com/argoproj/argo-cd/v3/test"
argo "github.com/argoproj/argo-cd/v3/util/argo/diff"
"github.com/argoproj/argo-cd/v3/util/argo/normalizers"
"github.com/argoproj/argo-cd/v3/util/argo/testdata"
appstatecache "github.com/argoproj/argo-cd/v3/util/cache/appstate"
)
func TestStateDiff(t *testing.T) {
type diffConfigParams struct {
ignores []v1alpha1.ResourceIgnoreDifferences
overrides map[string]v1alpha1.ResourceOverride
label string
trackingMethod string
ignoreRoles bool
}
defaultDiffConfigParams := func() *diffConfigParams {
return &diffConfigParams{
ignores: []v1alpha1.ResourceIgnoreDifferences{},
overrides: map[string]v1alpha1.ResourceOverride{},
label: "",
trackingMethod: "",
ignoreRoles: true,
}
}
diffConfig := func(t *testing.T, params *diffConfigParams) argo.DiffConfig {
t.Helper()
diffConfig, err := argo.NewDiffConfigBuilder().
WithDiffSettings(params.ignores, params.overrides, params.ignoreRoles, normalizers.IgnoreNormalizerOpts{}).
WithTracking(params.label, params.trackingMethod).
WithNoCache().
Build()
require.NoError(t, err)
return diffConfig
}
type testcase struct {
name string
params func() *diffConfigParams
desiredState *unstructured.Unstructured
liveState *unstructured.Unstructured
expectedNormalizedReplicas int
expectedPredictedReplicas int
}
testcases := []*testcase{
{
name: "will normalize replica field if owned by trusted manager",
params: func() *diffConfigParams {
params := defaultDiffConfigParams()
params.ignores = []v1alpha1.ResourceIgnoreDifferences{
{
Group: "*",
Kind: "*",
ManagedFieldsManagers: []string{"kube-controller-manager"},
},
}
return params
},
desiredState: testutil.YamlToUnstructured(testdata.DesiredDeploymentYaml),
liveState: testutil.YamlToUnstructured(testdata.LiveDeploymentWithManagedReplicaYaml),
expectedNormalizedReplicas: 1,
expectedPredictedReplicas: 1,
},
{
name: "will keep replica field not owned by trusted manager",
params: func() *diffConfigParams {
params := defaultDiffConfigParams()
params.ignores = []v1alpha1.ResourceIgnoreDifferences{
{
Group: "*",
Kind: "*",
ManagedFieldsManagers: []string{"some-other-manager"},
},
}
return params
},
desiredState: testutil.YamlToUnstructured(testdata.DesiredDeploymentYaml),
liveState: testutil.YamlToUnstructured(testdata.LiveDeploymentWithManagedReplicaYaml),
expectedNormalizedReplicas: 2,
expectedPredictedReplicas: 3,
},
{
name: "will normalize replica field if configured with json pointers",
params: func() *diffConfigParams {
params := defaultDiffConfigParams()
params.ignores = []v1alpha1.ResourceIgnoreDifferences{
{
Group: "*",
Kind: "*",
JSONPointers: []string{"/spec/replicas"},
},
}
return params
},
desiredState: testutil.YamlToUnstructured(testdata.DesiredDeploymentYaml),
liveState: testutil.YamlToUnstructured(testdata.LiveDeploymentWithManagedReplicaYaml),
expectedNormalizedReplicas: 1,
expectedPredictedReplicas: 1,
},
{
name: "will normalize replica field if configured with jq expression",
params: func() *diffConfigParams {
params := defaultDiffConfigParams()
params.ignores = []v1alpha1.ResourceIgnoreDifferences{
{
Group: "*",
Kind: "*",
JQPathExpressions: []string{".spec.replicas"},
},
}
return params
},
desiredState: testutil.YamlToUnstructured(testdata.DesiredDeploymentYaml),
liveState: testutil.YamlToUnstructured(testdata.LiveDeploymentWithManagedReplicaYaml),
expectedNormalizedReplicas: 1,
expectedPredictedReplicas: 1,
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
// given
dc := diffConfig(t, tc.params())
// when
result, err := argo.StateDiff(tc.liveState, tc.desiredState, dc)
// then
require.NoError(t, err)
assert.NotNil(t, result)
assert.True(t, result.Modified)
normalized := testutil.YamlToUnstructured(string(result.NormalizedLive))
replicas, found, err := unstructured.NestedFloat64(normalized.Object, "spec", "replicas")
require.NoError(t, err)
assert.True(t, found)
assert.InEpsilon(t, float64(tc.expectedNormalizedReplicas), replicas, 0.0001)
predicted := testutil.YamlToUnstructured(string(result.PredictedLive))
predictedReplicas, found, err := unstructured.NestedFloat64(predicted.Object, "spec", "replicas")
require.NoError(t, err)
assert.True(t, found)
assert.InEpsilon(t, float64(tc.expectedPredictedReplicas), predictedReplicas, 0.0001)
})
}
}
func TestDiffConfigBuilder(t *testing.T) {
type fixture struct {
ignores []v1alpha1.ResourceIgnoreDifferences
overrides map[string]v1alpha1.ResourceOverride
label string
trackingMethod string
noCache bool
ignoreRoles bool
appName string
}
setup := func() *fixture {
return &fixture{
ignores: []v1alpha1.ResourceIgnoreDifferences{},
overrides: make(map[string]v1alpha1.ResourceOverride),
label: "some-label",
trackingMethod: "tracking-method",
noCache: true,
ignoreRoles: false,
appName: "application-name",
}
}
t.Run("will build diff config successfully", func(t *testing.T) {
// given
f := setup()
// when
diffConfig, err := argo.NewDiffConfigBuilder().
WithDiffSettings(f.ignores, f.overrides, f.ignoreRoles, normalizers.IgnoreNormalizerOpts{}).
WithTracking(f.label, f.trackingMethod).
WithNoCache().
Build()
// then
require.NoError(t, err)
require.NotNil(t, diffConfig)
assert.Empty(t, diffConfig.Ignores())
assert.Empty(t, diffConfig.Overrides())
assert.Equal(t, f.label, diffConfig.AppLabelKey())
assert.Equal(t, f.overrides, diffConfig.Overrides())
assert.Equal(t, f.trackingMethod, diffConfig.TrackingMethod())
assert.Equal(t, f.noCache, diffConfig.NoCache())
assert.Equal(t, f.ignoreRoles, diffConfig.IgnoreAggregatedRoles())
assert.Empty(t, diffConfig.AppName())
assert.Nil(t, diffConfig.StateCache())
})
t.Run("will initialize ignore differences if nil is passed", func(t *testing.T) {
// given
f := setup()
// when
diffConfig, err := argo.NewDiffConfigBuilder().
WithDiffSettings(nil, nil, f.ignoreRoles, normalizers.IgnoreNormalizerOpts{}).
WithTracking(f.label, f.trackingMethod).
WithNoCache().
Build()
// then
require.NoError(t, err)
require.NotNil(t, diffConfig)
assert.Empty(t, diffConfig.Ignores())
assert.Empty(t, diffConfig.Overrides())
assert.Equal(t, f.label, diffConfig.AppLabelKey())
assert.Equal(t, f.overrides, diffConfig.Overrides())
assert.Equal(t, f.trackingMethod, diffConfig.TrackingMethod())
assert.Equal(t, f.noCache, diffConfig.NoCache())
assert.Equal(t, f.ignoreRoles, diffConfig.IgnoreAggregatedRoles())
})
t.Run("will return error if retrieving diff from cache an no appName configured", func(t *testing.T) {
// given
f := setup()
// when
diffConfig, err := argo.NewDiffConfigBuilder().
WithDiffSettings(f.ignores, f.overrides, f.ignoreRoles, normalizers.IgnoreNormalizerOpts{}).
WithTracking(f.label, f.trackingMethod).
WithCache(&appstatecache.Cache{}, "").
Build()
// then
require.Error(t, err)
require.Nil(t, diffConfig)
})
t.Run("will return error if retrieving diff from cache and no stateCache configured", func(t *testing.T) {
// given
f := setup()
// when
diffConfig, err := argo.NewDiffConfigBuilder().
WithDiffSettings(f.ignores, f.overrides, f.ignoreRoles, normalizers.IgnoreNormalizerOpts{}).
WithTracking(f.label, f.trackingMethod).
WithCache(nil, f.appName).
Build()
// then
require.Error(t, err)
require.Nil(t, diffConfig)
})
}