Skip to content
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
4 changes: 4 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ It captures practical rules that prevent avoidable CI and PR churn.
- If new learnings or misunderstandings are discovered, propose an `AGENTS.md` update in the same PR.
- Tests should verify observable behavior changes, not only internal/config state.
- Example: for a security option, assert a real secure/insecure behavior difference.
- Vitest runs tests concurrently by default (`sequence.concurrent: true` in `vitest.config.ts`).
- Tests that rely on shared/global mocks (for example `vi.spyOn` on shared loggers/singletons) can be flaky due to interleaving or automatic mock resets.
- Prefer asserting observable behavior instead of shared global mock state when possible.
- If a test must depend on shared/global mock state, use `it.sequential(...)` or `describe.sequential(...)`.

## Permission and Escalation

Expand Down
6 changes: 4 additions & 2 deletions docs/features/compose.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,18 @@ Provide a list of service names to only start those services:

```js
const environment = await new DockerComposeEnvironment(composeFilePath, composeFile)
.up(["redis-1", "postgres-1"]);
.up(["redis", "postgres"]);
```

### With wait strategy

`withWaitStrategy` expects **container names**, not service names. With Docker Compose v2, the default container name for the first replica is usually `<service>-1`.

```js
const environment = await new DockerComposeEnvironment(composeFilePath, composeFile)
.withWaitStrategy("redis-1", Wait.forLogMessage("Ready to accept connections"))
.withWaitStrategy("postgres-1", Wait.forHealthCheck())
.up();
.up(["redis", "postgres"]);
```

### With a default wait strategy
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import path from "path";
import { RandomUuid } from "../common";
import { log, RandomUuid } from "../common";
import { randomUuid } from "../common/uuid";
import { PullPolicy } from "../utils/pull-policy";
import {
Expand Down Expand Up @@ -131,6 +131,26 @@ describe("DockerComposeEnvironment", { timeout: 180_000 }, () => {
await checkEnvironmentContainerIsHealthy(startedEnvironment, await composeContainerName("container"));
});

it.sequential("should warn when no started containers match configured wait strategy names", async () => {
const unmatchedWaitStrategyName = "non-existent-container-name";
const warnSpy = vi.spyOn(log, "warn");

await using startedEnvironment = await new DockerComposeEnvironment(fixtures, "docker-compose.yml")
.withWaitStrategy(unmatchedWaitStrategyName, Wait.forLogMessage("Listening on port 8080"))
.up(["container"]);

await checkEnvironmentContainerIsHealthy(startedEnvironment, await composeContainerName("container"));

const warningMessages = warnSpy.mock.calls.map(([message]) => message);
expect(
warningMessages.some((warningMessage) =>
warningMessage.includes(
`No containers were started for the configured wait strategy names: "${unmatchedWaitStrategyName}"`
)
)
).toBe(true);
});

it("should support failing health check wait strategy", async () => {
await expect(
new DockerComposeEnvironment(fixtures, "docker-compose-with-healthcheck-unhealthy.yml")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,13 @@ export class DockerComposeEnvironment {
);
log.info(`Started containers "${startedContainerNames.join('", "')}"`);

const startedContainerNameSet = new Set(
startedContainers.map((startedContainer) =>
parseComposeContainerName(this.projectName, startedContainer.Names[0])
)
);
this.warnForUnusedWaitStrategies(startedContainerNameSet);

const startedGenericContainers = (
await Promise.all(
startedContainers.map(async (startedContainer) => {
Expand Down Expand Up @@ -207,4 +214,15 @@ export class DockerComposeEnvironment {
environment: this.environment,
});
}

private warnForUnusedWaitStrategies(startedContainerNames: Set<string>): void {
const unusedWaitStrategyContainerNames = Object.keys(this.waitStrategy).filter(
(configuredContainerName) => !startedContainerNames.has(configuredContainerName)
);
if (unusedWaitStrategyContainerNames.length > 0) {
log.warn(
`No containers were started for the configured wait strategy names: "${unusedWaitStrategyContainerNames.join('", "')}". Wait strategies are matched against container names (for example "redis-1"), not service names.`
);
}
}
}
Loading