Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Valkey module #906

Merged
merged 2 commits into from
Feb 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions docs/modules/valkey.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Valkey Module

[Valkey](https://valkey.io/) is a distributed, in-memory, key-value store.

## Install

```bash
npm install @testcontainers/valkey --save-dev
```

## Examples

<!--codeinclude-->

[Start container:](../../packages/modules/valkey/src/valkey-container.test.ts) inside_block:startContainer

<!--/codeinclude-->

<!--codeinclude-->

[Connect valkey client to container:](../../packages/modules/valkey/src/valkey-container.test.ts) inside_block:simpleConnect

<!--/codeinclude-->

<!--codeinclude-->

[Start container with password authentication:](../../packages/modules/valkey/src/valkey-container.test.ts) inside_block:startWithCredentials

<!--/codeinclude-->

<!--codeinclude-->

[Define volume for persistent/predefined data:](../../packages/modules/valkey/src/valkey-container.test.ts) inside_block:persistentData

<!--/codeinclude-->

<!--codeinclude-->

[Execute a command inside the container:](../../packages/modules/valkey/src/valkey-container.test.ts) inside_block:executeCommand

<!--/codeinclude-->
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,5 +71,6 @@ nav:
- ScyllaDB: modules/scylladb.md
- Selenium: modules/selenium.md
- ToxiProxy: modules/toxiproxy.md
- Valkey: modules/valkey.md
- Weaviate: modules/weaviate.md
- Configuration: configuration.md
131 changes: 131 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion packages/modules/redis/src/redis-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ export class RedisContainer extends GenericContainer {
return this;
}

protected override async containerStarted(container: StartedTestContainer): Promise<void> {
if (this.initialImportScriptFile) {
await this.importInitialData(container);
}
}

public override async start(): Promise<StartedRedisContainer> {
this.withCommand([
"redis-server",
Expand Down Expand Up @@ -60,7 +66,7 @@ export class RedisContainer extends GenericContainer {
return startedRedisContainer;
}

private async importInitialData(container: StartedRedisContainer) {
private async importInitialData(container: StartedTestContainer) {
const re = await container.exec(`/tmp/import.sh ${this.password}`);
if (re.exitCode != 0 || re.output.includes("ERR"))
throw Error(`Could not import initial data from ${this.initialImportScriptFile}: ${re.output}`);
Expand Down
11 changes: 11 additions & 0 deletions packages/modules/valkey/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { Config } from "jest";
import * as path from "path";

const config: Config = {
preset: "ts-jest",
moduleNameMapper: {
"^testcontainers$": path.resolve(__dirname, "../../testcontainers/src"),
},
};

export default config;
38 changes: 38 additions & 0 deletions packages/modules/valkey/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "@testcontainers/valkey",
"version": "10.17.1",
"license": "MIT",
"keywords": [
"valkey",
"testing",
"docker",
"testcontainers"
],
"description": "Valkey module for Testcontainers",
"homepage": "https://github.com/testcontainers/testcontainers-node#readme",
"repository": {
"type": "git",
"url": "https://github.com/testcontainers/testcontainers-node"
},
"bugs": {
"url": "https://github.com/testcontainers/testcontainers-node/issues"
},
"main": "build/index.js",
"files": [
"build"
],
"publishConfig": {
"access": "public"
},
"scripts": {
"prepack": "shx cp ../../../README.md . && shx cp ../../../LICENSE .",
"build": "tsc --project tsconfig.build.json"
},
"devDependencies": {
"@types/redis": "^4.0.11",
"redis": "^4.6.15"
},
"dependencies": {
"testcontainers": "^10.17.1"
}
}
4 changes: 4 additions & 0 deletions packages/modules/valkey/src/import.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env bash
set -e
valkey-cli $([[ -n "$1" ]] && echo "-a $1") < "/tmp/import.valkey"
echo "Imported"
1 change: 1 addition & 0 deletions packages/modules/valkey/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { ValkeyContainer, StartedValkeyContainer } from "./valkey-container";
2 changes: 2 additions & 0 deletions packages/modules/valkey/src/initData.valkey
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
SET "user:001" '{"first_name":"John","last_name":"Doe","dob":"12-JUN-1970"}'
SET "user:002" '{"first_name":"David","last_name":"Bloom","dob":"03-MAR-1981"}'
104 changes: 104 additions & 0 deletions packages/modules/valkey/src/valkey-container.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { createClient } from "redis";
import { ValkeyContainer, StartedValkeyContainer } from "./valkey-container";
import * as os from "os";
import * as path from "path";
import * as fs from "fs";

describe("ValkeyContainer", () => {
jest.setTimeout(240_000);

it("should connect and execute set-get", async () => {
const container = await new ValkeyContainer().start();

const client = await connectTo(container);

await client.set("key", "val");
expect(await client.get("key")).toBe("val");

await client.disconnect();
await container.stop();
});

it("should connect with password and execute set-get", async () => {
const container = await new ValkeyContainer().withPassword("test").start();

const client = await connectTo(container);

await client.set("key", "val");
expect(await client.get("key")).toBe("val");

await client.disconnect();
await container.stop();
});

it("should reconnect with volume and persistence data", async () => {
const sourcePath = fs.mkdtempSync(path.join(os.tmpdir(), "valkey-"));
const container = await new ValkeyContainer().withPassword("test").withPersistence(sourcePath).start();
let client = await connectTo(container);

await client.set("key", "val");
await client.disconnect();
await container.restart();
client = await connectTo(container);
expect(await client.get("key")).toBe("val");

await client.disconnect();
await container.stop();
try {
fs.rmSync(sourcePath, { force: true, recursive: true });
} catch (e) {
//Ignore clean up, when have no access on fs.
console.log(e);
}
});

it("should load initial data and can read it", async () => {
const container = await new ValkeyContainer()
.withPassword("test")
.withInitialData(path.join(__dirname, "initData.valkey"))
.start();
const client = await connectTo(container);
const user = {
first_name: "David",
last_name: "Bloom",
dob: "03-MAR-1981",
};
expect(await client.get("user:002")).toBe(JSON.stringify(user));

await client.disconnect();
await container.stop();
});

it("should start with credentials and login", async () => {
const password = "testPassword";

const container = await new ValkeyContainer().withPassword(password).start();
expect(container.getConnectionUrl()).toEqual(`redis://:${password}@${container.getHost()}:${container.getPort()}`);

const client = await connectTo(container);

await client.set("key", "val");
expect(await client.get("key")).toBe("val");

await client.disconnect();
await container.stop();
});

it("should execute container cmd and return the result", async () => {
const container = await new ValkeyContainer().start();

const queryResult = await container.executeCliCmd("info", ["clients"]);
expect(queryResult).toEqual(expect.stringContaining("connected_clients:1"));

await container.stop();
});

async function connectTo(container: StartedValkeyContainer) {
const client = createClient({
url: container.getConnectionUrl(),
});
await client.connect();
expect(client.isOpen).toBeTruthy();
return client;
}
});
Loading