Skip to content

Commit

Permalink
Fix PostgreSQL container restart (#914)
Browse files Browse the repository at this point in the history
  • Loading branch information
cristianrgreco authored Mar 3, 2025
1 parent 3b48cc0 commit e533856
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 9 deletions.
31 changes: 31 additions & 0 deletions packages/modules/postgresql/src/postgresql-container.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,35 @@ describe("PostgreSqlContainer", () => {
await container.stop();
});
// }

it("should work with restarted container", async () => {
const container = await new PostgreSqlContainer().start();
await container.restart();

const client = new Client({
host: container.getHost(),
port: container.getPort(),
database: container.getDatabase(),
user: container.getUsername(),
password: container.getPassword(),
});
await client.connect();

const result = await client.query("SELECT 1");
expect(result.rows[0]).toEqual({ "?column?": 1 });

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

it("should allow custom healthcheck", async () => {
const container = new PostgreSqlContainer().withHealthCheck({
test: ["CMD-SHELL", "exit 1"],
interval: 100,
retries: 0,
timeout: 0,
});

await expect(() => container.start()).rejects.toThrow();
});
});
19 changes: 12 additions & 7 deletions packages/modules/postgresql/src/postgresql-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ export class PostgreSqlContainer extends GenericContainer {

constructor(image = "postgres:13.3-alpine") {
super(image);
this.withExposedPorts(POSTGRES_PORT)
.withWaitStrategy(Wait.forLogMessage(/.*database system is ready to accept connections.*/, 2))
.withStartupTimeout(120_000);
this.withExposedPorts(POSTGRES_PORT);
this.withWaitStrategy(Wait.forHealthCheck());
this.withStartupTimeout(120_000);
}

public withDatabase(database: string): this {
Expand All @@ -35,25 +35,30 @@ export class PostgreSqlContainer extends GenericContainer {
POSTGRES_USER: this.username,
POSTGRES_PASSWORD: this.password,
});
if (!this.healthCheck) {
this.withHealthCheck({
test: ["CMD-SHELL", `PGPASSWORD=${this.password} psql -U ${this.username} -d ${this.database} -c 'SELECT 1;'`],
interval: 250,
timeout: 1000,
retries: 1000,
});
}
return new StartedPostgreSqlContainer(await super.start(), this.database, this.username, this.password);
}
}

export class StartedPostgreSqlContainer extends AbstractStartedContainer {
private readonly port: number;

constructor(
startedTestContainer: StartedTestContainer,
private readonly database: string,
private readonly username: string,
private readonly password: string
) {
super(startedTestContainer);
this.port = startedTestContainer.getMappedPort(POSTGRES_PORT);
}

public getPort(): number {
return this.port;
return super.getMappedPort(POSTGRES_PORT);
}

public getDatabase(): string {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,7 @@ export class DockerContainerClient implements ContainerClient {

async inspect(container: Dockerode.Container): Promise<ContainerInspectInfo> {
try {
log.debug(`Inspecting container...`, { containerId: container.id });
const inspectInfo = await container.inspect();
log.debug(`Inspected container`, { containerId: container.id });
return inspectInfo;
} catch (err) {
log.error(`Failed to inspect container: ${err}`, { containerId: container.id });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export class GenericContainer implements TestContainer {
protected filesToCopy: FileToCopy[] = [];
protected directoriesToCopy: DirectoryToCopy[] = [];
protected contentsToCopy: ContentToCopy[] = [];
protected healthCheck?: HealthCheck;

constructor(image: string) {
this.imageName = ImageName.fromString(image);
Expand Down Expand Up @@ -387,13 +388,15 @@ export class GenericContainer implements TestContainer {
public withHealthCheck(healthCheck: HealthCheck): this {
const toNanos = (duration: number): number => duration * 1e6;

this.healthCheck = healthCheck;
this.createOpts.Healthcheck = {
Test: healthCheck.test,
Interval: healthCheck.interval ? toNanos(healthCheck.interval) : 0,
Timeout: healthCheck.timeout ? toNanos(healthCheck.timeout) : 0,
Retries: healthCheck.retries || 0,
StartPeriod: healthCheck.startPeriod ? toNanos(healthCheck.startPeriod) : 0,
};

return this;
}

Expand Down

0 comments on commit e533856

Please sign in to comment.