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
+
+
+
+
+
+
+
+
+