Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add methods to verify service invocation #121

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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,12 +45,15 @@
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.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
Expand All @@ -75,6 +80,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 +568,167 @@ 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 doVerify(getHttpEndpoint(), serviceName, serviceVersion, null);
}

/**
* Verifies that given Service has been invoked at least one time, for the current invocations' date.
*
* @param microcksContainerHttpEndpoint mandatory
* @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 static boolean verify(String microcksContainerHttpEndpoint, String serviceName, String serviceVersion) {
return doVerify(microcksContainerHttpEndpoint, 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 doVerify(getHttpEndpoint(), serviceName, serviceVersion, invocationsDate);
}

/**
* 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 microcksContainerHttpEndpoint mandatory
* @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 static boolean verify(String microcksContainerHttpEndpoint, String serviceName, String serviceVersion, Date invocationsDate) {
return doVerify(microcksContainerHttpEndpoint, serviceName, serviceVersion, invocationsDate);
}

private static boolean doVerify(String microcksContainerHttpEndpoint, String serviceName, String serviceVersion, Date invocationsDate) {
DailyInvocationStatistic dailyInvocationStatistic = getServiceInvocations(microcksContainerHttpEndpoint, 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 doGetServiceInvocationsCount(getHttpEndpoint(), serviceName, serviceVersion, null);
}

/**
* Get the invocations' count for a given service, identified by its name and version, for the current invocations' date.
*
* @param microcksContainerHttpEndpoint mandatory
* @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 static Long getServiceInvocationsCount(String microcksContainerHttpEndpoint, String serviceName, String serviceVersion) {
return doGetServiceInvocationsCount(microcksContainerHttpEndpoint, 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 doGetServiceInvocationsCount(getHttpEndpoint(), serviceName, serviceVersion, invocationsDate);
}

/**
* 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 microcksContainerHttpEndpoint mandatory
* @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 static Long getServiceInvocationsCount(String microcksContainerHttpEndpoint, String serviceName, String serviceVersion, Date invocationsDate) {
return doGetServiceInvocationsCount(microcksContainerHttpEndpoint, serviceName, serviceVersion, invocationsDate);
}

private static Long doGetServiceInvocationsCount(String microcksContainerHttpEndpoint, String serviceName, String serviceVersion, Date invocationsDate) {
DailyInvocationStatistic dailyInvocationStatistic = getServiceInvocations(microcksContainerHttpEndpoint, 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);
Comment on lines +706 to +707
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this is needed as most of the HTTP client libraries or utilities are doing the encoding of the URL. Could you please check this one? If actually needed, couldn't we use instead the standard URLEncoder.encode() to avoid depending on third-party libs?

Copy link
Author

@pierrechristinimsa pierrechristinimsa Dec 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I encountered space character encoding issue.
Using URLEncoder.encode(), with UTF-8, it is changed in "%20".
Whereas the Metrics REST API expects the space character to be changed to "+".
Maybe there is another way to encode the service name so as it is correctly sent to the Metrics REST API ?


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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the link, interesting to see notably the event management.

} 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 +895,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 +923,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