diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 4251a2db853..fed1e5d8766 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -64,6 +64,7 @@ body: - Timeplus - ToxiProxy - Trino + - Typesense - Vault - Weaviate - YugabyteDB diff --git a/.github/ISSUE_TEMPLATE/enhancement.yaml b/.github/ISSUE_TEMPLATE/enhancement.yaml index 24d6b33ffdd..c89fc29208c 100644 --- a/.github/ISSUE_TEMPLATE/enhancement.yaml +++ b/.github/ISSUE_TEMPLATE/enhancement.yaml @@ -64,6 +64,7 @@ body: - Timeplus - ToxiProxy - Trino + - Typesense - Vault - Weaviate - YugabyteDB diff --git a/.github/ISSUE_TEMPLATE/feature.yaml b/.github/ISSUE_TEMPLATE/feature.yaml index d36532b7d9d..aa9bf4e7777 100644 --- a/.github/ISSUE_TEMPLATE/feature.yaml +++ b/.github/ISSUE_TEMPLATE/feature.yaml @@ -64,6 +64,7 @@ body: - Timeplus - ToxiProxy - Trino + - Typesense - Vault - Weaviate - YugabyteDB diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 22977397e03..17dd0e2aa05 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -361,6 +361,11 @@ updates: schedule: interval: "weekly" open-pull-requests-limit: 10 + - package-ecosystem: "gradle" + directory: "/modules/typesense" + schedule: + interval: "weekly" + open-pull-requests-limit: 10 - package-ecosystem: "gradle" directory: "/modules/vault" schedule: diff --git a/.github/labeler.yml b/.github/labeler.yml index e88811fd50a..537f40a944b 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -232,6 +232,10 @@ - changed-files: - any-glob-to-any-file: - modules/trino/**/* +"modules/typesense": + - changed-files: + - any-glob-to-any-file: + - modules/typesense/**/* "modules/vault": - changed-files: - any-glob-to-any-file: diff --git a/.github/settings.yml b/.github/settings.yml index 7571d82d55f..b10394a894a 100644 --- a/.github/settings.yml +++ b/.github/settings.yml @@ -262,6 +262,9 @@ labels: - name: modules/trino color: '#006b75' + - name: modules/typesense + color: '#006b75' + - name: modules/vault color: '#006b75' diff --git a/docs/modules/typesense.md b/docs/modules/typesense.md new file mode 100644 index 00000000000..b9828e40f33 --- /dev/null +++ b/docs/modules/typesense.md @@ -0,0 +1,30 @@ +# Typesense + +Testcontainers module for [Typesense](https://hub.docker.com/r/typesense/typesense). + +## TypesenseContainer's usage examples + +You can start an Typesense container instance from any Java application by using: + + +[Typesense container](../../modules/typesense/src/test/java/org/testcontainers/typesense/TypesenseContainerTest.java) inside_block:container + + +## Adding this module to your project dependencies + +Add the following dependency to your `pom.xml`/`build.gradle` file: + +=== "Gradle" + ```groovy + testImplementation "org.testcontainers:typesense:{{latest_version}}" + ``` + +=== "Maven" + ```xml + + org.testcontainers + typesense + {{latest_version}} + test + + ``` diff --git a/mkdocs.yml b/mkdocs.yml index 4689f6ee9fa..5c051c5565c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -102,6 +102,7 @@ nav: - modules/solace.md - modules/solr.md - modules/toxiproxy.md + - modules/typesense.md - modules/vault.md - modules/weaviate.md - modules/webdriver_containers.md diff --git a/modules/typesense/build.gradle b/modules/typesense/build.gradle new file mode 100644 index 00000000000..1a639ba2677 --- /dev/null +++ b/modules/typesense/build.gradle @@ -0,0 +1,8 @@ +description = "Testcontainers :: Typesense" + +dependencies { + api project(':testcontainers') + + testImplementation 'org.assertj:assertj-core:3.26.3' + testImplementation 'org.typesense:typesense-java:0.9.0' +} diff --git a/modules/typesense/src/main/java/org/testcontainers/typesense/TypesenseContainer.java b/modules/typesense/src/main/java/org/testcontainers/typesense/TypesenseContainer.java new file mode 100644 index 00000000000..f5ba92aee02 --- /dev/null +++ b/modules/typesense/src/main/java/org/testcontainers/typesense/TypesenseContainer.java @@ -0,0 +1,58 @@ +package org.testcontainers.typesense; + +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.utility.DockerImageName; + +/** + * Testcontainers implementation for Typesense. + *

+ * Supported image: {@code typesense/typesense} + *

+ * Exposed ports: 8108 + */ +public class TypesenseContainer extends GenericContainer { + + private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("typesense/typesense"); + + private static final int PORT = 8108; + + private static final String DEFAULT_API_KEY = "testcontainers"; + + private String apiKey = DEFAULT_API_KEY; + + public TypesenseContainer(String dockerImageName) { + this(DockerImageName.parse(dockerImageName)); + } + + public TypesenseContainer(DockerImageName dockerImageName) { + super(dockerImageName); + dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); + withExposedPorts(PORT); + withEnv("TYPESENSE_DATA_DIR", "/tmp"); + waitingFor( + Wait + .forHttp("/health") + .forStatusCode(200) + .forResponsePredicate(response -> response.contains("\"ok\":true")) + ); + } + + @Override + protected void configure() { + withEnv("TYPESENSE_API_KEY", this.apiKey); + } + + public TypesenseContainer withApiKey(String apiKey) { + this.apiKey = apiKey; + return this; + } + + public String getHttpPort() { + return String.valueOf(getMappedPort(PORT)); + } + + public String getApiKey() { + return this.apiKey; + } +} diff --git a/modules/typesense/src/test/java/org/testcontainers/typesense/TypesenseContainerTest.java b/modules/typesense/src/test/java/org/testcontainers/typesense/TypesenseContainerTest.java new file mode 100644 index 00000000000..8f39a05dd33 --- /dev/null +++ b/modules/typesense/src/test/java/org/testcontainers/typesense/TypesenseContainerTest.java @@ -0,0 +1,49 @@ +package org.testcontainers.typesense; + +import org.junit.Test; +import org.typesense.api.Client; +import org.typesense.api.Configuration; +import org.typesense.resources.Node; + +import java.time.Duration; +import java.util.Collections; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TypesenseContainerTest { + + @Test + public void query() throws Exception { + try ( // container { + TypesenseContainer typesense = new TypesenseContainer("typesense/typesense:27.1") + // } + ) { + typesense.start(); + List nodes = Collections.singletonList( + new Node("http", typesense.getHost(), typesense.getHttpPort()) + ); + + assertThat(typesense.getApiKey()).isEqualTo("testcontainers"); + Configuration configuration = new Configuration(nodes, Duration.ofSeconds(5), typesense.getApiKey()); + Client client = new Client(configuration); + System.out.println(client.health.retrieve()); + assertThat(client.health.retrieve()).containsEntry("ok", true); + } + } + + @Test + public void withCustomApiKey() throws Exception { + try (TypesenseContainer typesense = new TypesenseContainer("typesense/typesense:27.1").withApiKey("s3cr3t")) { + typesense.start(); + List nodes = Collections.singletonList( + new Node("http", typesense.getHost(), typesense.getHttpPort()) + ); + + assertThat(typesense.getApiKey()).isEqualTo("s3cr3t"); + Configuration configuration = new Configuration(nodes, Duration.ofSeconds(5), typesense.getApiKey()); + Client client = new Client(configuration); + assertThat(client.health.retrieve()).containsEntry("ok", true); + } + } +} diff --git a/modules/typesense/src/test/resources/logback-test.xml b/modules/typesense/src/test/resources/logback-test.xml new file mode 100644 index 00000000000..83ef7a1a3ef --- /dev/null +++ b/modules/typesense/src/test/resources/logback-test.xml @@ -0,0 +1,16 @@ + + + + + + %d{HH:mm:ss.SSS} %-5level %logger - %msg%n + + + + + + + + +