This is an automated email from the ASF dual-hosted git repository.

yasith pushed a commit to branch AIRAVATA-3981/integration-health-check
in repository https://gitbox.apache.org/repos/asf/airavata.git

commit 40a3999e99a803e3d3ed6ad922d4d8cbaccf2c24
Author: yasithdev <[email protected]>
AuthorDate: Thu Mar 26 15:34:28 2026 -0500

    feat: add Keycloak and Prometheus integration health tests
---
 .../airavata/integration/KeycloakHealthTest.java   | 132 +++++++++++++++++++++
 .../airavata/integration/PrometheusHealthTest.java |  87 ++++++++++++++
 2 files changed, 219 insertions(+)

diff --git 
a/integration-tests/src/test/java/org/apache/airavata/integration/KeycloakHealthTest.java
 
b/integration-tests/src/test/java/org/apache/airavata/integration/KeycloakHealthTest.java
new file mode 100644
index 0000000000..a323a0a5a8
--- /dev/null
+++ 
b/integration-tests/src/test/java/org/apache/airavata/integration/KeycloakHealthTest.java
@@ -0,0 +1,132 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.airavata.integration;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.net.URI;
+import java.net.URLEncoder;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Verifies that Keycloak is reachable, the OIDC discovery document is valid,
+ * and that a client-credentials token can be obtained for the test client.
+ */
+@Tag("integration")
+class KeycloakHealthTest {
+
+    private static final String BASE_URL =
+            System.getProperty("airavata.keycloak.url", 
"http://localhost:18080";);
+    private static final String REALM =
+            System.getProperty("airavata.keycloak.realm", "default");
+    private static final String CLIENT_ID =
+            System.getProperty("airavata.keycloak.client.id", "cs-jupyterlab");
+    private static final String CLIENT_SECRET =
+            System.getProperty("airavata.keycloak.client.secret", 
"DxeMtfiWU1qkDEmaGHf13RDahCujzhy1");
+
+    private static final String OIDC_URL =
+            BASE_URL + "/realms/" + REALM + 
"/.well-known/openid-configuration";
+
+    private static HttpClient httpClient;
+    private static ObjectMapper objectMapper;
+
+    @BeforeAll
+    static void setUp() {
+        httpClient = HttpClient.newBuilder()
+                .connectTimeout(Duration.ofSeconds(10))
+                .build();
+        objectMapper = new ObjectMapper();
+    }
+
+    @Test
+    void oidcDiscoveryShouldReturn200WithTokenEndpoint() throws Exception {
+        HttpRequest request = HttpRequest.newBuilder()
+                .uri(URI.create(OIDC_URL))
+                .timeout(Duration.ofSeconds(10))
+                .GET()
+                .build();
+
+        HttpResponse<String> response = httpClient.send(request, 
HttpResponse.BodyHandlers.ofString());
+
+        assertEquals(200, response.statusCode(),
+                "Expected HTTP 200 from OIDC discovery endpoint, got " + 
response.statusCode());
+
+        JsonNode doc = objectMapper.readTree(response.body());
+        JsonNode tokenEndpoint = doc.get("token_endpoint");
+        assertNotNull(tokenEndpoint, "OIDC discovery document is missing 
'token_endpoint'");
+        assertTrue(tokenEndpoint.asText().startsWith("http"),
+                "token_endpoint is not a valid URL: " + 
tokenEndpoint.asText());
+    }
+
+    @Test
+    void clientCredentialsGrantShouldReturnAccessToken() throws Exception {
+        // First retrieve the token endpoint from the discovery document
+        HttpRequest discoveryRequest = HttpRequest.newBuilder()
+                .uri(URI.create(OIDC_URL))
+                .timeout(Duration.ofSeconds(10))
+                .GET()
+                .build();
+        HttpResponse<String> discoveryResponse =
+                httpClient.send(discoveryRequest, 
HttpResponse.BodyHandlers.ofString());
+        assertEquals(200, discoveryResponse.statusCode(), "Could not reach 
Keycloak OIDC discovery");
+
+        String tokenEndpoint = objectMapper.readTree(discoveryResponse.body())
+                .get("token_endpoint")
+                .asText();
+
+        String form = "grant_type=" + encode("client_credentials")
+                + "&client_id=" + encode(CLIENT_ID)
+                + "&client_secret=" + encode(CLIENT_SECRET);
+
+        HttpRequest tokenRequest = HttpRequest.newBuilder()
+                .uri(URI.create(tokenEndpoint))
+                .timeout(Duration.ofSeconds(10))
+                .header("Content-Type", "application/x-www-form-urlencoded")
+                .POST(HttpRequest.BodyPublishers.ofString(form))
+                .build();
+
+        HttpResponse<String> tokenResponse =
+                httpClient.send(tokenRequest, 
HttpResponse.BodyHandlers.ofString());
+
+        assertEquals(200, tokenResponse.statusCode(),
+                "client_credentials grant failed with status " + 
tokenResponse.statusCode()
+                        + ": " + tokenResponse.body());
+
+        JsonNode tokenDoc = objectMapper.readTree(tokenResponse.body());
+        JsonNode accessToken = tokenDoc.get("access_token");
+        assertNotNull(accessToken, "Token response is missing 'access_token'");
+        assertTrue(accessToken.asText().length() > 0, "access_token is empty");
+    }
+
+    private static String encode(String value) {
+        return URLEncoder.encode(value, StandardCharsets.UTF_8);
+    }
+}
diff --git 
a/integration-tests/src/test/java/org/apache/airavata/integration/PrometheusHealthTest.java
 
b/integration-tests/src/test/java/org/apache/airavata/integration/PrometheusHealthTest.java
new file mode 100644
index 0000000000..74adef1beb
--- /dev/null
+++ 
b/integration-tests/src/test/java/org/apache/airavata/integration/PrometheusHealthTest.java
@@ -0,0 +1,87 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.airavata.integration;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.time.Duration;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Verifies that the Prometheus metrics endpoint served by {@code 
MonitoringServer}
+ * is reachable and returns valid Prometheus text format output.
+ */
+@Tag("integration")
+class PrometheusHealthTest {
+
+    private static final String HOST =
+            System.getProperty("airavata.monitoring.host", "localhost");
+    private static final int PORT =
+            Integer.parseInt(System.getProperty("airavata.monitoring.port", 
"9097"));
+
+    private static final String METRICS_URL = "http://"; + HOST + ":" + PORT + 
"/metrics";
+
+    private static HttpClient httpClient;
+
+    @BeforeAll
+    static void setUp() {
+        httpClient = HttpClient.newBuilder()
+                .connectTimeout(Duration.ofSeconds(10))
+                .build();
+    }
+
+    @Test
+    void metricsEndpointShouldReturn200() throws Exception {
+        HttpRequest request = HttpRequest.newBuilder()
+                .uri(URI.create(METRICS_URL))
+                .timeout(Duration.ofSeconds(10))
+                .GET()
+                .build();
+
+        HttpResponse<String> response = httpClient.send(request, 
HttpResponse.BodyHandlers.ofString());
+
+        assertEquals(200, response.statusCode(),
+                "Expected HTTP 200 from /metrics endpoint, got " + 
response.statusCode());
+    }
+
+    @Test
+    void metricsEndpointShouldReturnPrometheusTextFormat() throws Exception {
+        HttpRequest request = HttpRequest.newBuilder()
+                .uri(URI.create(METRICS_URL))
+                .timeout(Duration.ofSeconds(10))
+                .GET()
+                .build();
+
+        HttpResponse<String> response = httpClient.send(request, 
HttpResponse.BodyHandlers.ofString());
+        String body = response.body();
+
+        assertTrue(
+                body.contains("# HELP") || body.contains("# TYPE"),
+                "Expected Prometheus text format with '# HELP' or '# TYPE' 
markers, but body was: "
+                        + body.substring(0, Math.min(body.length(), 500)));
+    }
+}

Reply via email to