Skip to content

Commit

Permalink
feat: #6 Changes for better developer experience
Browse files Browse the repository at this point in the history
Signed-off-by: Laurent Broudoux <[email protected]>
  • Loading branch information
lbroudoux committed Aug 4, 2023
1 parent 5645130 commit e314b62
Show file tree
Hide file tree
Showing 15 changed files with 734 additions and 114 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/build-verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ jobs:
- name: Checkout Code
uses: actions/checkout@v3

- name: Set up JDK 17 for x64
- name: Set up JDK 11 for x64
uses: actions/setup-java@v3
with:
java-version: '17'
java-version: '11'
distribution: 'temurin'
architecture: x64
cache: maven
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/sonar-cloud-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ jobs:
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis

- name: Set up JDK 17 for x64
- name: Set up JDK 11 for x64
uses: actions/setup-java@v3
with:
java-version: '17'
java-version: '11'
distribution: 'temurin'
architecture: x64

Expand Down
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Microcks Testcontainers Jva
# Microcks Testcontainers Java

Java library for Testcontainers that enables embedding Microcks into your JUnit tests with lightweight, throwaway instance thanks to containers

Expand Down Expand Up @@ -70,10 +70,12 @@ During your test setup, you'd probably need to retrieve mock endpoints provided
setup your base API url calls. You can do it like this:

```java
String baseApiUrl = microcks.getMockEndpoint(ServiceType.REST, "API Pastries", "0.0.1");
String baseApiUrl = microcks.getRestMockEndpoint("API Pastries", "0.0.1");
```

The container also provides `getHttpEndpoint()` and `getGrpcEndpoint()` methods for raw access to those API endpoints.
The container provides methods for different supported API styles/protocols (Soap, GraphQL, gRPC,...).

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

### Launching new contract-tests

Expand All @@ -84,7 +86,7 @@ you can launch a Microcks contract/conformance test using the local server port
@LocalServerPort
private int port;

TestRequestDTO testRequest = new TestRequestDTO.Builder()
TestRequest testRequest = new TestRequest.Builder()
.serviceId("API Pastries:0.0.1")
.runnerType(TestRunnerType.OPEN_API_SCHEMA.name())
.testEndpoint("http://localhost:" + port)
Expand Down
16 changes: 2 additions & 14 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<groupId>io.github.microcks</groupId>
<artifactId>microcks-testcontainers</artifactId>
<packaging>jar</packaging>
<version>0.0.1-SNAPSHOT</version>
<version>0.1.0-SNAPSHOT</version>

<name>Microcks Testcontainers</name>
<description>Microcks Testcontainers Java library</description>
Expand Down Expand Up @@ -42,8 +42,7 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<sonar.organization>microcks</sonar.organization>
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
<java.version>17</java.version>
<microcks.version>1.7.1</microcks.version>
<java.version>11</java.version>
<testcontainers.version>1.18.3</testcontainers.version>
<httpmime.version>4.5.14</httpmime.version>
<jupiter.version>5.8.1</jupiter.version>
Expand All @@ -61,17 +60,6 @@
<artifactId>httpmime</artifactId>
<version>${httpmime.version}</version>
</dependency>
<dependency>
<groupId>io.github.microcks</groupId>
<artifactId>microcks-model</artifactId>
<version>${microcks.version}</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
Expand Down
125 changes: 72 additions & 53 deletions src/main/java/io/github/microcks/testcontainers/MicrocksContainer.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
*/
package io.github.microcks.testcontainers;

import io.github.microcks.domain.ServiceType;
import io.github.microcks.domain.TestResult;
import io.github.microcks.testcontainers.model.TestResult;
import io.github.microcks.testcontainers.model.TestRequest;

import com.fasterxml.jackson.annotation.JsonInclude;
import org.apache.http.HttpEntity;
Expand All @@ -28,9 +28,10 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.output.Slf4jLogConsumer;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper;
import org.testcontainers.shaded.org.awaitility.Awaitility;
import org.testcontainers.shaded.org.awaitility.core.ConditionTimeoutException;
import org.testcontainers.utility.DockerImageName;

import java.io.File;
Expand All @@ -43,6 +44,7 @@
import java.nio.channels.Channels;
import java.nio.channels.Pipe;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;

/**
* Testcontainers implementation for main Microcks container.
Expand All @@ -61,6 +63,15 @@ public class MicrocksContainer extends GenericContainer<MicrocksContainer> {

private ObjectMapper mapper;

/**
* Build a new MicrocksContainer with its container image name as string. This image must
* be compatible with quay.io/microcks/microcks-uber image.
* @param image The name (with tag/version) of Microcks Uber distribution to use.
*/
public MicrocksContainer(String image) {
this(DockerImageName.parse(image));
}

/**
* Build a new MicrocksContainer with its full container image name. This image must
* be compatible with quay.io/microcks/microcks-uber image.
Expand All @@ -71,57 +82,64 @@ public MicrocksContainer(DockerImageName imageName) {
imageName.assertCompatibleWith(MICROCKS_IMAGE);

withExposedPorts(MICROCKS_HTTP_PORT, MICROCKS_GRPC_PORT);
withLogConsumer(new Slf4jLogConsumer(log));

waitingFor(Wait.forLogMessage(".*Started MicrocksApplication.*", 1));
}

/**
* Get the Http endpoint where Microcks can be accessed (you'd have to append '/api' to access APIs)
* @return The Http endpoint for talking to container.
*/
public String getHttpEndpoint() {
return String.format("http://%s:%s", getHost(), getMappedPort(MICROCKS_HTTP_PORT));
}

public String getGrpcEndpoint() {
return String.format("grpc://%s:%s", getHost(), getMappedPort(MICROCKS_GRPC_PORT));
/**
* Get the exposed mock endpoint for a SOAP Service.
* @param service The name of Service/API
* @param version The version of Service/API
* @return A usable endpoint to interact with Microcks mocks.
*/
public String getSoapMockEndpoint(String service, String version) {
return String.format("%s/soap/%s/%s", getHttpEndpoint(), service, version);
}

/**
* Get the exposed mock endpoint for a specified Service/API.
* @param type The type of service (eg. API style or protocol)
* Get the exposed mock endpoint for a REST API.
* @param service The name of Service/API
* @param version The version of Service/API
* @return A usable endpoint to interact with Microcks mocks.
*/
public String getMockEndpoint(ServiceType type, String service, String version) {
switch (type) {
case SOAP_HTTP -> {
return String.format("%s/soap/%s/%s", getHttpEndpoint(), service, version);
}
case REST -> {
return String.format("%s/rest/%s/%s", getHttpEndpoint(), service, version);
}
case GRAPHQL -> {
return String.format("%s/graphql/%s/%s", getHttpEndpoint(), service, version);
}
case GENERIC_REST -> {
return String.format("%s/dynarest/%s/%s", getHttpEndpoint(), service, version);
}
case GRPC -> {
return getGrpcEndpoint();
}
case EVENT, GENERIC_EVENT -> {
return "Unsupported at the moment";
}
}
return getHttpEndpoint();
public String getRestMockEndpoint(String service, String version) {
return String.format("%s/rest/%s/%s", getHttpEndpoint(), service, version);
}

/**
* Get the exposed mock endpoint for a GRPC Service.
* @param service The name of Service/API
* @param version The version of Service/API
* @return A usable endpoint to interact with Microcks mocks.
*/
public String getGraphQLMockEndpoint(String service, String version) {
return String.format("%s/graphql/%s/%s", getHttpEndpoint(), service, version);
}

/**
* Get the exposed mock endpoint for a GRPC Service.
* @return A usable endpoint to interact with Microcks mocks.
*/
public String getGrpcMockEndpoint() {
return String.format("grpc://%s:%s", getHost(), getMappedPort(MICROCKS_GRPC_PORT));
}

/**
* Import an artifact as a primary or main one within the Microcks container.
* @param artifact The file representing artifact (OpenAPI, Postman collection, Protobuf, GraphQL schema, ...)
* @throws IOException If file cannot be read of transmission exception occurs.
* @throws InterruptedException If connection to the docker container is interrupted.
* @throws MicrocksException If artifact cannot be correctly imported in container (probably malformed)
*/
public void importAsMainArtifact(File artifact) throws IOException, InterruptedException {
public void importAsMainArtifact(File artifact) throws IOException, InterruptedException, MicrocksException {
importArtifact(artifact, true);
}

Expand All @@ -130,8 +148,9 @@ public void importAsMainArtifact(File artifact) throws IOException, InterruptedE
* @param artifact The file representing artifact (OpenAPI, Postman collection, Protobuf, GraphQL schema, ...)
* @throws IOException If file cannot be read of transmission exception occurs.
* @throws InterruptedException If connection to the docker container is interrupted.
* @throws MicrocksException If artifact cannot be correctly imported in container (probably malformed)
*/
public void importAsSecondaryArtifact(File artifact) throws IOException, InterruptedException {
public void importAsSecondaryArtifact(File artifact) throws IOException, InterruptedException, MicrocksException {
importArtifact(artifact, false);
}

Expand All @@ -143,10 +162,7 @@ public void importAsSecondaryArtifact(File artifact) throws IOException, Interru
* @throws InterruptedException If connection to Microcks container is interrupted
* @throws MicrocksException If Microcks fails creating a new test giving your request.
*/
public TestResult testEndpoint(TestRequestDTO testRequest) throws IOException, InterruptedException, MicrocksException {
long startTime = System.currentTimeMillis();
long wait = testRequest.getTimeout();

public TestResult testEndpoint(TestRequest testRequest) throws IOException, InterruptedException, MicrocksException {
String requestBody = getMapper().writeValueAsString(testRequest);

HttpClient client = HttpClient.newHttpClient();
Expand All @@ -162,23 +178,21 @@ public TestResult testEndpoint(TestRequestDTO testRequest) throws IOException, I

if (response.statusCode() == 201) {
TestResult testResult = getMapper().readValue(response.body(), TestResult.class);
log.debug("Got Test Result: {}", testResult.getId());

while (System.currentTimeMillis() < (startTime + wait)) {
testResult = refreshTestResult(testResult.getId());
if (!testResult.isInProgress()) {
break;
}

try {
// Else sleep for 500 ms before refreshing the status.
Thread.sleep(500);
} catch (InterruptedException ie) {
// It's fine to be interrupted here we'll loop again.
}
log.debug("Got Test Result: {}, now polling for progression", testResult.getId());

final String testResultId = testResult.getId();
try {
Awaitility.await()
.atMost(testRequest.getTimeout(), TimeUnit.MILLISECONDS)
.pollDelay(100, TimeUnit.MILLISECONDS)
.pollInterval(200, TimeUnit.MILLISECONDS)
.until(() -> !refreshTestResult(testResultId).isInProgress());
} catch (ConditionTimeoutException timeoutException) {
log.info("Caught a ConditionTimeoutException for test on {}", testRequest.getTestEndpoint());
}

return testResult;
// Return the final result.
return refreshTestResult(testResultId);
}
if (log.isErrorEnabled()) {
log.error("Couldn't launch on new test on Microcks with status {} ", response.statusCode());
Expand All @@ -187,7 +201,11 @@ public TestResult testEndpoint(TestRequestDTO testRequest) throws IOException, I
throw new MicrocksException("Couldn't launch on new test on Microcks. Please check Microcks container logs");
}

private void importArtifact(File artifact, boolean mainArtifact) throws IOException, InterruptedException {
private void importArtifact(File artifact, boolean mainArtifact) throws IOException, InterruptedException, MicrocksException {
if (!artifact.exists()) {
throw new IOException("Artifact " + artifact.getPath() + " does not exist or can't be read.");
}

HttpEntity httpEntity = MultipartEntityBuilder.create()
.addBinaryBody("file", artifact, ContentType.APPLICATION_OCTET_STREAM, artifact.getName())
.build();
Expand Down Expand Up @@ -221,7 +239,8 @@ private void importArtifact(File artifact, boolean mainArtifact) throws IOExcept
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

if (response.statusCode() != 201) {
log.error("");
log.error("Artifact has not been correctly been imported: {}", response.body());
throw new MicrocksException("Artifact has not been correctly been imported: " + response.body());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@
* specific language governing permissions and limitations
* under the License.
*/
package io.github.microcks.testcontainers;
package io.github.microcks.testcontainers.model;

/**
* Data Transfer object for basic header with its values (comma separated string).
* @author laurent
*/
public class HeaderDTO {
public class Header {

private String name;
private String values;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Licensed to Laurent Broudoux (the "Author") under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. Author 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 io.github.microcks.testcontainers.model;

import java.util.HashMap;
import java.util.Set;

/**
* Specification of additional headers for a Service/API operations. Keys are operation name or "globals"
* (if header applies to all), values are Header objects.
* @author laurent
*/
public class OperationsHeaders extends HashMap<String, Set<Header>> {

public static final String GLOBALS = "globals";

public Set<Header> getGlobals() {
return this.get(GLOBALS);
}
}
Loading

0 comments on commit e314b62

Please sign in to comment.