Skip to content

Commit 2526634

Browse files
authored
Add Oracle Free module (#1242)
1 parent da383da commit 2526634

File tree

10 files changed

+358
-0
lines changed

10 files changed

+358
-0
lines changed

docs/modules/oraclefree.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Oracle Free
2+
3+
## Install
4+
5+
```bash
6+
npm install @testcontainers/oraclefree --save-dev
7+
```
8+
9+
## Examples
10+
11+
These examples use the following libraries:
12+
13+
- [oracledb](https://www.npmjs.com/package/oracledb)
14+
15+
npm install oracledb
16+
17+
Recommended to use an image from [this registry](https://hub.docker.com/r/gvenzl/oracle-free) and substitute for `IMAGE`
18+
19+
### Start a database and execute queries
20+
21+
<!--codeinclude-->
22+
[](../../packages/modules/oraclefree/src/oraclefree-container.test.ts) inside_block:customDatabase
23+
<!--/codeinclude-->

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ nav:
8181
- Neo4J: modules/neo4j.md
8282
- Ollama: modules/ollama.md
8383
- OpenSearch: modules/opensearch.md
84+
- Oracle Free: modules/oraclefree.md
8485
- PostgreSQL: modules/postgresql.md
8586
- Qdrant: modules/qdrant.md
8687
- RabbitMQ: modules/rabbitmq.md

package-lock.json

Lines changed: 37 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
FROM gvenzl/oracle-free:23.26.1-slim-faststart
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"name": "@testcontainers/oraclefree",
3+
"version": "11.12.0",
4+
"license": "MIT",
5+
"keywords": [
6+
"oracle",
7+
"database",
8+
"testing",
9+
"docker",
10+
"testcontainers"
11+
],
12+
"description": "Oracle DB Free module for Testcontainers",
13+
"homepage": "https://github.com/testcontainers/testcontainers-node#readme",
14+
"repository": {
15+
"type": "git",
16+
"url": "git+https://github.com/testcontainers/testcontainers-node.git"
17+
},
18+
"bugs": {
19+
"url": "https://github.com/testcontainers/testcontainers-node/issues"
20+
},
21+
"main": "build/index.js",
22+
"files": [
23+
"build"
24+
],
25+
"publishConfig": {
26+
"access": "public"
27+
},
28+
"scripts": {
29+
"prepack": "shx cp ../../../README.md . && shx cp ../../../LICENSE .",
30+
"build": "tsc --project tsconfig.build.json"
31+
},
32+
"dependencies": {
33+
"testcontainers": "^11.12.0"
34+
},
35+
"devDependencies": {
36+
"oracledb": "^6.10.0",
37+
"@types/oracledb": "^6.10.0"
38+
}
39+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { OracleDbContainer, StartedOracleDbContainer } from "./oraclefree-container";
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import oracledb from "oracledb";
2+
import { getImage } from "../../../testcontainers/src/utils/test-helper";
3+
import { OracleDbContainer, StartedOracleDbContainer } from "./oraclefree-container";
4+
5+
const IMAGE = getImage(__dirname);
6+
7+
describe("OracleFreeContainer", { timeout: 240_000 }, () => {
8+
describe("default configuration", () => {
9+
let container: StartedOracleDbContainer;
10+
11+
// start one container for all tests in this block to save on resources
12+
beforeAll(async () => {
13+
container = await new OracleDbContainer(IMAGE).start();
14+
}, 120_000);
15+
16+
afterAll(async () => {
17+
await container.stop();
18+
});
19+
20+
it("should connect and return a query result", async () => {
21+
const connection = await oracledb.getConnection({
22+
user: container.getUsername(),
23+
password: container.getPassword(),
24+
connectString: container.getUrl(),
25+
});
26+
27+
const result = await connection.execute("SELECT 1 FROM DUAL");
28+
expect(result.rows![0]).toEqual([1]);
29+
30+
await connection.close();
31+
});
32+
33+
it("should work with connection descriptor", async () => {
34+
const connection = await oracledb.getConnection({
35+
user: container.getUsername(),
36+
password: container.getPassword(),
37+
connectString: container.getConnectionDescriptor(),
38+
});
39+
40+
const result = await connection.execute("SELECT 1 FROM DUAL");
41+
expect(result.rows![0]).toEqual([1]);
42+
43+
await connection.close();
44+
});
45+
46+
it("should have default database name", async () => {
47+
const connection = await oracledb.getConnection({
48+
user: container.getUsername(),
49+
password: container.getPassword(),
50+
connectString: container.getUrl(),
51+
});
52+
53+
const result = await connection.execute("SELECT SYS_CONTEXT('USERENV', 'CON_NAME') FROM DUAL");
54+
expect(result.rows![0]).toEqual(["FREEPDB1"]);
55+
56+
await connection.close();
57+
});
58+
});
59+
60+
it("should treat default database names as no-op and reject empty names", () => {
61+
const container = new OracleDbContainer(IMAGE);
62+
expect(() => container.withDatabase("FREEPDB1")).not.toThrow();
63+
expect(() => container.withDatabase("freepdb1")).not.toThrow();
64+
expect(() => container.withDatabase("")).toThrow("Database name cannot be empty.");
65+
});
66+
67+
it("should set the custom database and user", async () => {
68+
// customDatabase {
69+
const customDatabase = "TESTDB";
70+
const customUsername = "CUSTOMUSER";
71+
const customPassword = "customPassword";
72+
await using container = await new OracleDbContainer(IMAGE)
73+
.withDatabase(customDatabase)
74+
.withUsername(customUsername)
75+
.withPassword(customPassword)
76+
.start();
77+
78+
const connection = await oracledb.getConnection({
79+
user: container.getUsername(),
80+
password: container.getPassword(),
81+
connectString: container.getUrl(),
82+
});
83+
84+
const result = await connection.execute("SELECT SYS_CONTEXT('USERENV', 'CON_NAME') FROM DUAL");
85+
expect(result.rows![0]).toEqual([customDatabase]);
86+
87+
const resultUser = await connection.execute("SELECT USER FROM DUAL");
88+
expect(resultUser.rows![0]).toEqual([customUsername]);
89+
90+
await connection.close();
91+
// }
92+
});
93+
94+
it("should work with restarted container", async () => {
95+
const container = await new OracleDbContainer(IMAGE).start();
96+
await container.restart();
97+
98+
const connection = await oracledb.getConnection({
99+
user: container.getUsername(),
100+
password: container.getPassword(),
101+
connectString: container.getUrl(),
102+
});
103+
104+
const result = await connection.execute("SELECT 1 FROM DUAL");
105+
expect(result.rows![0]).toEqual([1]);
106+
107+
await connection.close();
108+
});
109+
});
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import { AbstractStartedContainer, GenericContainer, StartedTestContainer, Wait } from "testcontainers";
2+
3+
/** Default Oracle listener port. */
4+
const ORACLEDB_PORT = 1521;
5+
/** Default pluggable database name provided by the Oracle Free image. */
6+
const DEFAULT_DATABASE = "FREEPDB1";
7+
8+
/**
9+
* Testcontainers wrapper for Oracle Free.
10+
*
11+
* Supports configuring application user credentials and optional custom database creation.
12+
*/
13+
export class OracleDbContainer extends GenericContainer {
14+
private username = "test";
15+
private password = "test";
16+
private database?: string = undefined;
17+
18+
constructor(image: string) {
19+
super(image);
20+
this.withExposedPorts(ORACLEDB_PORT);
21+
this.withWaitStrategy(Wait.forLogMessage("DATABASE IS READY TO USE!"));
22+
this.withStartupTimeout(120_000);
23+
}
24+
25+
/** Sets the application username created at container startup. */
26+
public withUsername(username: string): this {
27+
this.username = username;
28+
return this;
29+
}
30+
31+
/** Sets the password for both SYS and application user startup configuration. */
32+
public withPassword(password: string): this {
33+
this.password = password;
34+
return this;
35+
}
36+
37+
/** Sets a custom application database/service name. */
38+
public withDatabase(database: string): this {
39+
if (database.trim() === "") {
40+
throw new Error("Database name cannot be empty.");
41+
}
42+
43+
if (database.toUpperCase() === DEFAULT_DATABASE) {
44+
this.database = undefined;
45+
return this;
46+
}
47+
48+
this.database = database;
49+
return this;
50+
}
51+
52+
/** Starts the container and returns a typed started container instance. */
53+
public override async start(): Promise<StartedOracleDbContainer> {
54+
this.withEnvironment({
55+
ORACLE_PASSWORD: this.password,
56+
APP_USER: this.username,
57+
APP_USER_PASSWORD: this.password,
58+
});
59+
60+
if (this.database) {
61+
this.withEnvironment({
62+
ORACLE_DATABASE: this.database,
63+
});
64+
}
65+
66+
return new StartedOracleDbContainer(
67+
await super.start(),
68+
this.username,
69+
this.password,
70+
this.database ?? DEFAULT_DATABASE
71+
);
72+
}
73+
}
74+
75+
/** Represents a running Oracle Free test container with Oracle-specific accessors. */
76+
export class StartedOracleDbContainer extends AbstractStartedContainer {
77+
constructor(
78+
startedTestContainer: StartedTestContainer,
79+
private readonly username: string,
80+
private readonly password: string,
81+
private readonly database: string
82+
) {
83+
super(startedTestContainer);
84+
}
85+
86+
/** Returns the mapped Oracle listener port. */
87+
public getPort(): number {
88+
return this.getMappedPort(ORACLEDB_PORT);
89+
}
90+
91+
/** Returns the configured application username. */
92+
public getUsername(): string {
93+
return this.username;
94+
}
95+
96+
/** Returns the configured password. */
97+
public getPassword(): string {
98+
return this.password;
99+
}
100+
101+
/** Returns the configured service/database name. */
102+
public getDatabase(): string {
103+
return this.database;
104+
}
105+
106+
/** Returns a host:port/database URL fragment. */
107+
public getUrl(): string {
108+
return `${this.getHost()}:${this.getPort()}/${this.database}`;
109+
}
110+
111+
/** Returns an Oracle connection descriptor string (TNS format). */
112+
public getConnectionDescriptor(): string {
113+
return `(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=${this.getHost()})(PORT=${this.getPort()}))(CONNECT_DATA=(SERVICE_NAME=${this.database})))`;
114+
}
115+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"extends": "./tsconfig.json",
3+
"exclude": [
4+
"build",
5+
"src/**/*.test.ts"
6+
],
7+
"references": [
8+
{
9+
"path": "../../testcontainers"
10+
}
11+
]
12+
}

0 commit comments

Comments
 (0)