Files
computer-playbook/roles/web-app-stalwart/files/playwright/test-onion-mailflow.js

63 lines
3.2 KiB
JavaScript

const { test, expect } = require("@playwright/test");
const { safeSkipUnlessEnabled, gotoOnion, recordVideoOptions } = require("./personas");
const { roundcubeSsoLogin, roundcubeLogout } = require("./webmail");
const { webmailBaseUrl, biberUsername, biberPassword } = require("./env");
const { resolveTimeout, isSplitRealmOidc } = require("./timeouts");
// Outbound .onion delivery from the user's side: biber addresses a recipient on a
// foreign onion and the message leaves as queued mail instead of bouncing.
//
// The lab has no external onion mail peer, so this asserts the routing decision
// rather than a remote receipt: with MtaRoute + MtaOutboundStrategy in place the
// recipient is accepted and queued to the tor-smtp relay, whereas an unrouted
// .onion domain has no MX and returns a delivery failure to the sender. The
// recipient domain is deliberately not a local domain — is_local_domain wins over
// the .onion match in the strategy, so a self-addressed onion would route 'local'
// and never reach the gateway.
const FOREIGN_ONION = "recipient@nexusprobe7xk3mjqzvhbnd4rlyugc2pfe6sotai5w.onion";
test("biber: a .onion recipient is accepted and routed through the Tor gateway", async ({ browser }, testInfo) => {
test.skip(isSplitRealmOidc(), "clearnet app with an onion OIDC issuer: unreachable from one browser");
safeSkipUnlessEnabled("tor");
safeSkipUnlessEnabled("sso");
expect(webmailBaseUrl, "WEBMAIL_BASE_URL must be set").toBeTruthy();
expect(biberPassword, "BIBER_PASSWORD must be set").toBeTruthy();
const testSubject = `Playwright onion route ${Date.now()}`;
const proxyServer = process.env.PLAYWRIGHT_PROXY;
const context = await browser.newContext({
ignoreHTTPSErrors: true,
...(proxyServer ? { proxy: { server: proxyServer } } : {}),
...recordVideoOptions(testInfo),
});
try {
const page = await context.newPage();
await roundcubeSsoLogin(page, biberUsername, biberPassword);
await gotoOnion(page, `${webmailBaseUrl}/?_task=mail&_action=compose`);
await page.waitForLoadState("networkidle", { timeout: resolveTimeout(15_000) }).catch(() => {});
await page.locator("#_to, input[name='_to']").first().fill(FOREIGN_ONION);
await page.locator("#compose-subject, input[name='_subject']").first().fill(testSubject);
await page.locator("#composebody, textarea[name='_message'], [contenteditable='true']").first()
.fill("Automated Playwright probe for the .onion outbound route.");
await page.locator(".formbuttons .send, button.send, a.send").first().click();
const sendError = page.locator("#messagestack .error, .toast .error, .toast-error").first();
if (await sendError.isVisible().catch(() => false)) {
throw new Error(`Stalwart refused the .onion recipient: ${await sendError.textContent()}`);
}
await gotoOnion(page, `${webmailBaseUrl}/?_task=mail&_mbox=INBOX`);
await page.waitForTimeout(resolveTimeout(10_000));
await gotoOnion(page, `${webmailBaseUrl}/?_task=mail&_mbox=INBOX`);
await expect(
page.locator("#messagelist").getByText(/undelivered|delivery status|delivery failure|mailer-daemon/i).first()
).toBeHidden({ timeout: resolveTimeout(15_000) });
await roundcubeLogout(page);
} finally {
await context.close().catch(() => {});
}
});