package conditions import ( "testing" "time" . "github.com/onsi/gomega" //nolint:revive // TODO(aw185176): remove metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "edge-infra.dev/pkg/k8s/object/fobject" ) func TestNewPatch(t *testing.T) { fooTrue := TrueCondition("foo", "reason true", "message true") fooFalse := FalseCondition("foo", "reason false", "message false") tests := []struct { name string before Getter after Getter want Patch }{ { name: "No changes return empty patch", before: getterWithConditions(), after: getterWithConditions(), want: nil, }, { name: "No changes return empty patch", before: getterWithConditions(fooTrue), after: getterWithConditions(fooTrue), want: nil, }, { name: "Detects AddConditionPatch", before: getterWithConditions(), after: getterWithConditions(fooTrue), want: Patch{ { Before: nil, After: fooTrue, Op: AddConditionPatch, }, }, }, { name: "Detects ChangeConditionPatch", before: getterWithConditions(fooTrue), after: getterWithConditions(fooFalse), want: Patch{ { Before: fooTrue, After: fooFalse, Op: ChangeConditionPatch, }, }, }, { name: "Detects RemoveConditionPatch", before: getterWithConditions(fooTrue), after: getterWithConditions(), want: Patch{ { Before: fooTrue, After: nil, Op: RemoveConditionPatch, }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := NewWithT(t) got := NewPatch(tt.before, tt.after) g.Expect(got).To(Equal(tt.want)) }) } } func TestApply(t *testing.T) { fooTrue := TrueCondition("foo", "reason true", "message true") fooFalse := FalseCondition("foo", "reason false", "message false") fooUnknown := UnknownCondition("foo", "reason unknown", "message unknown") tests := []struct { name string before Getter after Getter latest Setter options []ApplyOption want []metav1.Condition wantErr bool }{ { name: "No patch return same list", before: getterWithConditions(fooTrue), after: getterWithConditions(fooTrue), latest: setterWithConditions(fooTrue), want: conditionList(fooTrue), wantErr: false, }, { name: "Add: When a condition does not exists, it should add", before: getterWithConditions(), after: getterWithConditions(fooTrue), latest: setterWithConditions(), want: conditionList(fooTrue), wantErr: false, }, { name: "Add: When a condition already exists but without conflicts, it should add", before: getterWithConditions(), after: getterWithConditions(fooTrue), latest: setterWithConditions(fooTrue), want: conditionList(fooTrue), wantErr: false, }, { name: "Add: When a condition already exists but with conflicts, it should error", before: getterWithConditions(), after: getterWithConditions(fooTrue), latest: setterWithConditions(fooFalse), want: nil, wantErr: true, }, { name: "Add: When a condition already exists but with conflicts, it should not error if the condition is owned", before: getterWithConditions(), after: getterWithConditions(fooTrue), latest: setterWithConditions(fooFalse), options: []ApplyOption{WithOwnedConditions("foo")}, want: conditionList(fooTrue), // after condition should be kept in case of error wantErr: false, }, { name: "Remove: When a condition was already deleted, it should pass", before: getterWithConditions(fooTrue), after: getterWithConditions(), latest: setterWithConditions(), want: conditionList(), wantErr: false, }, { name: "Remove: When a condition already exists but without conflicts, it should delete", before: getterWithConditions(fooTrue), after: getterWithConditions(), latest: setterWithConditions(fooTrue), want: conditionList(), wantErr: false, }, { name: "Remove: When a condition already exists but with conflicts, it should error", before: getterWithConditions(fooTrue), after: getterWithConditions(), latest: setterWithConditions(fooFalse), want: nil, wantErr: true, }, { name: "Remove: When a condition already exists but with conflicts, it should not error if the condition is owned", before: getterWithConditions(fooTrue), after: getterWithConditions(), latest: setterWithConditions(fooFalse), options: []ApplyOption{WithOwnedConditions("foo")}, want: conditionList(), // after condition should be kept in case of error wantErr: false, }, { name: "Change: When a condition exists without conflicts, it should change", before: getterWithConditions(fooTrue), after: getterWithConditions(fooFalse), latest: setterWithConditions(fooTrue), want: conditionList(fooFalse), wantErr: false, }, { name: "Change: When a condition exists with conflicts but there is agreement on the final state, it should change", before: getterWithConditions(fooFalse), after: getterWithConditions(fooTrue), latest: setterWithConditions(fooTrue), want: conditionList(fooTrue), wantErr: false, }, { name: "Change: When a condition exists with conflicts but there is no agreement on the final state, it should error", before: getterWithConditions(fooUnknown), after: getterWithConditions(fooFalse), latest: setterWithConditions(fooTrue), want: nil, wantErr: true, }, { name: "Change: When a condition exists with conflicts but there is no agreement on the final state, it should not error if the condition is owned", before: getterWithConditions(fooUnknown), after: getterWithConditions(fooFalse), latest: setterWithConditions(fooTrue), options: []ApplyOption{WithOwnedConditions("foo")}, want: conditionList(fooFalse), // after condition should be kept in case of error wantErr: false, }, { name: "Change: When a condition was deleted, it should error", before: getterWithConditions(fooTrue), after: getterWithConditions(fooFalse), latest: setterWithConditions(), want: nil, wantErr: true, }, { name: "Change: When a condition was deleted, it should not error if the condition is owned", before: getterWithConditions(fooTrue), after: getterWithConditions(fooFalse), latest: setterWithConditions(), options: []ApplyOption{WithOwnedConditions("foo")}, want: conditionList(fooFalse), // after condition should be kept in case of error wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := NewWithT(t) patch := NewPatch(tt.before, tt.after) err := patch.Apply(tt.latest, tt.options...) if tt.wantErr { g.Expect(err).To(HaveOccurred()) return } g.Expect(err).ToNot(HaveOccurred()) g.Expect(tt.latest.GetConditions()).To(haveSameConditionsOf(tt.want)) }) } } func TestApplyDoesNotAlterLastTransitionTime(t *testing.T) { g := NewWithT(t) before := &fobject.Fake{} after := &fobject.Fake{ Status: fobject.FakeStatus{ Conditions: []metav1.Condition{ { Type: "foo", Status: metav1.ConditionTrue, LastTransitionTime: metav1.NewTime(time.Now().UTC().Truncate(time.Second)), }, }, }, } latest := &fobject.Fake{} // latest has no conditions, so we are actually adding the // condition but in this case we should not set the LastTransitionTime // but we should preserve the LastTransition set in after diff := NewPatch(before, after) err := diff.Apply(latest) g.Expect(err).ToNot(HaveOccurred()) g.Expect(latest.GetConditions()).To(Equal(after.GetConditions())) }