Skip to content

Commit 8aa6577

Browse files
committed
Add support for CosmosDB emulator
Fixes #140
1 parent 95bb354 commit 8aa6577

File tree

10 files changed

+247
-1
lines changed

10 files changed

+247
-1
lines changed

gradle/libs.versions.toml

+1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ managed-testcontainers-redis = "1.6.4"
5454
boms-testcontainers = { module = "org.testcontainers:testcontainers-bom", version.ref = "managed-testcontainers" }
5555

5656
managed-testcontainers-core = { module = "org.testcontainers:testcontainers", version.ref = "managed-testcontainers" }
57+
managed-testcontainers-azure = { module = "org.testcontainers:azure", version.ref = "managed-testcontainers" }
5758
managed-testcontainers-elasticsearch = { module = "org.testcontainers:elasticsearch", version.ref = "managed-testcontainers" }
5859
managed-testcontainers-jdbc = { module = "org.testcontainers:jdbc", version.ref = "managed-testcontainers" }
5960
managed-testcontainers-hivemq = { module = "org.testcontainers:hivemq", version.ref = "managed-testcontainers" }

settings.gradle

+10-1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ def localstackModules = [
5757
'sqs'
5858
]
5959

60+
def azureModules = [
61+
'cosmos'
62+
]
63+
6064
include 'test-resources-bom'
6165
include 'test-resources-build-tools'
6266
include 'test-resources-core'
@@ -94,13 +98,18 @@ localstackModules.each {
9498
project(":test-resources-localstack-$it").projectDir = file("test-resources-localstack/$projectName")
9599
}
96100

97-
98101
hibernateReactiveModules.each {
99102
String projectName = "test-resources-hibernate-reactive-$it"
100103
include projectName
101104
project(":test-resources-hibernate-reactive-$it").projectDir = file("test-resources-hibernate-reactive/$projectName")
102105
}
103106

107+
azureModules.each {
108+
String projectName = "test-resources-azure-$it"
109+
include projectName
110+
project(":test-resources-azure-$it").projectDir = file("test-resources-azure/$projectName")
111+
}
112+
104113
micronautBuild {
105114
useStandardizedProjectNames = true
106115
importMicronautCatalog()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
plugins {
2+
id 'io.micronaut.build.internal.test-resources-module'
3+
}
4+
5+
description = """
6+
Provides support for launching an Azure Cosmos test container.
7+
"""
8+
9+
dependencies {
10+
api(project(':micronaut-test-resources-core'))
11+
api(project(':micronaut-test-resources-testcontainers'))
12+
api(libs.managed.testcontainers.azure)
13+
14+
testAnnotationProcessor(mn.micronaut.inject.java)
15+
testAnnotationProcessor(mnData.micronaut.data.document.processor)
16+
testImplementation(project(":micronaut-test-resources-embedded"))
17+
testImplementation(testFixtures(project(":micronaut-test-resources-testcontainers")))
18+
testImplementation(mnData.micronaut.data.azure.cosmos)
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
* Copyright 2003-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.micronaut.testresources.azure.cosmos;
17+
18+
import io.micronaut.testresources.testcontainers.AbstractTestContainersProvider;
19+
import org.slf4j.Logger;
20+
import org.slf4j.LoggerFactory;
21+
import org.testcontainers.containers.CosmosDBEmulatorContainer;
22+
import org.testcontainers.utility.DockerImageName;
23+
24+
import java.io.IOException;
25+
import java.nio.file.Files;
26+
import java.security.KeyStoreException;
27+
import java.security.NoSuchAlgorithmException;
28+
import java.security.cert.CertificateException;
29+
import java.time.Duration;
30+
import java.util.Collection;
31+
import java.util.List;
32+
import java.util.Map;
33+
import java.util.Optional;
34+
import java.util.Set;
35+
36+
public class CosmosTestResourcesProvider extends AbstractTestContainersProvider<CosmosDBEmulatorContainer> {
37+
private static final Logger LOGGER = LoggerFactory.getLogger(CosmosTestResourcesProvider.class);
38+
private static final Duration STARTUP_TIMEOUT = Duration.ofMinutes(5);
39+
40+
public static final String SIMPLE_NAME = "cosmosdb";
41+
public static final String DEFAULT_IMAGE = "mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:latest";
42+
43+
public static final String ENDPOINT = "azure.cosmos.endpoint";
44+
public static final String KEY = "azure.cosmos.key";
45+
public static final List<String> RESOLVABLE_PROPERTIES_LIST = List.of(ENDPOINT, KEY);
46+
public static final Set<String> RESOLVABLE_PROPERTIES_SET = Set.of(ENDPOINT, KEY);
47+
48+
@Override
49+
protected String getSimpleName() {
50+
return SIMPLE_NAME;
51+
}
52+
53+
@Override
54+
protected String getDefaultImageName() {
55+
return DEFAULT_IMAGE;
56+
}
57+
58+
@Override
59+
public List<String> getResolvableProperties(Map<String, Collection<String>> propertyEntries, Map<String, Object> testResourcesConfig) {
60+
return RESOLVABLE_PROPERTIES_LIST;
61+
}
62+
63+
@Override
64+
protected boolean shouldAnswer(String propertyName, Map<String, Object> requestedProperties, Map<String, Object> testResourcesConfig) {
65+
return RESOLVABLE_PROPERTIES_SET.contains(propertyName);
66+
}
67+
68+
@Override
69+
protected CosmosDBEmulatorContainer createContainer(DockerImageName imageName, Map<String, Object> requestedProperties, Map<String, Object> testResourcesConfig) {
70+
return new CosmosDBEmulatorWithKeystoreContainer(imageName).withStartupTimeout(STARTUP_TIMEOUT);
71+
}
72+
73+
@Override
74+
protected Optional<String> resolveProperty(String propertyName, CosmosDBEmulatorContainer container) {
75+
if (RESOLVABLE_PROPERTIES_SET.contains(propertyName)) {
76+
return Optional.ofNullable(switch (propertyName) {
77+
case ENDPOINT -> container.getEmulatorEndpoint();
78+
case KEY -> container.getEmulatorKey();
79+
default -> null;
80+
});
81+
}
82+
return Optional.empty();
83+
}
84+
85+
private static class CosmosDBEmulatorWithKeystoreContainer extends CosmosDBEmulatorContainer {
86+
public CosmosDBEmulatorWithKeystoreContainer(DockerImageName imageName) {
87+
super(imageName);
88+
}
89+
90+
@Override
91+
public void start() {
92+
super.start();
93+
configureKeyStore(this);
94+
}
95+
96+
private void configureKeyStore(CosmosDBEmulatorContainer emulator) {
97+
try {
98+
var keyStoreFile = Files.createTempFile("azure-cosmos-emulator", ".keystore");
99+
var keyStore = emulator.buildNewKeyStore();
100+
try (var outputStream = Files.newOutputStream(keyStoreFile)) {
101+
keyStore.store(outputStream, emulator.getEmulatorKey().toCharArray());
102+
}
103+
} catch (IOException | KeyStoreException | NoSuchAlgorithmException |
104+
CertificateException ex) {
105+
LOGGER.error("Cannot create keystore for CosmosDB", ex);
106+
}
107+
}
108+
109+
}
110+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
io.micronaut.testresources.azure.cosmos.CosmosTestResourcesProvider
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package io.micronaut.testresources.azure.cosmos
2+
3+
import io.micronaut.test.extensions.spock.annotation.MicronautTest
4+
import io.micronaut.testresources.testcontainers.AbstractTestContainersSpec
5+
import jakarta.inject.Inject
6+
7+
@MicronautTest
8+
class CosmosStartedTest extends AbstractTestContainersSpec {
9+
@Inject
10+
CosmosBookRepository repository
11+
12+
def "starts a CosmosDB container"() {
13+
def book = new CosmosBook(null, "Micronaut for Spring developers", 50, "1")
14+
repository.save(book)
15+
16+
when:
17+
def books = repository.findAll()
18+
19+
then:
20+
books.size() == 1
21+
}
22+
23+
@Override
24+
String getImageName() {
25+
"cosmosdb"
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2003-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.micronaut.testresources.azure.cosmos;
17+
18+
import io.micronaut.data.annotation.GeneratedValue;
19+
import io.micronaut.data.annotation.Id;
20+
import io.micronaut.data.annotation.MappedEntity;
21+
import io.micronaut.data.cosmos.annotation.ETag;
22+
import io.micronaut.data.cosmos.annotation.PartitionKey;
23+
24+
@MappedEntity("cosmosbook")
25+
public record CosmosBook(
26+
@Id
27+
@GeneratedValue
28+
@PartitionKey
29+
String id,
30+
String title,
31+
int totalPages,
32+
@ETag
33+
String version) {
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright 2003-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.micronaut.testresources.azure.cosmos;
17+
18+
import io.micronaut.data.cosmos.annotation.CosmosRepository;
19+
import io.micronaut.data.repository.CrudRepository;
20+
21+
@CosmosRepository
22+
public abstract class CosmosBookRepository implements CrudRepository<CosmosBook, String> {
23+
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
azure:
2+
cosmos:
3+
database:
4+
packages: io.micronaut.testresources.azure.cosmos
5+
default-gateway-mode: true
6+
endpoint-discovery-enabled: false
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<configuration>
2+
3+
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
4+
<!-- encoders are assigned the type
5+
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
6+
<encoder>
7+
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
8+
</encoder>
9+
</appender>
10+
11+
<root level="info">
12+
<appender-ref ref="STDOUT" />
13+
</root>
14+
15+
</configuration>

0 commit comments

Comments
 (0)