strona-czynna/tests/backend-fixture.ts

244 lines
5.7 KiB
TypeScript

import { test as base } from "@playwright/test";
import { spawn, spawnSync } from "child_process";
import { v4 as uuid } from "uuid";
import getPort from "get-port";
import * as path from "path";
import * as dgram from "dgram";
import * as http from "http";
import * as fs from "fs/promises";
import { MongoClient } from "mongodb";
import {
MONGO_PORT,
MONGO_HOST,
MAILCATCHER_API_PORT,
MAILCATCHER_SMTP_PORT,
} from "../src/back/config.js";
export class MailcatcherMessage {
id: number;
sender: string;
recipients: string[];
subject: string;
size: string;
created_at: string;
constructor({
id,
sender,
recipients,
subject,
size,
created_at,
}: {
id: number;
sender: string;
recipients: string[];
subject: string;
size: string;
created_at: string;
}) {
this.id = id;
this.sender = sender;
this.recipients = recipients;
this.subject = subject;
this.size = size;
this.created_at = created_at;
}
async getBody(format: "html" | "plain" | "source") {
return (await fetch(`${mailcatcher_url}/messages/${this.id}.${format}`)).text();
}
async getURLs(): Promise<string[]> {
const url_regex =
/https?:\/\/[a-z0-9.]+(:[0-9]+)\/[a-zA-Z0-9.\/!@#$%^&*()?\-=]+/g;
const body = await this.getBody("plain");
return Array.from(body.matchAll(url_regex)).map((e) => e[0]);
}
}
export type MailcatcherAPI = {
getLatestMessage: () => Promise<MailcatcherMessage>;
};
type TestFixtures = {
backend: { url: string; env: Record<string, string> };
setMarkdownValue: (field_name: string, value: string) => Promise<void>;
email: MailcatcherAPI;
};
const c8 = "./node_modules/c8/bin/c8.js";
const app_host = process.env.TESTS_RUN_IN_DOCKER
? "test" // the docker name
: "172.17.0.1"; // ip to access localhost from within docker (playwright always runs in docker)
const mailcatcher_url = `http://${app_host}:${MAILCATCHER_API_PORT}`;
export async function waitForHttpPort({
port,
host = app_host,
timeout = 5000 * 20,
interval = 200,
}: {
port: number;
host?: string;
timeout?: number;
interval?: number;
}): Promise<void> {
const start = Date.now();
return new Promise((resolve, reject) => {
const check = () => {
console.log("Making a request to", { host, port });
const req = http.get({ hostname: host, port, timeout: 1000 }, (res) => {
res.destroy();
resolve(); // Port is ready
});
req.on("error", () => {
if (Date.now() - start > timeout) {
reject(new Error(`Timeout waiting for HTTP port ${port}`));
} else {
setTimeout(check, interval);
}
});
};
check();
});
}
function triggerKillswitch(port: number) {
const client = dgram.createSocket("udp4");
const message = Buffer.from("Hello, UDP!");
const HOST = "127.0.0.1";
client.send(message, port, HOST, () => {
client.close();
});
}
async function dropMongoDB(mongo_db_name: string) {
const client = new MongoClient(`mongodb://${MONGO_HOST}:${MONGO_PORT}`);
try {
await client.connect();
const db = client.db(mongo_db_name);
const result = await db.dropDatabase();
console.log(`Database "${mongo_db_name}" dropped:`, result);
} catch (err) {
console.error("Error dropping database:", err);
} finally {
await client.close();
}
}
export const test = base.extend<TestFixtures>({
backend: async ({}, use, { file, titlePath }) => {
const port = await getPort();
const mongo_db_name = `test-app-${uuid()}`;
const killswitch_port = await getPort();
const coverage_temp_dir = path.join(
"coverage",
"tmp_split",
Date.now().toString()
);
await fs.mkdir(coverage_temp_dir, {
recursive: true,
});
const env = {
...process.env,
SEALIOUS_PORT: `${port}`,
SEALIOUS_MONGO_DB_NAME: mongo_db_name,
...(process.env.TESTS_RUN_IN_DOCKER
? { MEILISEARCH_HOST: `http://meilisearch:7700` }
: {}),
MAILCATCHER_HOST: app_host,
MAILCATCHER_SMTP_PORT: String(MAILCATCHER_SMTP_PORT),
SEALIOUS_KILLSWITCH_PORT: `${killswitch_port}`,
SEALIOUS_BASE_URL: `http://${app_host}:${port}`,
};
const backendProcess = spawn(
c8,
[
"--src",
"src",
"--reporter",
"none",
"--temp-directory",
coverage_temp_dir,
"--all",
"node",
"dist/back/index.js",
],
{
env,
stdio: "inherit",
}
);
const onEndPromise = new Promise<void>((resolve) => {
backendProcess.on("exit", async () => {
await dropMongoDB(mongo_db_name);
spawnSync(
c8,
[
"report",
"--temp-directory",
coverage_temp_dir,
"--reporter",
"lcovonly",
"--report-dir",
path.join("coverage", Date.now().toString()),
],
{ stdio: "inherit" }
);
resolve();
});
});
await waitForHttpPort({ port, timeout: 20000 });
await use({ url: `http://${app_host}:${port}`, env });
triggerKillswitch(killswitch_port);
await onEndPromise;
},
setMarkdownValue: ({ page }, use) => {
use(async (field_name: string, value: string) => {
for (let i = 1; i <= 5; i++) {
try {
await page.getByRole("button", { name: "Preview" }).click();
await page
.locator(`textarea[name="${field_name}"] ~ .CodeMirror`)
.first()
.click({ timeout: 1000 });
await page.getByRole("textbox").press("ControlOrMeta+a");
await page.getByRole("textbox").first().type("Hamster, a dentist");
break;
} catch (e) {
console.error(e);
continue;
}
}
});
},
email: ({}, use) => {
async function getLatestMessage() {
const all_messages = (await (
await fetch(`${mailcatcher_url}/messages`)
).json()) as Record<string, unknown>[];
return new MailcatcherMessage(all_messages.at(-1) as any);
}
async function getURLsFromLatestMessage() {}
return use({
getLatestMessage,
});
},
});
export { expect } from "@playwright/test";