mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
feat: integration tests
This commit is contained in:
169
integration/scripts/it.mjs
Normal file
169
integration/scripts/it.mjs
Normal file
@@ -0,0 +1,169 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Freight integration-suite launcher.
|
||||
*
|
||||
* node integration/scripts/it.mjs <up|test|down|logs> [vitest args...]
|
||||
*
|
||||
* Overlays docker-compose.it.yaml on docker-compose.e2e.yaml: same freight
|
||||
* stack, but the payment microservice is real and only the bank gateways are
|
||||
* stubbed. Web/Cypress containers are never started — this suite is HTTP only.
|
||||
*
|
||||
* Ports are fixed (and distinct from the Cypress e2e defaults) so both stacks
|
||||
* can be up at once; they are separate compose projects.
|
||||
*
|
||||
* No dependencies — plain Node spawning `docker compose` and `pnpm`.
|
||||
*/
|
||||
|
||||
import { execFileSync, spawnSync } from "node:child_process";
|
||||
import { generateKeyPairSync } from "node:crypto";
|
||||
import { existsSync } from "node:fs";
|
||||
import { dirname, join, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const itDir = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const repoRoot = resolve(itDir, "..");
|
||||
const composeBase = [
|
||||
"compose",
|
||||
"-f",
|
||||
join(repoRoot, "docker-compose.e2e.yaml"),
|
||||
"-f",
|
||||
join(repoRoot, "docker-compose.it.yaml"),
|
||||
];
|
||||
|
||||
/** Deliberately offset from the Cypress stack's defaults (3101/5533/9310…). */
|
||||
const PORTS = {
|
||||
E2E_API_PORT: 3111,
|
||||
E2E_DB_PORT: 5543,
|
||||
E2E_MINIO_PORT: 9320,
|
||||
E2E_MINIO_CONSOLE_PORT: 9321,
|
||||
// Unused here (no web containers) but referenced by the base file's build args.
|
||||
E2E_PORTAL_PORT: 5393,
|
||||
E2E_BACKOFFICE_PORT: 5394,
|
||||
IT_PAYMENT_PORT: 3113,
|
||||
IT_GATEWAY_PORT: 4600,
|
||||
IT_RABBIT_PORT: 5772,
|
||||
IT_RABBIT_UI_PORT: 15772,
|
||||
};
|
||||
|
||||
/** Everything the suite needs up — web + cypress are deliberately absent. */
|
||||
const SERVICES = [
|
||||
"postgres-freight-e2e",
|
||||
"minio-e2e",
|
||||
"minio-init-e2e",
|
||||
"freight-migration-e2e",
|
||||
"fayda-mock-e2e",
|
||||
"etrade-mock-e2e",
|
||||
// Still a base-stack dependency of freight-api-e2e (reconcile-before-expire
|
||||
// has its own client); cheap to run alongside the real payment API.
|
||||
"payment-mock-e2e",
|
||||
"gateway-mock-it",
|
||||
"rabbitmq-it",
|
||||
"payment-api-it",
|
||||
"freight-api-e2e",
|
||||
];
|
||||
|
||||
const RUNNING = SERVICES.filter(
|
||||
(s) => !["minio-init-e2e", "freight-migration-e2e"].includes(s),
|
||||
);
|
||||
|
||||
function fail(msg) {
|
||||
console.error(`\nit: ${msg}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function preflight() {
|
||||
try {
|
||||
execFileSync("docker", ["info"], { stdio: "ignore" });
|
||||
} catch {
|
||||
fail("docker is not running (or not installed) — start Docker and retry.");
|
||||
}
|
||||
if (!existsSync(join(repoRoot, ".npmrc"))) {
|
||||
fail(".npmrc missing at repo root — image builds need GitHub Packages auth for @tria-plc.");
|
||||
}
|
||||
}
|
||||
|
||||
/** Throwaway RSA PEM — Telebirr PSS-signs every request object; the mock never
|
||||
* verifies it, but the provider refuses to build a request without a real key. */
|
||||
function fakeTelebirrPrivateKey() {
|
||||
const { privateKey } = generateKeyPairSync("rsa", { modulusLength: 2048 });
|
||||
return privateKey.export({ type: "pkcs8", format: "pem" }).toString();
|
||||
}
|
||||
|
||||
/** Throwaway RSA JWK for FAYDA_PRIVATE_KEY_BASE64 (see e2e.mjs — same reason). */
|
||||
function fakeFaydaPrivateKeyBase64() {
|
||||
const { privateKey } = generateKeyPairSync("rsa", { modulusLength: 2048 });
|
||||
const jwk = privateKey.export({ format: "jwk" });
|
||||
Object.assign(jwk, { kty: "RSA", use: "sig", alg: "RS256", kid: "it-fayda-mock" });
|
||||
return Buffer.from(JSON.stringify(jwk)).toString("base64");
|
||||
}
|
||||
|
||||
const env = {
|
||||
...process.env,
|
||||
...Object.fromEntries(Object.entries(PORTS).map(([k, v]) => [k, String(v)])),
|
||||
IT_API_URL: `http://localhost:${PORTS.E2E_API_PORT}`,
|
||||
IT_PAYMENT_URL: `http://localhost:${PORTS.IT_PAYMENT_PORT}`,
|
||||
IT_GATEWAY_URL: `http://localhost:${PORTS.IT_GATEWAY_PORT}`,
|
||||
IT_DB_URL: `postgres://edr_e2e:edr_e2e@localhost:${PORTS.E2E_DB_PORT}/edr_freight_e2e`,
|
||||
FAYDA_PRIVATE_KEY_BASE64:
|
||||
process.env.FAYDA_PRIVATE_KEY_BASE64 ?? fakeFaydaPrivateKeyBase64(),
|
||||
IT_TELEBIRR_PRIVATE_KEY:
|
||||
process.env.IT_TELEBIRR_PRIVATE_KEY ?? fakeTelebirrPrivateKey(),
|
||||
};
|
||||
|
||||
function compose(args) {
|
||||
const { status } = spawnSync("docker", [...composeBase, ...args], { stdio: "inherit", env });
|
||||
return status ?? 1;
|
||||
}
|
||||
|
||||
function stackRunning() {
|
||||
try {
|
||||
const out = execFileSync("docker", [...composeBase, "ps", "--services", "--status", "running"], {
|
||||
encoding: "utf8",
|
||||
env,
|
||||
stdio: ["ignore", "pipe", "ignore"],
|
||||
});
|
||||
const running = new Set(out.split("\n").filter(Boolean));
|
||||
return RUNNING.every((s) => running.has(s));
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function up() {
|
||||
preflight();
|
||||
console.log(
|
||||
`it: starting stack — freight :${PORTS.E2E_API_PORT} payment :${PORTS.IT_PAYMENT_PORT} ` +
|
||||
`gateway :${PORTS.IT_GATEWAY_PORT} db :${PORTS.E2E_DB_PORT}`,
|
||||
);
|
||||
if (compose(["up", "-d", "--build", "--wait", ...SERVICES]) !== 0) {
|
||||
fail(
|
||||
"stack failed to become healthy. Inspect with:\n" +
|
||||
" node integration/scripts/it.mjs logs payment-api-it",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const [cmd, ...extra] = process.argv.slice(2);
|
||||
|
||||
switch (cmd) {
|
||||
case "up":
|
||||
up();
|
||||
break;
|
||||
case "test": {
|
||||
if (!stackRunning()) up();
|
||||
const { status } = spawnSync(
|
||||
"pnpm",
|
||||
["--filter", "@edr/freight-integration", "run", "test", ...extra],
|
||||
{ cwd: repoRoot, stdio: "inherit", env },
|
||||
);
|
||||
process.exit(status ?? 1);
|
||||
}
|
||||
case "logs":
|
||||
process.exit(compose(["logs", "--tail", "200", ...extra]));
|
||||
break;
|
||||
case "down":
|
||||
process.exit(compose(["down", "-v", "--remove-orphans"]));
|
||||
break;
|
||||
default:
|
||||
fail(`unknown command "${cmd ?? ""}" — use up | test | logs | down`);
|
||||
}
|
||||
Reference in New Issue
Block a user