From 7e4031c41bbc7c18aea45db7df293cac9ba12098 Mon Sep 17 00:00:00 2001 From: rng Date: Wed, 4 Feb 2026 09:52:40 +1100 Subject: [PATCH 1/2] Add unit test --- .../core/configuration/ActuatorConfig.java | 3 +- .../configuration/ActuatorConfigTest.java | 147 ++++++++++++++++++ 2 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 server/src/test/java/au/org/aodn/ogcapi/server/core/configuration/ActuatorConfigTest.java diff --git a/server/src/main/java/au/org/aodn/ogcapi/server/core/configuration/ActuatorConfig.java b/server/src/main/java/au/org/aodn/ogcapi/server/core/configuration/ActuatorConfig.java index 7e6ae506..1661ddd4 100644 --- a/server/src/main/java/au/org/aodn/ogcapi/server/core/configuration/ActuatorConfig.java +++ b/server/src/main/java/au/org/aodn/ogcapi/server/core/configuration/ActuatorConfig.java @@ -2,6 +2,7 @@ import au.org.aodn.ogcapi.server.core.model.enumeration.ErrorCode; import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch._types.HealthStatus; import co.elastic.clients.elasticsearch.indices.ExistsRequest; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.actuate.health.Health; @@ -22,7 +23,7 @@ public HealthIndicator ogcApiHealth( try { // Is elastic up and run? String status = client.cluster().health(r -> r).status().toString(); - if ("yellow".equalsIgnoreCase(status) || "red".equalsIgnoreCase(status)) { + if (HealthStatus.Yellow.name().equalsIgnoreCase(status) || HealthStatus.Red.name().equalsIgnoreCase(status)) { return Health.status(ErrorCode.ELASTICSEARCH_UNAVAILABLE.getStatus()) .withDetail("reason", ErrorCode.ELASTICSEARCH_UNAVAILABLE.getMessage()) .withDetail("code", ErrorCode.ELASTICSEARCH_UNAVAILABLE.getCode()) diff --git a/server/src/test/java/au/org/aodn/ogcapi/server/core/configuration/ActuatorConfigTest.java b/server/src/test/java/au/org/aodn/ogcapi/server/core/configuration/ActuatorConfigTest.java new file mode 100644 index 00000000..26e2d5bf --- /dev/null +++ b/server/src/test/java/au/org/aodn/ogcapi/server/core/configuration/ActuatorConfigTest.java @@ -0,0 +1,147 @@ +package au.org.aodn.ogcapi.server.core.configuration; + +import au.org.aodn.ogcapi.server.core.model.enumeration.ErrorCode; +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch._types.HealthStatus; +import co.elastic.clients.elasticsearch.cluster.ElasticsearchClusterClient; +import co.elastic.clients.elasticsearch.cluster.HealthResponse; +import co.elastic.clients.elasticsearch.indices.ElasticsearchIndicesClient; +import co.elastic.clients.elasticsearch.indices.ExistsRequest; +import co.elastic.clients.transport.endpoints.BooleanResponse; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.Status; + +import java.util.function.Function; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +public class ActuatorConfigTest { + + @Mock + private ElasticsearchClient client; + + @Mock + private ElasticsearchClusterClient cluster; + + @Mock + private ElasticsearchIndicesClient indexes; + + @Mock + private HealthResponse clusterHealth; + + @Mock + private BooleanResponse booleanHealthTrue; + + @Mock + private BooleanResponse booleanHealthFalse; + + @InjectMocks + private ActuatorConfig config; + + private static final String INDEX = "test-core"; + private static final String CO_INDEX = "test-co-core"; + private static final String VOCAB_INDEX = "test-vocabs"; + + @BeforeEach + void init() { + when(client.cluster()).thenReturn(cluster); + when(client.indices()).thenReturn(indexes); + when(booleanHealthTrue.value()).thenReturn(true); + when(booleanHealthFalse.value()).thenReturn(false); + } + + @AfterEach + void reset() { + Mockito.reset(client, cluster, clusterHealth, booleanHealthTrue, booleanHealthFalse); + } + + @Test + @SuppressWarnings("unchecked") + void greenCluster_allIndicesExist_returnsUp() throws Exception { + when(client.cluster().health(any(Function.class))).thenReturn(clusterHealth); + + when(clusterHealth.status()).thenReturn(HealthStatus.Green); + when(client.indices().exists(any(ExistsRequest.class))).thenReturn(booleanHealthTrue); + + Health health = config.ogcApiHealth(VOCAB_INDEX, INDEX, CO_INDEX, client).health(); + assertEquals(Status.UP, health.getStatus()); + } + + @Test + @SuppressWarnings("unchecked") + void redCluster_returnsUnavailable() throws Exception { + when(client.cluster().health(any(Function.class))).thenReturn(clusterHealth); + when(clusterHealth.status()).thenReturn(HealthStatus.Red); + + Health health = config.ogcApiHealth(VOCAB_INDEX, INDEX, CO_INDEX, client).health(); + + assertEquals(ErrorCode.ELASTICSEARCH_UNAVAILABLE.getStatus(), health.getStatus().toString()); + assertTrue(health.getDetails().containsValue(ErrorCode.ELASTICSEARCH_UNAVAILABLE.getMessage())); + } + + @Test + @SuppressWarnings("unchecked") + void yellowCluster_returnsUnavailable() throws Exception { + when(client.cluster().health(any(Function.class))).thenReturn(clusterHealth); + when(clusterHealth.status()).thenReturn(HealthStatus.Yellow); + + Health health = config.ogcApiHealth(VOCAB_INDEX, INDEX, CO_INDEX, client).health(); + assertEquals(ErrorCode.ELASTICSEARCH_UNAVAILABLE.getStatus(), health.getStatus().toString()); + } + + @Test + @SuppressWarnings("unchecked") + void exceptionThrown_returnsUnavailableWithException() throws Exception { + when(client.cluster().health(any(Function.class))).thenThrow(new RuntimeException("connection failed")); + + Health health = config.ogcApiHealth(VOCAB_INDEX, INDEX, CO_INDEX, client).health(); + + assertEquals(ErrorCode.ELASTICSEARCH_UNAVAILABLE.getStatus(), health.getStatus().toString()); + // Exception details in error section + assertTrue(health.getDetails().containsKey("error")); + } + + // Helpers + @SuppressWarnings("unchecked") + private void greenCluster() throws Exception { + when(client.cluster().health(any(Function.class))).thenReturn(clusterHealth); + when(clusterHealth.status()).thenReturn(HealthStatus.Green); + } + + @Test + void missingCoreIndex_returnsMissingCoreIndex() throws Exception { + greenCluster(); + + when(client.indices().exists(any(ExistsRequest.class))).thenAnswer(inv -> { + ExistsRequest req = inv.getArgument(0); + return req.index().contains(INDEX) ? booleanHealthFalse : booleanHealthTrue; + }); + + Health health = config.ogcApiHealth(VOCAB_INDEX, INDEX, CO_INDEX, client).health(); + assertEquals(ErrorCode.MISSING_CORE_INDEX.getCode(), health.getDetails().get("code").toString()); + } + + @Test + void missingVocabIndex_returnsMissingVocabIndex() throws Exception { + greenCluster(); + + when(client.indices().exists(any(ExistsRequest.class))).thenAnswer(inv -> { + ExistsRequest req = inv.getArgument(0); + return req.index().contains(VOCAB_INDEX) ? booleanHealthFalse : booleanHealthTrue; + }); + + Health health = config.ogcApiHealth(VOCAB_INDEX, INDEX, CO_INDEX, client).health(); + assertEquals(ErrorCode.MISSING_VOCAB_INDEX.getCode(), health.getDetails().get("code").toString()); + } +} \ No newline at end of file From 224cec52c6cac86a15f094a0bf97aa81ba889fde Mon Sep 17 00:00:00 2001 From: rng Date: Wed, 4 Feb 2026 10:02:18 +1100 Subject: [PATCH 2/2] Pre-commit --- .../ogcapi/server/core/configuration/ActuatorConfigTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/test/java/au/org/aodn/ogcapi/server/core/configuration/ActuatorConfigTest.java b/server/src/test/java/au/org/aodn/ogcapi/server/core/configuration/ActuatorConfigTest.java index 26e2d5bf..63cf02c0 100644 --- a/server/src/test/java/au/org/aodn/ogcapi/server/core/configuration/ActuatorConfigTest.java +++ b/server/src/test/java/au/org/aodn/ogcapi/server/core/configuration/ActuatorConfigTest.java @@ -144,4 +144,4 @@ void missingVocabIndex_returnsMissingVocabIndex() throws Exception { Health health = config.ogcApiHealth(VOCAB_INDEX, INDEX, CO_INDEX, client).health(); assertEquals(ErrorCode.MISSING_VOCAB_INDEX.getCode(), health.getDetails().get("code").toString()); } -} \ No newline at end of file +}