Skip to content

Commit c6930aa

Browse files
authored
Add Qdrant module (#725)
1 parent 260e384 commit c6930aa

File tree

11 files changed

+337
-0
lines changed

11 files changed

+337
-0
lines changed

docs/modules/qdrant.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Qdrant Module
2+
3+
[Qdrant](https://qdrant.tech/) is an open-source, high-performance vector search engine/database. It provides a production-ready service with a convenient API to store, search, and manage points (i.e. vectors) with an additional payload.
4+
5+
## Install
6+
7+
```bash
8+
npm install @testcontainers/qdrant --save-dev
9+
```
10+
11+
## Examples
12+
13+
<!--codeinclude-->
14+
[Connect to Qdrant:](../../packages/modules/qdrant/src/qdrant-container.test.ts)
15+
inside_block:connectQdrantSimple
16+
<!--/codeinclude-->
17+
18+
<!--codeinclude-->
19+
[Connect to Qdrant with an API key:](../../packages/modules/qdrant/src/qdrant-container.test.ts) inside_block:connectQdrantWithApiKey
20+
<!--/codeinclude-->
21+
22+
<!--codeinclude-->
23+
[Customize Qdrant instance with a config file:](../../packages/modules/qdrant/src/qdrant-container.test.ts) inside_block:connectQdrantWithConfig
24+
<!--/codeinclude-->

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ nav:
5454
- Nats: modules/nats.md
5555
- Neo4J: modules/neo4j.md
5656
- PostgreSQL: modules/postgresql.md
57+
- Qdrant: modules/qdrant.md
5758
- Redis: modules/redis.md
5859
- Selenium: modules/selenium.md
5960
- Configuration: configuration.md

package-lock.json

Lines changed: 70 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import type { Config } from "jest";
2+
import * as path from "path";
3+
4+
const config: Config = {
5+
preset: "ts-jest",
6+
moduleNameMapper: {
7+
"^testcontainers$": path.resolve(__dirname, "../../testcontainers/src"),
8+
},
9+
};
10+
11+
export default config;
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"name": "@testcontainers/qdrant",
3+
"version": "10.7.2",
4+
"license": "MIT",
5+
"keywords": [
6+
"qdrant",
7+
"testing",
8+
"docker",
9+
"testcontainers"
10+
],
11+
"description": "Qdrant module for Testcontainers",
12+
"homepage": "https://github.com/testcontainers/testcontainers-node#readme",
13+
"repository": {
14+
"type": "git",
15+
"url": "https://github.com/testcontainers/testcontainers-node"
16+
},
17+
"bugs": {
18+
"url": "https://github.com/testcontainers/testcontainers-node/issues"
19+
},
20+
"main": "build/index.js",
21+
"files": [
22+
"build"
23+
],
24+
"publishConfig": {
25+
"access": "public"
26+
},
27+
"scripts": {
28+
"prepack": "shx cp ../../../README.md . && shx cp ../../../LICENSE .",
29+
"build": "tsc --project tsconfig.build.json"
30+
},
31+
32+
"devDependencies": {
33+
"@qdrant/js-client-rest": "^1.8.0"
34+
},
35+
"dependencies": {
36+
"testcontainers": "^10.7.2"
37+
}
38+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { QdrantContainer, StartedQdrantContainer } from "./qdrant-container";
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { QdrantContainer } from "./qdrant-container";
2+
import { QdrantClient } from "@qdrant/js-client-rest";
3+
import crypto from "crypto";
4+
import path from "path";
5+
6+
describe("QdrantContainer", () => {
7+
jest.setTimeout(100_000);
8+
9+
// connectQdrantSimple {
10+
it("should connect to the client", async () => {
11+
const container = await new QdrantContainer().start();
12+
13+
const client = new QdrantClient({ url: `http://${container.getRestHostAddress()}` });
14+
15+
expect((await client.getCollections()).collections.length).toBe(0);
16+
17+
await container.stop();
18+
});
19+
// }
20+
21+
// connectQdrantWithApiKey {
22+
it("should work with valid API keys", async () => {
23+
const apiKey = crypto.randomUUID();
24+
25+
const container = await new QdrantContainer().withApiKey(apiKey).start();
26+
27+
const client = new QdrantClient({ url: `http://${container.getRestHostAddress()}`, apiKey });
28+
29+
expect((await client.getCollections()).collections.length).toBe(0);
30+
31+
await container.stop();
32+
});
33+
// }
34+
35+
it("should fail for invalid API keys", async () => {
36+
const apiKey = crypto.randomUUID();
37+
38+
const container = await new QdrantContainer().withApiKey(apiKey).start();
39+
40+
const client = new QdrantClient({
41+
url: `http://${container.getRestHostAddress()}`,
42+
apiKey: "INVALID_KEY_" + crypto.randomUUID(),
43+
});
44+
45+
expect(client.getCollections()).rejects.toThrow("Forbidden");
46+
47+
await container.stop();
48+
});
49+
50+
// connectQdrantWithConfig {
51+
it("should work with config files - valid API key", async () => {
52+
const container = await new QdrantContainer().withConfigFile(path.resolve(__dirname, "test_config.yaml")).start();
53+
54+
const client = new QdrantClient({ url: `http://${container.getRestHostAddress()}`, apiKey: "SOME_TEST_KEY" });
55+
56+
expect((await client.getCollections()).collections.length).toBe(0);
57+
58+
await container.stop();
59+
});
60+
// }
61+
62+
it("should work with config files - invalid API key", async () => {
63+
const container = await new QdrantContainer().withConfigFile(path.resolve(__dirname, "test_config.yaml")).start();
64+
65+
const client = new QdrantClient({
66+
url: `http://${container.getRestHostAddress()}`,
67+
apiKey: "INVALID_KEY_" + crypto.randomUUID(),
68+
});
69+
70+
expect(client.getCollections()).rejects.toThrow("Forbidden");
71+
72+
await container.stop();
73+
});
74+
});
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { AbstractStartedContainer, GenericContainer, StartedTestContainer, Wait } from "testcontainers";
2+
3+
const QDRANT_REST_PORT = 6333;
4+
const QDRANT_GRPC_PORT = 6334;
5+
const QDRANT_CONFIG_FILE_PATH = "/qdrant/config/config.yaml";
6+
7+
export class QdrantContainer extends GenericContainer {
8+
private apiKey: string | undefined;
9+
private configFilePath: string | undefined;
10+
11+
constructor(image = "qdrant/qdrant:v1.8.1") {
12+
super(image);
13+
this.withExposedPorts(QDRANT_REST_PORT, QDRANT_GRPC_PORT);
14+
this.withWaitStrategy(
15+
Wait.forAll([
16+
Wait.forLogMessage(/Actix runtime found; starting in Actix runtime/),
17+
Wait.forHttp("/readyz", QDRANT_REST_PORT),
18+
])
19+
);
20+
}
21+
22+
public withApiKey(apiKey: string): this {
23+
this.apiKey = apiKey;
24+
return this;
25+
}
26+
27+
public withConfigFile(configFile: string): this {
28+
this.configFilePath = configFile;
29+
return this;
30+
}
31+
32+
public override async start(): Promise<StartedQdrantContainer> {
33+
if (this.apiKey) {
34+
this.withEnvironment({
35+
QDRANT__SERVICE__API_KEY: this.apiKey,
36+
});
37+
}
38+
39+
if (this.configFilePath) {
40+
this.withBindMounts([
41+
{
42+
target: QDRANT_CONFIG_FILE_PATH,
43+
source: this.configFilePath,
44+
},
45+
]);
46+
}
47+
return new StartedQdrantContainer(await super.start());
48+
}
49+
}
50+
51+
export class StartedQdrantContainer extends AbstractStartedContainer {
52+
private readonly _restPort: number;
53+
54+
public get restPort(): number {
55+
return this._restPort;
56+
}
57+
58+
private readonly _grpcPort: number;
59+
60+
public get grpcPort(): number {
61+
return this._grpcPort;
62+
}
63+
64+
constructor(override readonly startedTestContainer: StartedTestContainer) {
65+
super(startedTestContainer);
66+
this._restPort = this.getMappedPort(QDRANT_REST_PORT);
67+
this._grpcPort = this.getMappedPort(QDRANT_GRPC_PORT);
68+
}
69+
70+
public getRestHostAddress(): string {
71+
return `${this.getHost()}:${this.restPort}`;
72+
}
73+
74+
public getGrpcHostAddress(): string {
75+
return `${this.getHost()}:${this.grpcPort}`;
76+
}
77+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Qdrant image configuration file for testing
2+
# Reference: https://qdrant.tech/documentation/guides/configuration/#configuration-file-example
3+
log_level: INFO
4+
5+
service:
6+
api_key: "SOME_TEST_KEY"
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"extends": "./tsconfig.json",
3+
"exclude": [
4+
"build",
5+
"jest.config.ts",
6+
"src/**/*.test.ts",
7+
"src/test_config.yaml"
8+
],
9+
"references": [
10+
{
11+
"path": "../../testcontainers"
12+
}
13+
]
14+
}

0 commit comments

Comments
 (0)