Skip to content

Commit 1b274c1

Browse files
authored
Add Cassandra module (#855)
1 parent 798c057 commit 1b274c1

File tree

10 files changed

+363
-0
lines changed

10 files changed

+363
-0
lines changed

docs/modules/cassandra.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Cassandra Module
2+
3+
[Cassandra](https://cassandra.apache.org/_/index.html) is a free and open source, distributed NoSQL database management system. It is designed to handle large amounts of data across many commodity servers, providing high availability with no single point of failure.
4+
5+
6+
7+
## Install
8+
9+
```bash
10+
npm install @testcontainers/cassandra --save-dev
11+
```
12+
13+
## Examples
14+
15+
<!--codeinclude-->
16+
[Connect:](../../packages/modules/cassandra/src/cassandra-container.test.ts) inside_block:connectWithDefaultCredentials
17+
<!--/codeinclude-->
18+
19+
<!--codeinclude-->
20+
[Connect with custom credentials:](../../packages/modules/cassandra/src/cassandra-container.test.ts) inside_block:connectWithCustomCredentials
21+
<!--/codeinclude-->
22+
23+
<!--codeinclude-->
24+
[With custom datacenter / rack](../../packages/modules/cassandra/src/cassandra-container.test.ts) inside_block:customDataSenterAndRack
25+
<!--/codeinclude-->
26+
27+
<!--codeinclude-->
28+
[Insert & fetch data:](../../packages/modules/cassandra/src/cassandra-container.test.ts) inside_block:createAndFetchData
29+
<!--/codeinclude-->

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ nav:
4343
- Advanced: features/advanced.md
4444
- Modules:
4545
- ArangoDB: modules/arangodb.md
46+
- Cassandra: modules/cassandra.md
4647
- ChromaDB: modules/chromadb.md
4748
- Couchbase: modules/couchbase.md
4849
- Elasticsearch: modules/elasticsearch.md

package-lock.json

Lines changed: 49 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: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"name": "@testcontainers/cassandra",
3+
"version": "10.13.2",
4+
"license": "MIT",
5+
"keywords": [
6+
"mariadb",
7+
"testing",
8+
"docker",
9+
"testcontainers"
10+
],
11+
"description": "Cassandra 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+
"dependencies": {
32+
"testcontainers": "^10.13.2"
33+
},
34+
"devDependencies": {
35+
"cassandra-driver": "^4.7.2"
36+
}
37+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { Client } from "cassandra-driver";
2+
import { CassandraContainer } from "./cassandra-container";
3+
4+
describe("Cassandra", () => {
5+
jest.setTimeout(240_000);
6+
7+
// connectWithDefaultCredentials {
8+
it("should connect and execute a query with default credentials", async () => {
9+
const container = await new CassandraContainer("cassandra:5.0.2").start();
10+
11+
const client = new Client({
12+
contactPoints: [container.getContactPoint()],
13+
localDataCenter: container.getDatacenter(),
14+
keyspace: "system",
15+
});
16+
17+
await client.connect();
18+
19+
const result = await client.execute("SELECT release_version FROM system.local");
20+
expect(result.rows[0].release_version).toBe("5.0.2");
21+
22+
await client.shutdown();
23+
await container.stop();
24+
});
25+
// }
26+
27+
// connectWithCustomCredentials {
28+
it("should connect with custom username and password", async () => {
29+
const username = "testUser";
30+
const password = "testPassword";
31+
32+
const container = await new CassandraContainer().withUsername(username).withPassword(password).start();
33+
34+
const client = new Client({
35+
contactPoints: [container.getContactPoint()],
36+
localDataCenter: container.getDatacenter(),
37+
credentials: { username, password },
38+
keyspace: "system",
39+
});
40+
41+
await client.connect();
42+
43+
const result = await client.execute("SELECT release_version FROM system.local");
44+
expect(result.rows.length).toBeGreaterThan(0);
45+
46+
await client.shutdown();
47+
await container.stop();
48+
});
49+
// }
50+
51+
// customDataSenterAndRack {
52+
it("should set datacenter and rack", async () => {
53+
const customDataCenter = "customDC";
54+
const customRack = "customRack";
55+
const container = await new CassandraContainer().withDatacenter(customDataCenter).withRack(customRack).start();
56+
57+
const client = new Client({
58+
contactPoints: [container.getContactPoint()],
59+
localDataCenter: container.getDatacenter(),
60+
});
61+
62+
await client.connect();
63+
const result = await client.execute("SELECT data_center, rack FROM system.local");
64+
expect(result.rows[0].data_center).toBe(customDataCenter);
65+
expect(result.rows[0].rack).toBe(customRack);
66+
67+
await client.shutdown();
68+
await container.stop();
69+
});
70+
// }
71+
72+
// createAndFetchData {
73+
it("should create keyspace, a table, insert data, and retrieve it", async () => {
74+
const container = await new CassandraContainer().start();
75+
76+
const client = new Client({
77+
contactPoints: [container.getContactPoint()],
78+
localDataCenter: container.getDatacenter(),
79+
});
80+
81+
await client.connect();
82+
83+
// Create the keyspace
84+
await client.execute(`
85+
CREATE KEYSPACE IF NOT EXISTS test_keyspace
86+
WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}
87+
`);
88+
89+
await client.execute("USE test_keyspace");
90+
91+
// Create the table.
92+
await client.execute(`
93+
CREATE TABLE IF NOT EXISTS test_keyspace.users (
94+
id UUID PRIMARY KEY,
95+
name text
96+
)
97+
`);
98+
99+
// Insert a record
100+
const id = "d002cd08-401a-47d6-92d7-bb4204d092f8"; // Fixed UUID for testing
101+
const username = "Testy McTesterson";
102+
client.execute("INSERT INTO test_keyspace.users (id, name) VALUES (?, ?)", [id, username]);
103+
104+
// Fetch and verify the record
105+
const result = await client.execute("SELECT * FROM test_keyspace.users WHERE id = ?", [id], { prepare: true });
106+
expect(result.rows[0].name).toBe(username);
107+
108+
await client.shutdown();
109+
await container.stop();
110+
});
111+
// }
112+
});
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { AbstractStartedContainer, GenericContainer, type StartedTestContainer } from "testcontainers";
2+
3+
const CASSANDRA_PORT = 9042;
4+
5+
export class CassandraContainer extends GenericContainer {
6+
private dc = "dc1";
7+
private rack = "rack1";
8+
private username = "cassandra";
9+
private password = "cassandra";
10+
11+
constructor(image = "cassandra:5.0.2") {
12+
super(image);
13+
this.withExposedPorts(CASSANDRA_PORT).withStartupTimeout(120_000);
14+
}
15+
16+
public withDatacenter(dc: string): this {
17+
this.dc = dc;
18+
return this;
19+
}
20+
21+
public withRack(rack: string): this {
22+
this.rack = rack;
23+
return this;
24+
}
25+
26+
public withUsername(username: string): this {
27+
this.username = username;
28+
return this;
29+
}
30+
31+
public withPassword(password: string): this {
32+
this.password = password;
33+
return this;
34+
}
35+
36+
public override async start(): Promise<StartedCassandraContainer> {
37+
this.withEnvironment({
38+
CASSANDRA_DC: this.dc,
39+
CASSANDRA_RACK: this.rack,
40+
CASSANDRA_LISTEN_ADDRESS: "auto",
41+
CASSANDRA_BROADCAST_ADDRESS: "auto",
42+
CASSANDRA_RPC_ADDRESS: "0.0.0.0",
43+
CASSANDRA_USERNAME: this.username,
44+
CASSANDRA_PASSWORD: this.password,
45+
CASSANDRA_SNITCH: "GossipingPropertyFileSnitch",
46+
CASSANDRA_ENDPOINT_SNITCH: "GossipingPropertyFileSnitch",
47+
});
48+
return new StartedCassandraContainer(await super.start(), this.dc, this.rack, this.username, this.password);
49+
}
50+
}
51+
52+
export class StartedCassandraContainer extends AbstractStartedContainer {
53+
private readonly port: number;
54+
55+
constructor(
56+
startedTestContainer: StartedTestContainer,
57+
private readonly dc: string,
58+
private readonly rack: string,
59+
private readonly username: string,
60+
private readonly password: string
61+
) {
62+
super(startedTestContainer);
63+
this.port = startedTestContainer.getMappedPort(CASSANDRA_PORT);
64+
}
65+
66+
public getPort(): number {
67+
return this.port;
68+
}
69+
70+
public getDatacenter(): string {
71+
return this.dc;
72+
}
73+
74+
public getRack(): string {
75+
return this.rack;
76+
}
77+
78+
public getUsername(): string {
79+
return this.username;
80+
}
81+
82+
public getPassword(): string {
83+
return this.password;
84+
}
85+
86+
public getContactPoint(): string {
87+
return `${this.getHost()}:${this.getPort()}`;
88+
}
89+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { CassandraContainer, StartedCassandraContainer } from "./cassandra-container";
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"extends": "./tsconfig.json",
3+
"exclude": [
4+
"build",
5+
"jest.config.ts",
6+
"src/**/*.test.ts"
7+
],
8+
"references": [
9+
{
10+
"path": "../../testcontainers"
11+
}
12+
]
13+
}

0 commit comments

Comments
 (0)