mirror of
https://github.com/nextcloud/all-in-one.git
synced 2026-09-22 11:10:22 +00:00
test(desec): end-to-end Playwright coverage against a local mock
- desec-mock.mjs: a dependency-free Node mock of the deSEC API endpoints the code uses, with the real status-code semantics (202 on account creation, 403 until verified then 200 + token, 201/409 on domain creation) plus /__control hooks to verify and reset state. - desec-register.spec.js / desec-existing.spec.js drive the real AIO UI through the register -> verify -> domain flow and the existing-account login flow; desec-helpers.js holds the shared login helper. Each scenario ends by setting a domain, so they run as separate CI steps with a re-seed in between. - seed-desec-mock-config.php points desec_api_base / desec_update_url at the mock (config key only, no env override, so production is unaffected) and seeds a known master password, since seeding configuration.json makes AIO consider itself already installed and /setup no longer shows a generated password. - Both Playwright workflows start the mock, mount ./community-containers so the caddy/dnsmasq definitions enabled by the flow are present, seed the config, and run the two deSEC specs with a re-seed between them. Co-Authored-By: szaimen <42591237+szaimen@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Simon L. <szaimen@e.mail.de>
This commit is contained in:
co-authored by
szaimen
Claude Opus 4.8
parent
0753c6c902
commit
4c8012adcd
@@ -0,0 +1,42 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { DESEC_MOCK_URL, logInToContainersPage } from './desec-helpers.js';
|
||||
|
||||
// Exercises the deSEC "I already have a verified account" login path: supplying a valid
|
||||
// password logs straight in and registers the domain in one step (no email-verification
|
||||
// round-trip). See desec-register.spec.js for the full setup notes.
|
||||
//
|
||||
// This flow also ends by registering a domain, so it is run as its own CI step with the
|
||||
// deSEC state re-seeded beforehand (configuration.json must have no domain set for the
|
||||
// registration UI to render).
|
||||
|
||||
test('deSEC existing-account login flow', async ({ page: setupPage }) => {
|
||||
test.setTimeout(5 * 60 * 1000);
|
||||
|
||||
// Pre-create a verified account in the mock so the password (login) path is exercised
|
||||
// directly, without the verification round-trip.
|
||||
await fetch(`${DESEC_MOCK_URL}/__control/reset`, { method: 'POST' });
|
||||
const email = `existing-${Math.floor(Math.random() * 2147483647)}@example.com`;
|
||||
const password = 'correct horse battery staple';
|
||||
await fetch(`${DESEC_MOCK_URL}/api/v1/auth/`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email, password }),
|
||||
});
|
||||
await fetch(`${DESEC_MOCK_URL}/__control/verify`, { method: 'POST' });
|
||||
|
||||
const containersPage = await logInToContainersPage(setupPage);
|
||||
|
||||
const slug = `aio-existing-${Math.floor(Math.random() * 2147483647)}`;
|
||||
|
||||
await containersPage.getByText("Don't have a domain? Get a free one from deSEC").click();
|
||||
await containersPage.locator('input[name="desec_email"]').fill(email);
|
||||
await containersPage.locator('input[name="desec_password"]').fill(password);
|
||||
await containersPage.locator('input[name="desec_slug"]').first().fill(slug);
|
||||
await containersPage.getByRole('button', { name: 'Register free domain via deSEC' }).click();
|
||||
|
||||
// Supplying a valid password logs straight in and registers the domain in one step.
|
||||
await expect(containersPage.getByRole('button', { name: 'Download and start containers' })).toBeVisible({ timeout: 60 * 1000 });
|
||||
await expect(
|
||||
containersPage.getByText("Don't have a domain? Get a free one from deSEC"),
|
||||
).toHaveCount(0);
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
// Shared helpers for the deSEC Playwright scenarios.
|
||||
//
|
||||
// The deSEC mock is wired up by seeding configuration.json (see seed-desec-mock-config.php),
|
||||
// which makes AIO consider itself already installed: /setup no longer renders the
|
||||
// initial-password page. The seed step therefore writes a known master password (AIO_TEST_PASSWORD)
|
||||
// that we log in with directly here instead of scraping it from /setup.
|
||||
|
||||
export const DESEC_MOCK_URL = process.env.DESEC_MOCK_URL ?? 'http://localhost:8090';
|
||||
|
||||
const AIO_PASSWORD = process.env.AIO_TEST_PASSWORD;
|
||||
|
||||
export async function logInToContainersPage(page) {
|
||||
if (!AIO_PASSWORD) {
|
||||
throw new Error('AIO_TEST_PASSWORD must be set to the master password seeded into configuration.json');
|
||||
}
|
||||
await page.goto('./');
|
||||
await page.locator('#master-password').click();
|
||||
await page.locator('#master-password').fill(AIO_PASSWORD);
|
||||
await page.getByRole('button', { name: 'Log in' }).click();
|
||||
await page.waitForURL('./containers');
|
||||
return page;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { DESEC_MOCK_URL, logInToContainersPage } from './desec-helpers.js';
|
||||
|
||||
// Drives the real AIO interface through the full deSEC "register a free domain" flow
|
||||
// against the local mock (php/tests/desec-mock.mjs). The mastercontainer must be started
|
||||
// with SKIP_DOMAIN_VALIDATION=false (so the deSEC registration form is rendered) and its
|
||||
// configuration.json must point desec_api_base / desec_update_url at the mock (see the
|
||||
// Playwright CI workflow). The mock's control endpoint is reachable from the test runner
|
||||
// on the host at DESEC_MOCK_URL (default http://localhost:8090).
|
||||
//
|
||||
// This flow ends by registering a domain, which persists in configuration.json. It is
|
||||
// therefore run as its own CI step; the deSEC state is reset (re-seeded) before the
|
||||
// separate existing-account flow runs.
|
||||
|
||||
test('deSEC register -> verify -> domain flow', async ({ page: setupPage }) => {
|
||||
test.setTimeout(5 * 60 * 1000);
|
||||
|
||||
// Start from a clean mock state so this test is independent of any other run.
|
||||
await fetch(`${DESEC_MOCK_URL}/__control/reset`, { method: 'POST' });
|
||||
|
||||
const containersPage = await logInToContainersPage(setupPage);
|
||||
|
||||
const email = `e2e-${Math.floor(Math.random() * 2147483647)}@example.com`;
|
||||
const slug = `aio-e2e-${Math.floor(Math.random() * 2147483647)}`;
|
||||
|
||||
// Open the deSEC registration section.
|
||||
await containersPage.getByText("Don't have a domain? Get a free one from deSEC").click();
|
||||
|
||||
// 1) Submit email only -> a new account is "created" (mock 202) and AIO asks the user
|
||||
// to verify their email. This is a normal (non-error) state transition: the page
|
||||
// reloads into the awaiting-verification UI, which renders inside <main> and shows
|
||||
// the dedicated re-submit button.
|
||||
await containersPage.locator('input[name="desec_email"]').fill(email);
|
||||
await containersPage.locator('input[name="desec_slug"]').first().fill(slug);
|
||||
await containersPage.getByRole('button', { name: 'Register free domain via deSEC' }).click();
|
||||
await expect(containersPage.getByRole('main')).toContainText('check your inbox', { timeout: 30 * 1000 });
|
||||
await expect(
|
||||
containersPage.getByRole('button', { name: 'I have verified my email – register domain' }),
|
||||
).toBeVisible();
|
||||
|
||||
// 2) Re-submit BEFORE verifying -> login still fails (mock 403) -> friendly hint shown.
|
||||
await containersPage.getByRole('button', { name: 'I have verified my email – register domain' }).click();
|
||||
// Same as above: the failure hint is a transient toast, not <main> content.
|
||||
await expect(containersPage.locator('.toast.error')).toContainText('Could not log in to deSEC', { timeout: 8 * 1000 });
|
||||
|
||||
// 3) Simulate the user clicking the verification link in their email.
|
||||
const verifyResponse = await fetch(`${DESEC_MOCK_URL}/__control/verify`, { method: 'POST' });
|
||||
expect(verifyResponse.status).toBe(200);
|
||||
|
||||
// 4) Re-submit after verification -> login succeeds, domain is registered, the deSEC
|
||||
// section disappears and the container-start UI appears.
|
||||
await containersPage.getByRole('button', { name: 'I have verified my email – register domain' }).click();
|
||||
await expect(containersPage.getByRole('button', { name: 'Download and start containers' })).toBeVisible({ timeout: 60 * 1000 });
|
||||
await expect(
|
||||
containersPage.getByText("Don't have a domain? Get a free one from deSEC"),
|
||||
).toHaveCount(0);
|
||||
});
|
||||
Reference in New Issue
Block a user