FactuGES_ERP/tests/e2e/support/e2e-database-environment.ts

118 lines
3.4 KiB
TypeScript
Raw Permalink Normal View History

import { spawn } from "node:child_process";
import { randomBytes, randomUUID } from "node:crypto";
import { E2E_DATABASE_PREFIX } from "./e2e-contract";
const composeFile = "tests/e2e/docker-compose.yml";
export interface E2EDatabaseEnvironment {
readonly executionId: string;
readonly databaseName: string;
readonly projectName: string;
readonly containerId: string;
readonly host: string;
readonly port: number;
readonly username: string;
readonly password: string;
readonly environment: Record<string, string>;
destroy(): Promise<void>;
}
function run(command: string, args: string[], environment: NodeJS.ProcessEnv): Promise<string> {
return new Promise((resolve, reject) => {
const child = spawn(command, args, { env: environment, stdio: ["ignore", "pipe", "pipe"] });
let output = "";
let errorOutput = "";
child.stdout.on("data", (chunk: Buffer) => (output += chunk));
child.stderr.on("data", (chunk: Buffer) => (errorOutput += chunk));
child.on("error", reject);
child.on("close", (code) =>
code === 0
? resolve(output.trim())
: reject(new Error(`${command} ${args.join(" ")}: ${errorOutput}`))
);
});
}
/** Provisiona un único proyecto Compose y elimina exclusivamente ese proyecto. */
export async function createE2EDatabaseEnvironment(): Promise<E2EDatabaseEnvironment> {
const runId = randomUUID().replaceAll("-", "");
const databaseName = `${E2E_DATABASE_PREFIX}${runId}`;
const projectName = `factuges_e2e_${runId}`;
const user = "e2e";
const password = randomBytes(24).toString("hex");
const rootPassword = randomBytes(24).toString("hex");
const composeEnvironment = {
...process.env,
E2E_DB_NAME: databaseName,
E2E_DB_USER: user,
E2E_DB_PASS: password,
E2E_DB_ROOT_PASS: rootPassword,
};
const composeArgs = ["compose", "--project-name", projectName, "--file", composeFile];
try {
await run("docker", [...composeArgs, "up", "--detach", "--wait"], composeEnvironment);
const containerId = await run(
"docker",
[...composeArgs, "ps", "--quiet", "db"],
composeEnvironment
);
const portOutput = await run(
"docker",
[...composeArgs, "port", "db", "3306"],
composeEnvironment
);
const port = portOutput.match(/:(\d+)$/)?.[1];
if (!(containerId && port)) {
throw new Error("Compose did not return the E2E MariaDB container and port.");
}
const environment = {
DB_HOST: "127.0.0.1",
DB_PORT: port,
DB_NAME: databaseName,
DB_USER: user,
DB_PASS: password,
};
await run(
"docker",
[
"exec",
containerId,
"mariadb",
`--user=${user}`,
`--password=${password}`,
databaseName,
"--execute=SELECT 1",
],
composeEnvironment
);
return {
executionId: runId,
databaseName,
projectName,
containerId,
host: "127.0.0.1",
port: Number(port),
username: user,
password,
environment,
destroy: async () =>
run(
"docker",
[...composeArgs, "down", "--volumes", "--remove-orphans"],
composeEnvironment
).then(() => undefined),
};
} catch (error) {
await run(
"docker",
[...composeArgs, "down", "--volumes", "--remove-orphans"],
composeEnvironment
).catch(() => undefined);
throw error;
}
}