diff --git a/docs/modules/elasticsearch.md b/docs/modules/elasticsearch.md index 6347bda6c..d9c65bdbd 100644 --- a/docs/modules/elasticsearch.md +++ b/docs/modules/elasticsearch.md @@ -8,11 +8,15 @@ npm install @testcontainers/elasticsearch --save-dev ## Examples -These examples use the following libraries: +These examples use the following libraries. Use the client major that matches your Elasticsearch major: -- [@elastic/elasticsearch](https://www.npmjs.com/package/@elastic/elasticsearch) +- [@elastic/elasticsearch 9](https://www.npmjs.com/package/@elastic/elasticsearch) for Elasticsearch 9 - npm install @elastic/elasticsearch + npm install @elastic/elasticsearch@9 + +- [@elastic/elasticsearch 8](https://www.npmjs.com/package/@elastic/elasticsearch/v/8.19.2) for Elasticsearch 8 + + npm install @elastic/elasticsearch@8 Choose an image from the [container registry](https://hub.docker.com/_/elasticsearch) and substitute `IMAGE`. diff --git a/package-lock.json b/package-lock.json index 7c1d8e767..1a3b130a0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1738,6 +1738,69 @@ } } }, + "node_modules/@elastic/elasticsearch-8": { + "name": "@elastic/elasticsearch", + "version": "8.19.2", + "resolved": "https://registry.npmjs.org/@elastic/elasticsearch/-/elasticsearch-8.19.2.tgz", + "integrity": "sha512-LMJCju/+AZkDlJArd/MYABWTDrHi4U7j3qGTKi1hYC7+67SaiYmYFItiueACQVrj2j3ECPMcguZ+7WrZeB+Z5g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@elastic/transport": "^8.9.6", + "apache-arrow": "18.x - 21.x", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@elastic/elasticsearch-8/node_modules/@elastic/transport": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@elastic/transport/-/transport-8.11.0.tgz", + "integrity": "sha512-1QD1j7gQF8+tVrf+UOcOrLF1LXnhBYwBnNl4xd+fYxkQONjaOjiTcfqXcALHesKezUv8wRzv3tedEnhtYLBmhw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "1.x", + "@opentelemetry/core": "2.x", + "debug": "^4.4.1", + "hpagent": "^1.2.0", + "ms": "^2.1.3", + "secure-json-parse": "^3.0.1", + "tslib": "^2.8.1", + "undici": "^6.21.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@elastic/elasticsearch-8/node_modules/secure-json-parse": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-3.0.2.tgz", + "integrity": "sha512-H6nS2o8bWfpFEV6U38sOSjS7bTbdgbCGU9wEM6W14P5H0QOsz94KCusifV44GpHDTu2nqZbuDNhTzu+mjDSw1w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/@elastic/elasticsearch-8/node_modules/undici": { + "version": "6.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.29.0.tgz", + "integrity": "sha512-R+RODBqp6i2pPflGdq+xIOUkl+RNfGgHwoinecKu/JCuf2uO06cOKoDbI2P7Dn6KcswdKwrczbU6IYJ6K8X+wg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, "node_modules/@elastic/transport": { "version": "9.4.0", "resolved": "https://registry.npmjs.org/@elastic/transport/-/transport-9.4.0.tgz", @@ -15319,7 +15382,8 @@ "testcontainers": "^12.2.1" }, "devDependencies": { - "@elastic/elasticsearch": "^9.5.1" + "@elastic/elasticsearch": "^9.5.1", + "@elastic/elasticsearch-8": "npm:@elastic/elasticsearch@^8.19.2" } }, "packages/modules/etcd": { diff --git a/packages/modules/elasticsearch/package.json b/packages/modules/elasticsearch/package.json index 7aff51b33..931673159 100644 --- a/packages/modules/elasticsearch/package.json +++ b/packages/modules/elasticsearch/package.json @@ -29,7 +29,8 @@ "build": "tsc --project tsconfig.build.json" }, "devDependencies": { - "@elastic/elasticsearch": "^9.5.1" + "@elastic/elasticsearch": "^9.5.1", + "@elastic/elasticsearch-8": "npm:@elastic/elasticsearch@^8.19.2" }, "dependencies": { "testcontainers": "^12.2.1" diff --git a/packages/modules/elasticsearch/src/elasticsearch-container-v8.test.ts b/packages/modules/elasticsearch/src/elasticsearch-container-v8.test.ts new file mode 100644 index 000000000..a0f79dfa3 --- /dev/null +++ b/packages/modules/elasticsearch/src/elasticsearch-container-v8.test.ts @@ -0,0 +1,67 @@ +import { Client } from "@elastic/elasticsearch-8"; +import { ElasticsearchContainer } from "./elasticsearch-container"; + +const IMAGE = "elasticsearch:8.19.23"; + +describe("ElasticsearchContainer 8", { concurrent: false, timeout: 180_000 }, () => { + it("should create an index", async () => { + await using container = await new ElasticsearchContainer(IMAGE).start(); + + const client = new Client({ + node: container.getHttpUrl(), + auth: { username: container.getUsername(), password: container.getPassword() }, + }); + + await client.indices.create({ index: "people" }); + + expect(await client.indices.exists({ index: "people" })).toBe(true); + }); + + it("should index a document", async () => { + await using container = await new ElasticsearchContainer(IMAGE).start(); + + const client = new Client({ + node: container.getHttpUrl(), + auth: { username: container.getUsername(), password: container.getPassword() }, + }); + + const document = { + id: "1", + name: "John Doe", + }; + await client.index({ + index: "people", + id: document.id, + document, + }); + + expect((await client.get({ index: "people", id: document.id }))._source).toStrictEqual(document); + }); + + it("should set custom password", async () => { + await using container = await new ElasticsearchContainer(IMAGE).withPassword("testPassword").start(); + + const client = new Client({ + node: container.getHttpUrl(), + auth: { username: container.getUsername(), password: container.getPassword() }, + }); + + await client.indices.create({ index: "people" }); + + expect(await client.indices.exists({ index: "people" })).toBe(true); + }); + + it("should work with restarted container", async () => { + await using container = await new ElasticsearchContainer(IMAGE).start(); + await container.restart(); + + const client = new Client({ + node: container.getHttpUrl(), + auth: { username: container.getUsername(), password: container.getPassword() }, + }); + + await client.indices.create({ index: "people" }); + + expect(await client.indices.exists({ index: "people" })).toBe(true); + }); +}); diff --git a/packages/modules/elasticsearch/src/elasticsearch-container.test.ts b/packages/modules/elasticsearch/src/elasticsearch-container.test.ts index 886365a0e..e63bd10fe 100644 --- a/packages/modules/elasticsearch/src/elasticsearch-container.test.ts +++ b/packages/modules/elasticsearch/src/elasticsearch-container.test.ts @@ -3,12 +3,11 @@ import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { ElasticsearchContainer } from "./elasticsearch-container"; const IMAGE = getImage(__dirname); -const images = ["elasticsearch:7.17.28", "elasticsearch:8.18.1", IMAGE]; -describe("ElasticsearchContainer", { timeout: 180_000 }, () => { - it.each(images)("should create an index with %s", async (image) => { +describe("ElasticsearchContainer", { concurrent: false, timeout: 180_000 }, () => { + it("should create an index", async () => { // createIndex { - await using container = await new ElasticsearchContainer(image).start(); + await using container = await new ElasticsearchContainer(IMAGE).start(); const client = new Client({ node: container.getHttpUrl(), diff --git a/packages/modules/mongodb/src/mongodb-atlas-local-container.test.ts b/packages/modules/mongodb/src/mongodb-atlas-local-container.test.ts index b69a73de2..3dd70b52f 100644 --- a/packages/modules/mongodb/src/mongodb-atlas-local-container.test.ts +++ b/packages/modules/mongodb/src/mongodb-atlas-local-container.test.ts @@ -3,7 +3,7 @@ import { IntervalRetry } from "testcontainers"; import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { MongoDBAtlasLocalContainer } from "./mongodb-atlas-local-container"; -const IMAGE = getImage(__dirname, 1); +const IMAGES = ["mongodb/mongodb-atlas-local:7.0.43", getImage(__dirname, 1)]; const ATLAS_SEARCH_INDEX = { mappings: { dynamic: false, @@ -23,18 +23,18 @@ const ATLAS_SEARCH_INDEX = { }, }; -describe("MongoDBAtlasLocalContainer", { timeout: 240_000 }, () => { - it("should provide a connection string", async () => { +describe.each(IMAGES)("MongoDBAtlasLocalContainer (%s)", { concurrent: false, timeout: 240_000 }, (image) => { + it.concurrent("should provide a connection string", async () => { // connectAtlasLocal { - await using container = await new MongoDBAtlasLocalContainer(IMAGE).start(); + await using container = await new MongoDBAtlasLocalContainer(image).start(); // } expect(container.getConnectionString()).toBe(`mongodb://${container.getHost()}:${container.getMappedPort(27017)}/`); }); - it("should provide a database connection string", async () => { + it.concurrent("should provide a database connection string", async () => { // connectAtlasLocalDatabase { - await using container = await new MongoDBAtlasLocalContainer(IMAGE).start(); + await using container = await new MongoDBAtlasLocalContainer(image).start(); // } expect(container.getDatabaseConnectionString()).toBe( @@ -42,9 +42,9 @@ describe("MongoDBAtlasLocalContainer", { timeout: 240_000 }, () => { ); }); - it("should provide connection strings with credentials", async () => { + it.concurrent("should provide connection strings with credentials", async () => { // connectAtlasLocalWithCredentials { - await using container = await new MongoDBAtlasLocalContainer(IMAGE) + await using container = await new MongoDBAtlasLocalContainer(image) .withUsername("customUsername") .withPassword("customPassword") .start(); @@ -58,8 +58,8 @@ describe("MongoDBAtlasLocalContainer", { timeout: 240_000 }, () => { ); }); - it("should connect to mongodb atlas local", async () => { - await using container = await new MongoDBAtlasLocalContainer(IMAGE).start(); + it.concurrent("should connect to mongodb atlas local", async () => { + await using container = await new MongoDBAtlasLocalContainer(image).start(); const db = await mongoose.createConnection(container.getConnectionString(), { directConnection: true }).asPromise(); @@ -73,8 +73,8 @@ describe("MongoDBAtlasLocalContainer", { timeout: 240_000 }, () => { await db.close(); }); - it("should connect to mongodb atlas local with credentials", async () => { - await using container = await new MongoDBAtlasLocalContainer(IMAGE) + it.concurrent("should connect to mongodb atlas local with credentials", async () => { + await using container = await new MongoDBAtlasLocalContainer(image) .withUsername("customUsername") .withPassword("customPassword") .start(); @@ -93,9 +93,9 @@ describe("MongoDBAtlasLocalContainer", { timeout: 240_000 }, () => { await db.close(); }); - it("should create an atlas search index and search it", async () => { + it.concurrent("should create an atlas search index and search it", async () => { // createAtlasIndexAndSearchIt { - await using atlasLocalContainer = await new MongoDBAtlasLocalContainer(IMAGE).start(); + await using atlasLocalContainer = await new MongoDBAtlasLocalContainer(image).start(); const db = await mongoose .createConnection(atlasLocalContainer.getConnectionString(), { diff --git a/packages/modules/mongodb/src/mongodb-container.test.ts b/packages/modules/mongodb/src/mongodb-container.test.ts index d44864e9e..67cbe39d2 100644 --- a/packages/modules/mongodb/src/mongodb-container.test.ts +++ b/packages/modules/mongodb/src/mongodb-container.test.ts @@ -2,10 +2,10 @@ import mongoose from "mongoose"; import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { MongoDBContainer } from "./mongodb-container"; -const IMAGE = getImage(__dirname); +const IMAGES = ["mongo:7.0.43", "mongo:8.3.11", getImage(__dirname)]; -describe("MongoDBContainer", { timeout: 240_000 }, () => { - it.each([IMAGE, "mongo:6.0.25", "mongo:4.4.29"])("should work with %s", async (image) => { +describe.each(IMAGES)("MongoDBContainer (%s)", { concurrent: false, timeout: 240_000 }, (image) => { + it.concurrent("should connect", async () => { // connectMongo { await using container = await new MongoDBContainer(image).start(); @@ -22,9 +22,9 @@ describe("MongoDBContainer", { timeout: 240_000 }, () => { // } }); - it("should connect with credentials", async () => { + it.concurrent("should connect with credentials", async () => { // connectWithCredentials { - await using container = await new MongoDBContainer(IMAGE) + await using container = await new MongoDBContainer(image) .withUsername("customUsername") .withPassword("customPassword") .start(); diff --git a/packages/modules/opensearch/src/opensearch-container.test.ts b/packages/modules/opensearch/src/opensearch-container.test.ts index 2e7d57eaf..fe909e64f 100644 --- a/packages/modules/opensearch/src/opensearch-container.test.ts +++ b/packages/modules/opensearch/src/opensearch-container.test.ts @@ -2,11 +2,10 @@ import { Client } from "@opensearch-project/opensearch"; import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { OpenSearchContainer } from "./opensearch-container"; -const IMAGE = getImage(__dirname); -const images = ["opensearchproject/opensearch:2.19.2", IMAGE]; +const IMAGES = ["opensearchproject/opensearch:2.19.6", getImage(__dirname)]; -describe("OpenSearchContainer", { timeout: 180_000 }, () => { - it.each(images)("should create an index with %s", async (image) => { +describe.each(IMAGES)("OpenSearchContainer (%s)", { concurrent: false, timeout: 180_000 }, (image) => { + it.concurrent("should create an index", async () => { // opensearchCreateIndex { await using container = await new OpenSearchContainer(image).start(); @@ -28,9 +27,9 @@ describe("OpenSearchContainer", { timeout: 180_000 }, () => { // } }); - it("should index a document", async () => { + it.concurrent("should index a document", async () => { // opensearchIndexDocument { - await using container = await new OpenSearchContainer(IMAGE).start(); + await using container = await new OpenSearchContainer(image).start(); const client = new Client({ node: container.getHttpUrl(), @@ -56,8 +55,8 @@ describe("OpenSearchContainer", { timeout: 180_000 }, () => { // } }); - it("should work with restarted container", async () => { - await using container = await new OpenSearchContainer(IMAGE).start(); + it.concurrent("should work with restarted container", async () => { + await using container = await new OpenSearchContainer(image).start(); await container.restart(); const client = new Client({ @@ -77,13 +76,13 @@ describe("OpenSearchContainer", { timeout: 180_000 }, () => { expect(existsResponse.body).toBe(true); }); - it("should throw when given an invalid password", () => { - expect(() => new OpenSearchContainer(IMAGE).withPassword("weakpwd")).toThrowError(/Password "weakpwd" is too weak/); + it.concurrent("should throw when given an invalid password", () => { + expect(() => new OpenSearchContainer(image).withPassword("weakpwd")).toThrowError(/Password "weakpwd" is too weak/); }); - it("should set custom password", async () => { + it.concurrent("should set custom password", async () => { // opensearchCustomPassword { - await using container = await new OpenSearchContainer(IMAGE).withPassword("Str0ng!Passw0rd2025").start(); + await using container = await new OpenSearchContainer(image).withPassword("Str0ng!Passw0rd2025").start(); // } const client = new Client({ @@ -103,9 +102,9 @@ describe("OpenSearchContainer", { timeout: 180_000 }, () => { expect(body).toBe(true); }); - it("should be reachable with security disabled", async () => { + it.concurrent("should be reachable with security disabled", async () => { // opensearchDisableSecurity { - await using container = await new OpenSearchContainer(IMAGE).withSecurityEnabled(false).start(); + await using container = await new OpenSearchContainer(image).withSecurityEnabled(false).start(); const client = new Client({ node: container.getHttpUrl(),