Skip to content

Commit 567c131

Browse files
Fix healthcheck for Postgres/variants (#972)
1 parent aebb6fc commit 567c131

File tree

13 files changed

+355
-233
lines changed

13 files changed

+355
-233
lines changed

docs/modules/postgresql.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,5 +38,5 @@ tests very modular, since they always run on a brand-new database.
3838
not work if the database for the container is set to `"postgres"`.
3939

4040
<!--codeinclude-->
41-
[Test with a reusable Postgres container](../../packages/modules/postgresql/src/postgresql-container.test.ts) inside_block:createAndRestoreFromSnapshot
41+
[Test with a reusable Postgres container](../../packages/modules/postgresql/src/postgresql-container-snapshot.test.ts) inside_block:createAndRestoreFromSnapshot
4242
<!--/codeinclude-->

package-lock.json

Lines changed: 0 additions & 19 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"scripts": {
1010
"pre-commit": "lint-staged",
1111
"docs:serve": "docker-compose up",
12-
"test": "cross-env NODE_ENV=test DEBUG=testcontainers* vitest run",
12+
"test": "vitest run",
1313
"test:ci": "npm run test -- --coverage",
1414
"format": "prettier --write package.json \"packages/**/*.ts\"",
1515
"lint": "eslint --fix package.json \"packages/**/*.ts\"",
@@ -21,7 +21,6 @@
2121
"@eslint/js": "^9.22.0",
2222
"@eslint/json": "^0.11.0",
2323
"@vitest/coverage-v8": "^3.1.1",
24-
"cross-env": "^7.0.3",
2524
"eslint": "^9.22.0",
2625
"eslint-config-prettier": "^10.1.1",
2726
"eslint-plugin-prettier": "^5.2.3",
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { Client } from "pg";
2+
import { PostgreSqlContainer } from "./postgresql-container";
3+
4+
const IMAGE = "pgvector/pgvector:pg16";
5+
6+
describe("PgvectorContainer", { timeout: 180_000 }, () => {
7+
it("should work", async () => {
8+
const container = await new PostgreSqlContainer(IMAGE).start();
9+
10+
const client = new Client({
11+
host: container.getHost(),
12+
port: container.getPort(),
13+
database: container.getDatabase(),
14+
user: container.getUsername(),
15+
password: container.getPassword(),
16+
});
17+
await client.connect();
18+
19+
const result = await client.query("SELECT 1");
20+
expect(result.rows[0]).toEqual({ "?column?": 1 });
21+
22+
await client.end();
23+
await container.stop();
24+
});
25+
26+
it("should restart", async () => {
27+
const container = await new PostgreSqlContainer(IMAGE).start();
28+
await container.restart();
29+
30+
const client = new Client({
31+
host: container.getHost(),
32+
port: container.getPort(),
33+
database: container.getDatabase(),
34+
user: container.getUsername(),
35+
password: container.getPassword(),
36+
});
37+
await client.connect();
38+
39+
const result = await client.query("SELECT 1");
40+
expect(result.rows[0]).toEqual({ "?column?": 1 });
41+
42+
await client.end();
43+
await container.stop();
44+
});
45+
});
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { Client } from "pg";
2+
import { PostgreSqlContainer } from "./postgresql-container";
3+
4+
const IMAGE = "postgis/postgis:16-3.4";
5+
6+
describe("PostgisContainer", { timeout: 180_000 }, () => {
7+
it("should work", async () => {
8+
const container = await new PostgreSqlContainer(IMAGE).start();
9+
10+
const client = new Client({
11+
host: container.getHost(),
12+
port: container.getPort(),
13+
database: container.getDatabase(),
14+
user: container.getUsername(),
15+
password: container.getPassword(),
16+
});
17+
await client.connect();
18+
19+
const result = await client.query("SELECT 1");
20+
expect(result.rows[0]).toEqual({ "?column?": 1 });
21+
22+
await client.end();
23+
await container.stop();
24+
});
25+
26+
it("should restart", async () => {
27+
const container = await new PostgreSqlContainer(IMAGE).start();
28+
await container.restart();
29+
30+
const client = new Client({
31+
host: container.getHost(),
32+
port: container.getPort(),
33+
database: container.getDatabase(),
34+
user: container.getUsername(),
35+
password: container.getPassword(),
36+
});
37+
await client.connect();
38+
39+
const result = await client.query("SELECT 1");
40+
expect(result.rows[0]).toEqual({ "?column?": 1 });
41+
42+
await client.end();
43+
await container.stop();
44+
});
45+
});
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
import { Client } from "pg";
2+
import { PostgreSqlContainer } from "./postgresql-container";
3+
4+
describe("PostgreSqlContainer snapshot and restore", { timeout: 180_000 }, () => {
5+
// createAndRestoreFromSnapshot {
6+
it("should create and restore from snapshot", async () => {
7+
const container = await new PostgreSqlContainer().start();
8+
9+
// Connect to the database
10+
let client = new Client({
11+
connectionString: container.getConnectionUri(),
12+
});
13+
await client.connect();
14+
15+
// Create some test data
16+
await client.query("CREATE TABLE test_table (id SERIAL PRIMARY KEY, name TEXT)");
17+
await client.query("INSERT INTO test_table (name) VALUES ('initial data')");
18+
19+
// Close connection before snapshot (otherwise we'll get an error because user is already connected)
20+
await client.end();
21+
22+
// Take a snapshot
23+
await container.snapshot();
24+
25+
// Reconnect to database
26+
client = new Client({
27+
connectionString: container.getConnectionUri(),
28+
});
29+
await client.connect();
30+
31+
// Modify the database
32+
await client.query("INSERT INTO test_table (name) VALUES ('data after snapshot')");
33+
34+
// Verify both records exist
35+
let result = await client.query("SELECT * FROM test_table ORDER BY id");
36+
expect(result.rows).toHaveLength(2);
37+
expect(result.rows[0].name).toEqual("initial data");
38+
expect(result.rows[1].name).toEqual("data after snapshot");
39+
40+
// Close connection before restore (same reason as above)
41+
await client.end();
42+
43+
// Restore to the snapshot
44+
await container.restoreSnapshot();
45+
46+
// Reconnect to database
47+
client = new Client({
48+
connectionString: container.getConnectionUri(),
49+
});
50+
await client.connect();
51+
52+
// Verify only the initial data exists after restore
53+
result = await client.query("SELECT * FROM test_table ORDER BY id");
54+
expect(result.rows).toHaveLength(1);
55+
expect(result.rows[0].name).toEqual("initial data");
56+
57+
await client.end();
58+
await container.stop();
59+
});
60+
// }
61+
62+
it("should use custom snapshot name", async () => {
63+
const container = await new PostgreSqlContainer().start();
64+
const customSnapshotName = "my_custom_snapshot";
65+
66+
// Connect to the database
67+
let client = new Client({
68+
connectionString: container.getConnectionUri(),
69+
});
70+
await client.connect();
71+
72+
// Create a test table and insert data
73+
await client.query("CREATE TABLE test_table (id SERIAL PRIMARY KEY, name TEXT)");
74+
await client.query("INSERT INTO test_table (name) VALUES ('initial data')");
75+
76+
// Close connection before snapshot
77+
await client.end();
78+
79+
// Take a snapshot with custom name
80+
await container.snapshot(customSnapshotName);
81+
82+
// Reconnect to database
83+
client = new Client({
84+
connectionString: container.getConnectionUri(),
85+
});
86+
await client.connect();
87+
88+
// Modify the database
89+
await client.query("INSERT INTO test_table (name) VALUES ('data after snapshot')");
90+
91+
// Close connection before restore
92+
await client.end();
93+
94+
// Restore using the custom snapshot name
95+
await container.restoreSnapshot(customSnapshotName);
96+
97+
// Reconnect to database
98+
client = new Client({
99+
connectionString: container.getConnectionUri(),
100+
});
101+
await client.connect();
102+
103+
// Verify only the initial data exists after restore
104+
const result = await client.query("SELECT * FROM test_table ORDER BY id");
105+
expect(result.rows).toHaveLength(1);
106+
expect(result.rows[0].name).toEqual("initial data");
107+
108+
await client.end();
109+
await container.stop();
110+
});
111+
112+
it("should handle multiple snapshots", async () => {
113+
const container = await new PostgreSqlContainer().start();
114+
115+
// Connect to the database
116+
let client = new Client({
117+
connectionString: container.getConnectionUri(),
118+
});
119+
await client.connect();
120+
121+
// Create a test table
122+
await client.query("CREATE TABLE test_table (id SERIAL PRIMARY KEY, name TEXT)");
123+
124+
// Close connection before snapshot
125+
await client.end();
126+
127+
// Take first snapshot with empty table
128+
await container.snapshot("snapshot1");
129+
130+
// Reconnect to database
131+
client = new Client({
132+
connectionString: container.getConnectionUri(),
133+
});
134+
await client.connect();
135+
136+
// Add first record
137+
await client.query("INSERT INTO test_table (name) VALUES ('data for snapshot 2')");
138+
139+
// Close connection before snapshot
140+
await client.end();
141+
142+
// Take second snapshot with one record
143+
await container.snapshot("snapshot2");
144+
145+
// Reconnect to database
146+
client = new Client({
147+
connectionString: container.getConnectionUri(),
148+
});
149+
await client.connect();
150+
151+
// Add second record
152+
await client.query("INSERT INTO test_table (name) VALUES ('data after snapshots')");
153+
154+
// Verify we have two records
155+
let result = await client.query("SELECT COUNT(*) as count FROM test_table");
156+
expect(result.rows[0].count).toEqual("2");
157+
158+
// Close connection before restore
159+
await client.end();
160+
161+
// Restore to first snapshot (empty table)
162+
await container.restoreSnapshot("snapshot1");
163+
164+
// Reconnect to database
165+
client = new Client({
166+
connectionString: container.getConnectionUri(),
167+
});
168+
await client.connect();
169+
170+
// Verify table is empty
171+
result = await client.query("SELECT COUNT(*) as count FROM test_table");
172+
expect(result.rows[0].count).toEqual("0");
173+
174+
// Close connection before restore
175+
await client.end();
176+
177+
// Restore to second snapshot (one record)
178+
await container.restoreSnapshot("snapshot2");
179+
180+
// Reconnect to database
181+
client = new Client({
182+
connectionString: container.getConnectionUri(),
183+
});
184+
await client.connect();
185+
186+
// Verify we have one record
187+
result = await client.query("SELECT * FROM test_table");
188+
expect(result.rows).toHaveLength(1);
189+
expect(result.rows[0].name).toEqual("data for snapshot 2");
190+
191+
await client.end();
192+
await container.stop();
193+
});
194+
195+
it("should throw an error when trying to snapshot postgres system database", async () => {
196+
const container = await new PostgreSqlContainer().withDatabase("postgres").start();
197+
198+
await expect(container.snapshot()).rejects.toThrow(
199+
"Snapshot feature is not supported when using the postgres system database"
200+
);
201+
202+
await expect(container.restoreSnapshot()).rejects.toThrow(
203+
"Snapshot feature is not supported when using the postgres system database"
204+
);
205+
206+
await container.stop();
207+
});
208+
});

0 commit comments

Comments
 (0)