Template
1
0
Files
boilerplate/api/libraries/testing/containers/test-container.ts

179 lines
4.6 KiB
TypeScript

import { MongoTestContainer } from "@valkyr/testcontainers/mongodb";
import { config } from "../config.ts";
import { ApiTestContainer } from "./api-container.ts";
import { DatabaseTestContainer } from "./database-container.ts";
export class TestContainer {
readonly id = crypto.randomUUID();
// ### Enablers
// A map of services to enable when the TestContainer is started. These toggles
// must be toggled before the container is started.
#with: With = {
mongodb: false,
database: false,
api: false,
};
// ### Needs
#needs: Needs = {
mongodb: [],
database: ["mongodb"],
api: ["mongodb", "database"],
};
// ### Services
// Any services that has been enabled will be running under the following
// assignments. Make sure to .stop any running services to avoid shutdown
// leaks.
#mongodb?: MongoTestContainer;
#database?: DatabaseTestContainer;
#api?: ApiTestContainer;
/*
|--------------------------------------------------------------------------------
| Accessors
|--------------------------------------------------------------------------------
*/
get accountId() {
if (this.#api === undefined) {
throw new Error("TestContainer > .withApi() must be called before starting the TestContainer.");
}
return this.#api.accountId;
}
get mongodb(): MongoTestContainer {
if (this.#mongodb === undefined) {
throw new Error("TestContainer > .withMongo() must be called before starting the TestContainer.");
}
return this.#mongodb;
}
get database(): DatabaseTestContainer {
if (this.#database === undefined) {
throw new Error("TestContainer > .withDatabase() must be called before starting the TestContainer.");
}
return this.#database;
}
get api() {
if (this.#api === undefined) {
throw new Error("TestContainer > .withApi() must be called before starting the TestContainer.");
}
return this.#api.client;
}
get authorize() {
if (this.#api === undefined) {
throw new Error("TestContainer > .withApi() must be called before starting the TestContainer.");
}
return this.#api.authorize.bind(this.#api);
}
get unauthorize() {
if (this.#api === undefined) {
throw new Error("TestContainer > .withApi() must be called before starting the TestContainer.");
}
return this.#api.unauthorize.bind(this.#api);
}
/*
|--------------------------------------------------------------------------------
| Builder
|--------------------------------------------------------------------------------
*/
withMongo(): this {
this.#with.mongodb = true;
return this;
}
withDatabase(): this {
this.#with.database = true;
return this;
}
withApi(): this {
this.#with.api = true;
return this;
}
/*
|--------------------------------------------------------------------------------
| Lifecycle
|--------------------------------------------------------------------------------
*/
async start(): Promise<this> {
const promises: Promise<void>[] = [];
if (this.#isNeeded("mongodb") === true) {
promises.push(
(async () => {
this.#mongodb = await MongoTestContainer.start(config.mongodb);
if (this.#isNeeded("database") === true) {
this.#database = await new DatabaseTestContainer(this.mongodb).start();
}
})(),
);
}
if (this.#isNeeded("api") === true) {
promises.push(
(async () => {
this.#api = await new ApiTestContainer().start();
})(),
);
}
await Promise.all(promises);
return this;
}
async stop(): Promise<this> {
await this.#api?.stop();
await this.#database?.stop();
await this.#mongodb?.stop();
this.#api = undefined;
this.#database = undefined;
this.#mongodb = undefined;
return this;
}
/*
|--------------------------------------------------------------------------------
| Helpers
|--------------------------------------------------------------------------------
*/
#isNeeded(target: keyof With): boolean {
if (this.#with[target] !== false) {
return true;
}
for (const key in this.#needs) {
if (this.#with[key as keyof With] !== false && this.#needs[key as keyof With].includes(target) === true) {
return true;
}
}
return false;
}
}
/*
|--------------------------------------------------------------------------------
| Types
|--------------------------------------------------------------------------------
*/
type Needs = Record<keyof With, (keyof With)[]>;
type With = {
mongodb: boolean;
database: boolean;
api: boolean;
};