Skip to content

Commit 578f667

Browse files
committed
Add integration tests
1 parent 942071a commit 578f667

34 files changed

+3248
-20
lines changed

api-client/build.gradle.kts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ plugins {
1212
id("io.freefair.maven-central.validate-poms")
1313
}
1414

15-
val remoteApiVersion = "1.41"
16-
1715
java {
1816
sourceCompatibility = JavaVersion.VERSION_1_8
1917
targetCompatibility = JavaVersion.VERSION_1_8
@@ -26,13 +24,22 @@ dependencies {
2624
kapt("com.squareup.moshi:moshi-kotlin-codegen:1.12.0")
2725
implementation("com.squareup.okhttp3:okhttp:[4.9,5)!!4.9.2")
2826
// implementation("com.squareup.okhttp3:logging-interceptor:[4.9,5)!!4.9.2")
29-
testImplementation("io.kotlintest:kotlintest-runner-junit5:3.4.2")
30-
implementation("de.gesellix:docker-remote-api-model-${remoteApiVersion.replace(".", "-")}:2021-10-04T21-50-00")
27+
implementation("de.gesellix:docker-remote-api-model-1-41:2021-10-16T19-28-00")
3128
implementation("de.gesellix:docker-engine:2021-10-09T19-06-00")
3229
implementation("de.gesellix:docker-filesocket:2021-09-20T20-10-00")
3330

3431
implementation("org.slf4j:slf4j-api:[1.7,)!!1.7.32")
3532
testImplementation("ch.qos.logback:logback-classic:[1.2,2)!!1.2.6")
33+
34+
testImplementation("io.kotlintest:kotlintest-runner-junit5:3.4.2")
35+
testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1")
36+
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1")
37+
testRuntimeOnly("cglib:cglib-nodep:3.3.0")
38+
testImplementation("org.junit.platform:junit-platform-launcher:1.8.0")
39+
testImplementation("org.junit.platform:junit-platform-commons:1.8.0")
40+
41+
testImplementation("org.apache.commons:commons-compress:1.21")
42+
testImplementation("de.gesellix:testutil:[2021-08-05T22-09-32,)")
3643
}
3744

3845
tasks.withType(Test::class.java) {
@@ -76,7 +83,7 @@ publishing {
7683
register(publicationName, MavenPublication::class) {
7784
pom {
7885
name.set("docker-remote-api-client")
79-
description.set("Client for the Docker remote api v${remoteApiVersion}")
86+
description.set("Client for the Docker remote api")
8087
url.set("https://github.com/docker-client/docker-remote-api-client")
8188
licenses {
8289
license {
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package de.gesellix.docker.remote.api;
2+
3+
import de.gesellix.docker.remote.api.client.ConfigApi;
4+
import de.gesellix.docker.remote.api.client.ContainerApi;
5+
import de.gesellix.docker.remote.api.client.DistributionApi;
6+
import de.gesellix.docker.remote.api.client.ExecApi;
7+
import de.gesellix.docker.remote.api.client.ImageApi;
8+
import de.gesellix.docker.remote.api.client.NetworkApi;
9+
import de.gesellix.docker.remote.api.client.NodeApi;
10+
import de.gesellix.docker.remote.api.client.PluginApi;
11+
import de.gesellix.docker.remote.api.client.SecretApi;
12+
import de.gesellix.docker.remote.api.client.ServiceApi;
13+
import de.gesellix.docker.remote.api.client.SwarmApi;
14+
import de.gesellix.docker.remote.api.client.SystemApi;
15+
import de.gesellix.docker.remote.api.client.TaskApi;
16+
import de.gesellix.docker.remote.api.client.VolumeApi;
17+
18+
public interface EngineApiClient {
19+
20+
ConfigApi getConfigApi();
21+
22+
ContainerApi getContainerApi();
23+
24+
DistributionApi getDistributionApi();
25+
26+
ExecApi getExecApi();
27+
28+
ImageApi getImageApi();
29+
30+
NetworkApi getNetworkApi();
31+
32+
NodeApi getNodeApi();
33+
34+
PluginApi getPluginApi();
35+
36+
SecretApi getSecretApi();
37+
38+
ServiceApi getServiceApi();
39+
40+
SwarmApi getSwarmApi();
41+
42+
SystemApi getSystemApi();
43+
44+
TaskApi getTaskApi();
45+
46+
VolumeApi getVolumeApi();
47+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package de.gesellix.docker.remote.api;
2+
3+
import de.gesellix.docker.remote.api.client.ConfigApi;
4+
import de.gesellix.docker.remote.api.client.ContainerApi;
5+
import de.gesellix.docker.remote.api.client.DistributionApi;
6+
import de.gesellix.docker.remote.api.client.ExecApi;
7+
import de.gesellix.docker.remote.api.client.ImageApi;
8+
import de.gesellix.docker.remote.api.client.NetworkApi;
9+
import de.gesellix.docker.remote.api.client.NodeApi;
10+
import de.gesellix.docker.remote.api.client.PluginApi;
11+
import de.gesellix.docker.remote.api.client.SecretApi;
12+
import de.gesellix.docker.remote.api.client.ServiceApi;
13+
import de.gesellix.docker.remote.api.client.SwarmApi;
14+
import de.gesellix.docker.remote.api.client.SystemApi;
15+
import de.gesellix.docker.remote.api.client.TaskApi;
16+
import de.gesellix.docker.remote.api.client.VolumeApi;
17+
18+
public class EngineApiClientImpl implements EngineApiClient {
19+
20+
private final ConfigApi configApi;
21+
private final ContainerApi containerApi;
22+
private final DistributionApi distributionApi;
23+
private final ExecApi execApi;
24+
private final ImageApi imageApi;
25+
private final NetworkApi networkApi;
26+
private final NodeApi nodeApi;
27+
private final PluginApi pluginApi;
28+
private final SecretApi secretApi;
29+
private final ServiceApi serviceApi;
30+
private final SwarmApi swarmApi;
31+
private final SystemApi systemApi;
32+
private final TaskApi taskApi;
33+
private final VolumeApi volumeApi;
34+
35+
public EngineApiClientImpl() {
36+
configApi = new ConfigApi();
37+
containerApi = new ContainerApi();
38+
distributionApi = new DistributionApi();
39+
execApi = new ExecApi();
40+
imageApi = new ImageApi();
41+
networkApi = new NetworkApi();
42+
nodeApi = new NodeApi();
43+
pluginApi = new PluginApi();
44+
secretApi = new SecretApi();
45+
serviceApi = new ServiceApi();
46+
swarmApi = new SwarmApi();
47+
systemApi = new SystemApi();
48+
taskApi = new TaskApi();
49+
volumeApi = new VolumeApi();
50+
}
51+
52+
@Override
53+
public ConfigApi getConfigApi() {
54+
return configApi;
55+
}
56+
57+
@Override
58+
public ContainerApi getContainerApi() {
59+
return containerApi;
60+
}
61+
62+
@Override
63+
public DistributionApi getDistributionApi() {
64+
return distributionApi;
65+
}
66+
67+
@Override
68+
public ExecApi getExecApi() {
69+
return execApi;
70+
}
71+
72+
@Override
73+
public ImageApi getImageApi() {
74+
return imageApi;
75+
}
76+
77+
@Override
78+
public NetworkApi getNetworkApi() {
79+
return networkApi;
80+
}
81+
82+
@Override
83+
public NodeApi getNodeApi() {
84+
return nodeApi;
85+
}
86+
87+
@Override
88+
public PluginApi getPluginApi() {
89+
return pluginApi;
90+
}
91+
92+
@Override
93+
public SecretApi getSecretApi() {
94+
return secretApi;
95+
}
96+
97+
@Override
98+
public ServiceApi getServiceApi() {
99+
return serviceApi;
100+
}
101+
102+
@Override
103+
public SwarmApi getSwarmApi() {
104+
return swarmApi;
105+
}
106+
107+
@Override
108+
public SystemApi getSystemApi() {
109+
return systemApi;
110+
}
111+
112+
@Override
113+
public TaskApi getTaskApi() {
114+
return taskApi;
115+
}
116+
117+
@Override
118+
public VolumeApi getVolumeApi() {
119+
return volumeApi;
120+
}
121+
}

api-client/src/main/kotlin/de/gesellix/docker/remote/api/core/ApiClient.kt

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import okhttp3.ResponseBody
2626
import okio.Source
2727
import java.io.File
2828
import java.lang.reflect.Type
29+
import java.util.*
2930
import java.util.concurrent.TimeUnit
3031

3132
open class ApiClient(val baseUrl: String, val dockerClientConfig: DockerClientConfig = DockerClientConfig()) {
@@ -180,26 +181,41 @@ open class ApiClient(val baseUrl: String, val dockerClientConfig: DockerClientCo
180181
}
181182

182183
// TODO: support multiple contentType options here.
183-
val mediaType = (headers[ContentType] as String).substringBefore(";").toLowerCase()
184+
val mediaType = (headers[ContentType] as String).substringBefore(";").lowercase(Locale.getDefault())
184185

185-
val request = when (requestConfig.method) {
186-
DELETE -> Request.Builder().url(url).delete(requestBody(requestConfig.body, mediaType))
187-
GET -> Request.Builder().url(url)
188-
HEAD -> Request.Builder().url(url).head()
189-
PATCH -> Request.Builder().url(url).patch(requestBody(requestConfig.body, mediaType))
190-
PUT -> Request.Builder().url(url).put(requestBody(requestConfig.body, mediaType))
191-
POST -> Request.Builder().url(url).post(requestBody(requestConfig.body, mediaType))
192-
OPTIONS -> Request.Builder().url(url).method("OPTIONS", null)
186+
val requestBuilder = when (requestConfig.method) {
187+
DELETE -> Request.Builder()
188+
.url(url)
189+
.delete(requestBody(requestConfig.body, mediaType))
190+
GET -> Request.Builder()
191+
.url(url)
192+
HEAD -> Request.Builder()
193+
.url(url)
194+
.head()
195+
PATCH -> Request.Builder()
196+
.url(url)
197+
.patch(requestBody(requestConfig.body, mediaType))
198+
PUT -> Request.Builder()
199+
.url(url)
200+
.put(requestBody(requestConfig.body, mediaType))
201+
POST -> Request.Builder()
202+
.url(url)
203+
.post(requestBody(requestConfig.body, mediaType))
204+
OPTIONS -> Request.Builder()
205+
.url(url)
206+
.method("OPTIONS", null)
193207
null -> throw IllegalStateException("Request method is null")
194208
}.apply {
195209
headers.forEach { header -> addHeader(header.key, header.value) }
196210
}.apply {
197211
tag(EnforceResponseContentTypeConfig::class.java, EnforceResponseContentTypeConfig(fallbackContentType))
198-
}.build()
199-
return request
212+
}
213+
// requestBuilder.cacheControl(FORCE_NETWORK)
214+
return requestBuilder.build()
200215
}
201216

202217
protected fun prepareClient(requestConfig: EngineRequest): OkHttpClient {
218+
// Logger.getLogger(OkHttpClient::class.java.name).level = Level.FINE
203219
// val engineResponse = engineClient.request(requestConfig)
204220
val actualClient = buildHttpClient(client.newBuilder())
205221
// .proxy(proxy) // TODO
@@ -212,7 +228,7 @@ open class ApiClient(val baseUrl: String, val dockerClientConfig: DockerClientCo
212228

213229
protected inline fun <reified T : Any?> request(request: Request, client: OkHttpClient, elementType: Type? = null): ApiInfrastructureResponse<T?> {
214230
val response = client.newCall(request).execute()
215-
val mediaType = response.header(ContentType)?.substringBefore(";")?.toLowerCase()
231+
val mediaType = response.header(ContentType)?.substringBefore(";")?.lowercase(Locale.getDefault())
216232

217233
// TODO: handle specific mapping types. e.g. Map<int, Class<?>>
218234
when {
@@ -247,7 +263,7 @@ open class ApiClient(val baseUrl: String, val dockerClientConfig: DockerClientCo
247263

248264
protected inline fun <reified T : Any?> requestStream(request: Request, client: OkHttpClient): ApiInfrastructureResponse<T?> {
249265
val response = client.newCall(request).execute()
250-
val mediaType = response.header(ContentType)?.substringBefore(";")?.toLowerCase()
266+
val mediaType = response.header(ContentType)?.substringBefore(";")?.lowercase(Locale.getDefault())
251267

252268
// TODO: handle specific mapping types. e.g. Map<int, Class<?>>
253269
when {
@@ -282,7 +298,7 @@ open class ApiClient(val baseUrl: String, val dockerClientConfig: DockerClientCo
282298

283299
protected inline fun requestFrames(request: Request, client: OkHttpClient, expectMultiplexedResponse: Boolean = false): ApiInfrastructureResponse<Frame> {
284300
val response = client.newCall(request).execute()
285-
val mediaType = response.header(ContentType)?.substringBefore(";")?.toLowerCase()
301+
val mediaType = response.header(ContentType)?.substringBefore(";")?.lowercase(Locale.getDefault())
286302

287303
// TODO: handle specific mapping types. e.g. Map<int, Class<?>>
288304
when {
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package de.gesellix.docker.remote.api.client;
2+
3+
import de.gesellix.docker.remote.api.Config;
4+
import de.gesellix.docker.remote.api.ConfigSpec;
5+
import de.gesellix.docker.remote.api.EngineApiClient;
6+
import de.gesellix.docker.remote.api.IdResponse;
7+
import de.gesellix.docker.remote.api.LocalNodeState;
8+
import de.gesellix.docker.remote.api.testutil.DockerEngineAvailable;
9+
import de.gesellix.docker.remote.api.testutil.InjectDockerClient;
10+
import org.junit.jupiter.api.AfterEach;
11+
import org.junit.jupiter.api.BeforeEach;
12+
import org.junit.jupiter.api.Test;
13+
14+
import java.util.Base64;
15+
import java.util.Collections;
16+
import java.util.List;
17+
import java.util.Objects;
18+
import java.util.stream.Stream;
19+
20+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
21+
import static org.junit.jupiter.api.Assertions.assertEquals;
22+
import static org.junit.jupiter.api.Assertions.assertNotNull;
23+
import static org.junit.jupiter.api.Assertions.assertTrue;
24+
25+
@DockerEngineAvailable(requiredSwarmMode = LocalNodeState.Active)
26+
class ConfigApiIntegrationTest {
27+
28+
@InjectDockerClient
29+
private EngineApiClient engineApiClient;
30+
31+
ConfigApi configApi;
32+
33+
IdResponse defaultConfig;
34+
35+
@BeforeEach
36+
public void setup() {
37+
configApi = engineApiClient.getConfigApi();
38+
39+
String encoded = Base64.getEncoder().encodeToString("config-data".getBytes());
40+
defaultConfig = configApi.configCreate(new ConfigSpec("config-name", Collections.emptyMap(), encoded, null));
41+
}
42+
43+
@AfterEach
44+
public void cleanup() {
45+
configApi.configDelete(defaultConfig.getId());
46+
}
47+
48+
@Test
49+
public void configCreate() {
50+
String encoded = Base64.getEncoder().encodeToString("config-data".getBytes());
51+
IdResponse response = configApi.configCreate(new ConfigSpec("my-config", Collections.emptyMap(), encoded, null));
52+
assertTrue(response.getId().matches("\\w{5,}"));
53+
54+
configApi.configDelete(response.getId());
55+
}
56+
57+
@Test
58+
public void configDelete() {
59+
String encoded = Base64.getEncoder().encodeToString("config-data".getBytes());
60+
IdResponse response = configApi.configCreate(new ConfigSpec("my-config", Collections.emptyMap(), encoded, null));
61+
62+
assertDoesNotThrow(() -> configApi.configDelete(response.getId()));
63+
}
64+
65+
@Test
66+
public void configInspect() {
67+
Config inspect = configApi.configInspect(defaultConfig.getId());
68+
assertEquals("config-name", inspect.getSpec().getName());
69+
assertEquals("config-data", new String(Base64.getDecoder().decode(inspect.getSpec().getData())));
70+
}
71+
72+
@Test
73+
public void configList() {
74+
List<Config> configs = configApi.configList(null);
75+
Stream<Config> filtered = configs.stream().filter(c -> Objects.equals(c.getID(), defaultConfig.getId()));
76+
assertEquals(defaultConfig.getId(), filtered.findFirst().orElse(new Config()).getID());
77+
}
78+
79+
@Test
80+
public void configUpdate() {
81+
Config inspect = configApi.configInspect(defaultConfig.getId());
82+
ConfigSpec configSpec = inspect.getSpec();
83+
assertNotNull(configSpec);
84+
assertDoesNotThrow(() -> configApi.configUpdate(defaultConfig.getId(), inspect.getVersion().getIndex(), new ConfigSpec(configSpec.getName(), Collections.singletonMap("foo", "bar"), configSpec.getData(), configSpec.getTemplating())));
85+
}
86+
}

0 commit comments

Comments
 (0)