182 lines
5.8 KiB
TypeScript
182 lines
5.8 KiB
TypeScript
import {
|
|
afterEach,
|
|
beforeEach,
|
|
describe,
|
|
expect,
|
|
it,
|
|
} from "@suaveplan/testing/runner";
|
|
import { MailpitFixture } from "./mailpit-fixture.js";
|
|
|
|
function makeContainerFake(smtpPort = 1025, apiPort = 8025) {
|
|
const stopped: string[] = [];
|
|
const portMap: Record<number, number> = {
|
|
1025: smtpPort,
|
|
8025: apiPort,
|
|
};
|
|
const fakeStarted = {
|
|
getMappedPort: (port: number) => portMap[port] ?? port,
|
|
stop: async () => {
|
|
stopped.push("stopped");
|
|
},
|
|
};
|
|
const fakeContainer = {
|
|
withExposedPorts: function () {
|
|
return this;
|
|
},
|
|
withWaitStrategy: function () {
|
|
return this;
|
|
},
|
|
start: async () => fakeStarted,
|
|
};
|
|
return {
|
|
fakeContainer,
|
|
fakeStarted,
|
|
stopped,
|
|
factory: (_img: string) => fakeContainer as never,
|
|
};
|
|
}
|
|
|
|
describe("MailpitFixture", () => {
|
|
beforeEach(() => {
|
|
delete process.env["MAILPIT_SMTP_URL"];
|
|
delete process.env["MAILPIT_API_URL"];
|
|
});
|
|
|
|
afterEach(() => {
|
|
delete process.env["MAILPIT_SMTP_URL"];
|
|
delete process.env["MAILPIT_API_URL"];
|
|
});
|
|
|
|
it("name property returns mailpit", { timeout: 5000 }, () => {
|
|
const fixture = new MailpitFixture();
|
|
expect(fixture.name).toBe("mailpit");
|
|
});
|
|
|
|
it("passthrough mode: returns config from both env vars", {
|
|
timeout: 30000,
|
|
}, async () => {
|
|
process.env["MAILPIT_SMTP_URL"] = "smtp://localhost:1025";
|
|
process.env["MAILPIT_API_URL"] = "http://localhost:8025";
|
|
|
|
const fixture = new MailpitFixture();
|
|
const config = await fixture.start();
|
|
|
|
expect(config.smtpUrl).toBe("smtp://localhost:1025");
|
|
expect(config.apiUrl).toBe("http://localhost:8025");
|
|
});
|
|
|
|
it("passthrough mode: stop is a no-op", { timeout: 30000 }, async () => {
|
|
process.env["MAILPIT_SMTP_URL"] = "smtp://localhost:1025";
|
|
process.env["MAILPIT_API_URL"] = "http://localhost:8025";
|
|
|
|
const fixture = new MailpitFixture();
|
|
await fixture.start();
|
|
await expect(fixture.stop()).resolves.toBeUndefined();
|
|
});
|
|
|
|
it("partial passthrough (only SMTP set) falls through to container mode", {
|
|
timeout: 30000,
|
|
}, async () => {
|
|
process.env["MAILPIT_SMTP_URL"] = "smtp://localhost:1025";
|
|
// MAILPIT_API_URL not set
|
|
|
|
const { factory } = makeContainerFake(11025, 18025);
|
|
const fixture = new MailpitFixture({ containerFactory: factory });
|
|
const config = await fixture.start();
|
|
|
|
// Should have used the container, not env var
|
|
expect(config.smtpUrl).toBe("smtp://localhost:11025");
|
|
expect(config.apiUrl).toBe("http://localhost:18025");
|
|
});
|
|
|
|
it("container mode happy path: returns correct URLs", {
|
|
timeout: 30000,
|
|
}, async () => {
|
|
const { factory } = makeContainerFake(11025, 18025);
|
|
const fixture = new MailpitFixture({ containerFactory: factory });
|
|
const config = await fixture.start();
|
|
|
|
expect(config.smtpUrl).toBe("smtp://localhost:11025");
|
|
expect(config.apiUrl).toBe("http://localhost:18025");
|
|
});
|
|
|
|
it("container mode: stop calls container.stop()", {
|
|
timeout: 30000,
|
|
}, async () => {
|
|
const { factory, stopped } = makeContainerFake(11025, 18025);
|
|
const fixture = new MailpitFixture({ containerFactory: factory });
|
|
await fixture.start();
|
|
await fixture.stop();
|
|
expect(stopped).toContain("stopped");
|
|
});
|
|
|
|
it("healthCheck returns true via injected fetchFn", {
|
|
timeout: 30000,
|
|
}, async () => {
|
|
const { factory } = makeContainerFake(11025, 18025);
|
|
const fixture = new MailpitFixture({
|
|
containerFactory: factory,
|
|
fetchFn: async () => ({ ok: true }) as Response,
|
|
});
|
|
await fixture.start();
|
|
const healthy = await fixture.healthCheck();
|
|
expect(healthy).toBe(true);
|
|
});
|
|
|
|
it("healthCheck returns false via injected fetchFn (ok: false)", {
|
|
timeout: 30000,
|
|
}, async () => {
|
|
const { factory } = makeContainerFake(11025, 18025);
|
|
const fixture = new MailpitFixture({
|
|
containerFactory: factory,
|
|
fetchFn: async () => ({ ok: false }) as Response,
|
|
});
|
|
await fixture.start();
|
|
const healthy = await fixture.healthCheck();
|
|
expect(healthy).toBe(false);
|
|
});
|
|
|
|
it("healthCheck returns false when fetch throws", {
|
|
timeout: 30000,
|
|
}, async () => {
|
|
const { factory } = makeContainerFake(11025, 18025);
|
|
const fixture = new MailpitFixture({
|
|
containerFactory: factory,
|
|
fetchFn: async () => {
|
|
throw new Error("ECONNREFUSED");
|
|
},
|
|
});
|
|
await fixture.start();
|
|
const healthy = await fixture.healthCheck();
|
|
expect(healthy).toBe(false);
|
|
});
|
|
|
|
it("healthCheck returns false when no container", {
|
|
timeout: 30000,
|
|
}, async () => {
|
|
const fixture = new MailpitFixture();
|
|
const healthy = await fixture.healthCheck();
|
|
expect(healthy).toBe(false);
|
|
});
|
|
|
|
it("stop when no container is a no-op", { timeout: 30000 }, async () => {
|
|
const fixture = new MailpitFixture();
|
|
await expect(fixture.stop()).resolves.toBeUndefined();
|
|
});
|
|
|
|
it("resolveContainerFactory: falls back to GenericContainer import", {
|
|
timeout: 60000,
|
|
}, async () => {
|
|
class Exposed extends MailpitFixture {
|
|
resolveFactory() {
|
|
return this.resolveContainerFactory();
|
|
}
|
|
}
|
|
const exposed = new Exposed();
|
|
const factory = await exposed.resolveFactory();
|
|
expect(typeof factory).toBe("function");
|
|
const container = factory("axllent/mailpit:latest");
|
|
expect(container).toBeDefined();
|
|
});
|
|
});
|