Skip to content

Commit 89da920

Browse files
committed
Reconile utils unit tests
Signed-off-by: Attila Mészáros <[email protected]>
1 parent c913167 commit 89da920

File tree

2 files changed

+127
-5
lines changed

2 files changed

+127
-5
lines changed

operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/reconciler/ReconcileUtilsTest.java

Lines changed: 125 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
*/
1616
package io.javaoperatorsdk.operator.api.reconciler;
1717

18+
import java.util.function.UnaryOperator;
19+
20+
import org.junit.jupiter.api.BeforeEach;
1821
import org.junit.jupiter.api.Disabled;
1922
import org.junit.jupiter.api.Test;
2023
import org.slf4j.Logger;
@@ -23,13 +26,55 @@
2326
import io.fabric8.kubernetes.api.model.HasMetadata;
2427
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
2528
import io.fabric8.kubernetes.api.model.PodBuilder;
29+
import io.fabric8.kubernetes.client.KubernetesClient;
30+
import io.fabric8.kubernetes.client.KubernetesClientException;
31+
import io.fabric8.kubernetes.client.dsl.MixedOperation;
32+
import io.fabric8.kubernetes.client.dsl.Resource;
33+
import io.javaoperatorsdk.operator.TestUtils;
34+
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
35+
import io.javaoperatorsdk.operator.processing.event.EventSourceRetriever;
36+
import io.javaoperatorsdk.operator.processing.event.source.controller.ControllerEventSource;
37+
import io.javaoperatorsdk.operator.sample.simple.TestCustomResource;
2638

2739
import static org.assertj.core.api.Assertions.assertThat;
2840
import static org.junit.jupiter.api.Assertions.*;
41+
import static org.mockito.ArgumentMatchers.any;
42+
import static org.mockito.Mockito.*;
2943

3044
class ReconcileUtilsTest {
3145

3246
private static final Logger log = LoggerFactory.getLogger(ReconcileUtilsTest.class);
47+
private static final String FINALIZER_NAME = "test.javaoperatorsdk.io/finalizer";
48+
49+
private Context<TestCustomResource> context;
50+
private KubernetesClient client;
51+
private MixedOperation mixedOperation;
52+
private Resource resourceOp;
53+
private ControllerEventSource<TestCustomResource> controllerEventSource;
54+
private ControllerConfiguration<TestCustomResource> controllerConfiguration;
55+
56+
@BeforeEach
57+
@SuppressWarnings("unchecked")
58+
void setupMocks() {
59+
context = mock(Context.class);
60+
client = mock(KubernetesClient.class);
61+
mixedOperation = mock(MixedOperation.class);
62+
resourceOp = mock(Resource.class);
63+
controllerEventSource = mock(ControllerEventSource.class);
64+
controllerConfiguration = mock(ControllerConfiguration.class);
65+
66+
var eventSourceRetriever = mock(EventSourceRetriever.class);
67+
68+
when(context.getClient()).thenReturn(client);
69+
when(context.eventSourceRetriever()).thenReturn(eventSourceRetriever);
70+
when(context.getControllerConfiguration()).thenReturn(controllerConfiguration);
71+
when(controllerConfiguration.getFinalizerName()).thenReturn(FINALIZER_NAME);
72+
when(eventSourceRetriever.getControllerEventSource()).thenReturn(controllerEventSource);
73+
74+
when(client.resources(TestCustomResource.class)).thenReturn(mixedOperation);
75+
when(mixedOperation.inNamespace(any())).thenReturn(mixedOperation);
76+
when(mixedOperation.withName(any())).thenReturn(resourceOp);
77+
}
3378

3479
@Test
3580
void validateAndCompareResourceVersionsTest() {
@@ -162,17 +207,94 @@ public void compareResourcePerformanceTest() {
162207

163208
@Test
164209
void retriesAddingFinalizerWithoutSSA() {
210+
var resource = TestUtils.testCustomResource1();
211+
resource.getMetadata().setResourceVersion("1");
212+
213+
when(context.getPrimaryResource()).thenReturn(resource);
214+
215+
// First call throws conflict, second succeeds
216+
when(controllerEventSource.eventFilteringUpdateAndCacheResource(
217+
any(), any(UnaryOperator.class)))
218+
.thenThrow(new KubernetesClientException("Conflict", 409, null))
219+
.thenAnswer(
220+
invocation -> {
221+
var res = TestUtils.testCustomResource1();
222+
res.getMetadata().setResourceVersion("2");
223+
res.addFinalizer(FINALIZER_NAME);
224+
return res;
225+
});
226+
227+
// Return fresh resource on retry
228+
when(resourceOp.get()).thenReturn(resource);
229+
230+
var result = ReconcileUtils.addFinalizer(context, FINALIZER_NAME);
165231

166-
// todo
232+
assertThat(result).isNotNull();
233+
assertThat(result.hasFinalizer(FINALIZER_NAME)).isTrue();
234+
verify(controllerEventSource, times(2))
235+
.eventFilteringUpdateAndCacheResource(any(), any(UnaryOperator.class));
236+
verify(resourceOp, times(1)).get();
167237
}
168238

239+
// todo double check
169240
@Test
170241
void nullResourceIsGracefullyHandledOnFinalizerRemovalRetry() {
171-
// todo
242+
var resource = TestUtils.testCustomResource1();
243+
resource.getMetadata().setResourceVersion("1");
244+
resource.addFinalizer(FINALIZER_NAME);
245+
246+
when(context.getPrimaryResource()).thenReturn(resource);
247+
248+
// First call throws conflict
249+
when(controllerEventSource.eventFilteringUpdateAndCacheResource(
250+
any(), any(UnaryOperator.class)))
251+
.thenThrow(new KubernetesClientException("Conflict", 409, null));
252+
253+
// Return null on retry (resource was deleted)
254+
when(resourceOp.get()).thenReturn(null);
255+
256+
// Should throw NullPointerException when resource is null after retry
257+
assertThrows(
258+
NullPointerException.class, () -> ReconcileUtils.removeFinalizer(context, FINALIZER_NAME));
259+
260+
verify(controllerEventSource, times(1))
261+
.eventFilteringUpdateAndCacheResource(any(), any(UnaryOperator.class));
262+
verify(resourceOp, times(1)).get();
172263
}
173264

174265
@Test
175266
void retriesFinalizerRemovalWithFreshResource() {
176-
// todo
267+
var originalResource = TestUtils.testCustomResource1();
268+
originalResource.getMetadata().setResourceVersion("1");
269+
originalResource.addFinalizer(FINALIZER_NAME);
270+
271+
when(context.getPrimaryResource()).thenReturn(originalResource);
272+
273+
// First call throws unprocessable (422), second succeeds
274+
when(controllerEventSource.eventFilteringUpdateAndCacheResource(
275+
any(), any(UnaryOperator.class)))
276+
.thenThrow(new KubernetesClientException("Unprocessable", 422, null))
277+
.thenAnswer(
278+
invocation -> {
279+
var res = TestUtils.testCustomResource1();
280+
res.getMetadata().setResourceVersion("3");
281+
// finalizer should be removed
282+
return res;
283+
});
284+
285+
// Return fresh resource with newer version on retry
286+
var freshResource = TestUtils.testCustomResource1();
287+
freshResource.getMetadata().setResourceVersion("2");
288+
freshResource.addFinalizer(FINALIZER_NAME);
289+
when(resourceOp.get()).thenReturn(freshResource);
290+
291+
var result = ReconcileUtils.removeFinalizer(context, FINALIZER_NAME);
292+
293+
assertThat(result).isNotNull();
294+
assertThat(result.getMetadata().getResourceVersion()).isEqualTo("3");
295+
assertThat(result.hasFinalizer(FINALIZER_NAME)).isFalse();
296+
verify(controllerEventSource, times(2))
297+
.eventFilteringUpdateAndCacheResource(any(), any(UnaryOperator.class));
298+
verify(resourceOp, times(1)).get();
177299
}
178300
}

operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/filterpatchevent/FilterPatchEventTestReconciler.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ public UpdateControl<FilterPatchEventTestCustomResource> reconcile(
4242
resource.setStatus(new FilterPatchEventTestCustomResourceStatus());
4343
resource.getStatus().setValue(UPDATED);
4444

45-
var uc = UpdateControl.patchStatus(resource);
45+
var uc = UpdateControl.patchStatus(resource);
4646
if (!filterPatchEvent.get()) {
47-
uc = uc.reschedule();
47+
uc = uc.reschedule();
4848
}
4949
return uc;
5050
}

0 commit comments

Comments
 (0)