Skip to content

Commit

Permalink
feat: add methods to verify service invocation
Browse files Browse the repository at this point in the history
Signed-off-by: Pierre CHRISTIN <christin.pierre@imsa.msa.fr>
  • Loading branch information
pierrechristinimsa committed Dec 24, 2024
1 parent bb4fa6e commit 235e188
Show file tree
Hide file tree
Showing 4 changed files with 436 additions and 12 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,21 @@ The container provides methods for different supported API styles/protocols (Soa

The container also provides `getHttpEndpoint()` for raw access to those API endpoints.

### Verifying mock endpoint has been invoked

Once the mock endpoint has been invoked, you'd probably need to ensure that the mock have been really invoked.

You can do it like this :

```java
Boolean serviceMockInvoked = microcks.verify("API Pastries", "0.0.1");
```

Or like this :
```java
Long serviceInvocationsCount = microcks.getServiceInvocationsCount("API Pastries", "0.0.1");
```

### Launching new contract-tests

If you want to ensure that your application under test is conformant to an OpenAPI contract (or other type of contract),
Expand Down
139 changes: 132 additions & 7 deletions src/main/java/io/github/microcks/testcontainers/MicrocksContainer.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package io.github.microcks.testcontainers;

import io.github.microcks.testcontainers.model.DailyInvocationStatistic;
import io.github.microcks.testcontainers.model.RequestResponsePair;
import io.github.microcks.testcontainers.model.Secret;
import io.github.microcks.testcontainers.model.TestResult;
Expand All @@ -30,6 +31,7 @@
import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper;
import org.testcontainers.shaded.com.github.dockerjava.core.MediaType;
import org.testcontainers.shaded.com.google.common.net.HttpHeaders;
import org.testcontainers.shaded.com.google.common.net.UrlEscapers;
import org.testcontainers.shaded.org.awaitility.Awaitility;
import org.testcontainers.shaded.org.awaitility.core.ConditionTimeoutException;
import org.testcontainers.utility.DockerImageName;
Expand All @@ -43,15 +45,14 @@
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.math.BigDecimal;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.TimeUnit;
Expand All @@ -75,6 +76,8 @@ public class MicrocksContainer extends GenericContainer<MicrocksContainer> {

private static ObjectMapper mapper;

private static SimpleDateFormat metricsApiDayFormatter = new SimpleDateFormat("yyyyMMdd");

private Set<String> snapshotsToImport;
private Set<String> mainArtifactsToImport;
private Set<String> secondaryArtifactsToImport;
Expand Down Expand Up @@ -561,6 +564,115 @@ public void downloadAsSecondaryRemoteArtifact(String remoteArtifactUrl) throws A
downloadArtifact(remoteArtifactUrl, false);
}

/**
* Verifies that given Service has been invoked at least one time, for the current invocations' date.
*
* @param serviceName mandatory
* @param serviceVersion mandatory
* @return false if the given service was not found, or if the daily invocation's count is zero. Else, returns true if the daily invocations' count for the given service is at least one.
*/
public boolean verify(String serviceName, String serviceVersion) {
return this.doVerify(serviceName, serviceVersion, null);
}

/**
* Verifies that given Service has been invoked at least one time, for the given invocations' date.
* In unit testing context, it should be useless to pass the invocations date, prefer calling {@link #verify(String, String)}`.
*
* @param serviceName mandatory
* @param serviceVersion mandatory
* @param invocationsDate nullable
* @return false if the given service was not found, or if the daily invocation's count is zero. Else, returns true if the daily invocations' count for the given service is at least one.
*/
public boolean verify(String serviceName, String serviceVersion, Date invocationsDate) {
return this.doVerify(serviceName, serviceVersion, invocationsDate);
}

private boolean doVerify(String serviceName, String serviceVersion, Date invocationsDate) {
DailyInvocationStatistic dailyInvocationStatistic = getServiceInvocations(getHttpEndpoint(), serviceName, serviceVersion, invocationsDate);

if (dailyInvocationStatistic == null) {
return false;
}

BigDecimal count = dailyInvocationStatistic.getDailyCount();

return count != null && count.intValue() != 0;
}

/**
* Get the invocations' count for a given service, identified by its name and version, for the current invocations' date.
*
* @param serviceName mandatory
* @param serviceVersion mandatory
* @return zero if the given service was not found, or has never been invoked. Else, returns the daily count of invocations for the given service.
*/
public Long getServiceInvocationsCount(String serviceName, String serviceVersion) {
return this.doGetServiceInvocationsCount(serviceName, serviceVersion, null);
}

/**
* Get the invocations' count for a given service, identified by its name and version, for the given invocations' date.
* In unit testing context, it should be useless to pass the invocations date, prefer calling {@link #getServiceInvocationsCount(String, String)}`.
*
* @param serviceName mandatory
* @param serviceVersion mandatory
* @param invocationsDate nullable
* @return zero if the given service was not found, or has never been invoked. Else, returns the daily count of invocations for the given service.
*/
public Long getServiceInvocationsCount(String serviceName, String serviceVersion, Date invocationsDate) {
return this.doGetServiceInvocationsCount(serviceName, serviceVersion, invocationsDate);
}

private Long doGetServiceInvocationsCount(String serviceName, String serviceVersion, Date invocationsDate) {
DailyInvocationStatistic dailyInvocationStatistic = getServiceInvocations(getHttpEndpoint(), serviceName, serviceVersion, invocationsDate);

if (dailyInvocationStatistic == null) {
return 0L;
}

BigDecimal count = dailyInvocationStatistic.getDailyCount();

return count.longValue();
}


/**
* Returns all data from Microcks Metrics REST API about the invocations of a given service.
*
* @param microcksContainerHttpEndpoint mandatory
* @param serviceName mandatory
* @param serviceVersion mandatory
* @param invocationsDate nullable
* @return
*/
private static DailyInvocationStatistic getServiceInvocations(String microcksContainerHttpEndpoint, String serviceName, String serviceVersion, Date invocationsDate) {
String encodedServiceName = UrlEscapers.urlFragmentEscaper().escape(serviceName);
String encodedServiceVersion = UrlEscapers.urlFragmentEscaper().escape(serviceVersion);

String restApiURL = String.format("%s/api/metrics/invocations/%s/%s", microcksContainerHttpEndpoint, encodedServiceName, encodedServiceVersion);

if (invocationsDate != null) {
restApiURL += "?day=" + metricsApiDayFormatter.format(invocationsDate);
}

try {
Thread.sleep(100); // to avoid race condition issue when requesting Microcks Metrics REST API
} catch (InterruptedException e) {
log.warn("Failed to sleep before calling Microcks API", e);
Thread.currentThread().interrupt();
}

try {
StringBuilder content = getFromRestApi(restApiURL);
return content.length() == 0 ? null : getMapper().readValue(content.toString(), DailyInvocationStatistic.class);
} catch (IOException e) {
log.warn("Failed to get service's invocations at address " + restApiURL, e);
}

return null;
}

private void importArtifact(String artifactPath, boolean mainArtifact) {
URL resource = Thread.currentThread().getContextClassLoader().getResource(artifactPath);
if (resource == null) {
Expand Down Expand Up @@ -727,8 +839,21 @@ private static HttpURLConnection uploadFileToMicrocks(URL microcksApiURL, File f
}

private static TestResult refreshTestResult(String microcksContainerHttpEndpoint, String testResultId) throws IOException {
StringBuilder content = getFromRestApi(microcksContainerHttpEndpoint + "/api/tests/" + testResultId);

return getMapper().readValue(content.toString(), TestResult.class);
}

/**
* Does a GET HTTP call to Microcks REST API and expecting to obtain a 200 response with a `application/json` body: returns it as a StringBuilder.
*
* @param restApiURL mandatory
* @return
* @throws IOException
*/
private static StringBuilder getFromRestApi(String restApiURL) throws IOException {
// Build a new client on correct API endpoint.
URL url = new URL(microcksContainerHttpEndpoint + "/api/tests/" + testResultId);
URL url = new URL(restApiURL);
HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
httpConn.setRequestMethod("GET");
httpConn.setRequestProperty("Accept", "application/json");
Expand All @@ -742,10 +867,10 @@ private static TestResult refreshTestResult(String microcksContainerHttpEndpoint
content.append(inputLine);
}
}

// Disconnect Http connection.
httpConn.disconnect();

return getMapper().readValue(content.toString(), TestResult.class);
return content;
}

public static class ArtifactLoadException extends RuntimeException {
Expand Down
Loading

0 comments on commit 235e188

Please sign in to comment.