The virtual-authenticator setup and registerUser were copy-pasted into six spec files and had drifted: the auth spec's copy lost the final URL assertion, and the settings spec's variant skipped hydration and the Terms checkbox entirely. Seed-route boilerplate and hardcoded localhost URLs were repeated across three more files. - helpers/auth.ts: setup/removeVirtualAuthenticator, submitRegistration (no outcome assertion, for expected-failure attempts), registerUser (asserts the signed-in redirect), registerFreshUser, logout - helpers/journal.ts: JOURNAL/PLANNER base URLs, seedRoute, routeHasGeom, seedKomootConnection - nine spec files now import the helpers instead of re-deriving them Found while consolidating: settings.test.ts, explore.test.ts, and social.test.ts are not matched by any playwright project and have never run — which is how the settings spec's broken register helper survived. Registering them (and fixing whatever has rotted) is a follow-up; the config now carries a warning so the trap is at least documented. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
153 lines
5.8 KiB
TypeScript
153 lines
5.8 KiB
TypeScript
import { test, expect } from "./fixtures/test";
|
|
import { JOURNAL, seedKomootConnection, seedRoute } from "./helpers/journal";
|
|
|
|
// Fixed test data — the e2e seed endpoint creates a stable user + Komoot connection
|
|
const KOMOOT_USER_ID = "99999999999";
|
|
|
|
const SAMPLE_GPX = `<?xml version="1.0" encoding="UTF-8"?>
|
|
<gpx version="1.1" creator="komoot" xmlns="http://www.topografix.com/GPX/1/1">
|
|
<trk>
|
|
<name>Morning Hike</name>
|
|
<trkseg>
|
|
<trkpt lat="48.137" lon="11.575"><ele>520</ele></trkpt>
|
|
<trkpt lat="48.138" lon="11.576"><ele>525</ele></trkpt>
|
|
<trkpt lat="48.139" lon="11.577"><ele>530</ele></trkpt>
|
|
</trkseg>
|
|
</trk>
|
|
</gpx>`;
|
|
|
|
const MOCK_TOURS_RESPONSE = {
|
|
_embedded: {
|
|
tours: [
|
|
{
|
|
id: 111111111,
|
|
name: "Morning Hike",
|
|
type: "hike",
|
|
date: "2024-06-01T08:00:00.000Z",
|
|
distance: 12500,
|
|
duration: 7200,
|
|
elevation_up: 350,
|
|
elevation_down: 350,
|
|
},
|
|
],
|
|
},
|
|
page: { totalPages: 1, number: 0 },
|
|
};
|
|
|
|
test.describe("Komoot connection page", () => {
|
|
test("unauthenticated user is redirected to login", async ({ page }) => {
|
|
await page.goto("/settings/connections/komoot");
|
|
await expect(page).toHaveURL(/\/auth\/login/, { timeout: 5000 });
|
|
});
|
|
|
|
test("shows mode badge for public connection", async ({ page, request }) => {
|
|
await seedKomootConnection(request, "public");
|
|
|
|
// Use the session cookie returned from the seed endpoint
|
|
const resp = await request.post(`${JOURNAL}/api/e2e/komoot`, { data: { mode: "public" } });
|
|
const setCookie = resp.headers()["set-cookie"];
|
|
await page.context().addCookies([
|
|
{
|
|
name: "__session",
|
|
value: setCookie.match(/__session=([^;]+)/)?.[1] ?? "",
|
|
domain: "localhost",
|
|
path: "/",
|
|
},
|
|
]);
|
|
|
|
await page.goto("/settings/connections/komoot");
|
|
await expect(page.getByText(/Public tours only/)).toBeVisible({ timeout: 5000 });
|
|
});
|
|
});
|
|
|
|
test.describe("Komoot public verify flow", () => {
|
|
test.setTimeout(60000);
|
|
|
|
test("verify endpoint rejects when bio does not match", async ({ request }) => {
|
|
// Mock Komoot profile returns bio without the URL
|
|
const resp = await request.post(`${JOURNAL}/api/sync/komoot/verify`, {
|
|
headers: { "Content-Type": "application/json", Cookie: "__session=invalid" },
|
|
data: { komootProfileUrl: `https://www.komoot.com/user/${KOMOOT_USER_ID}` },
|
|
});
|
|
// Without a valid session it should redirect/401
|
|
expect([302, 401, 302].includes(resp.status()) || resp.url().includes("/auth/login")).toBeTruthy();
|
|
});
|
|
|
|
test("verify endpoint returns 400 for invalid URL", async ({ request }) => {
|
|
// Seed a real session first
|
|
const seedResp = await request.post(`${JOURNAL}/api/e2e/komoot`, { data: { mode: "public" } });
|
|
const cookie = seedResp.headers()["set-cookie"];
|
|
|
|
const resp = await request.post(`${JOURNAL}/api/sync/komoot/verify`, {
|
|
headers: { "Content-Type": "application/json", Cookie: cookie },
|
|
data: { komootProfileUrl: "not-a-valid-url" },
|
|
});
|
|
expect(resp.status()).toBe(400);
|
|
const body = await resp.json() as { error: string };
|
|
expect(body.error).toBe("invalid_url");
|
|
});
|
|
});
|
|
|
|
test.describe("Komoot import page", () => {
|
|
test.setTimeout(60000);
|
|
|
|
test("redirects to connect page when not connected", async ({ page, request }) => {
|
|
// We only need the session — navigate to import page unauthenticated redirects to login
|
|
await seedRoute(request);
|
|
await page.goto("/sync/import/komoot");
|
|
await expect(page).toHaveURL(/\/auth\/login|\/settings\/connections\/komoot/, { timeout: 5000 });
|
|
});
|
|
|
|
test("shows tour list for connected user", async ({ page, request }) => {
|
|
// Seed Komoot public connection
|
|
const seedResp = await request.post(`${JOURNAL}/api/e2e/komoot`, { data: { mode: "public" } });
|
|
const setCookie = seedResp.headers()["set-cookie"];
|
|
await page.context().addCookies([
|
|
{
|
|
name: "__session",
|
|
value: setCookie.match(/__session=([^;]+)/)?.[1] ?? "",
|
|
domain: "localhost",
|
|
path: "/",
|
|
},
|
|
]);
|
|
|
|
// Mock Komoot tours API (server calls it, but via page.route for server-sent-from-browser paths)
|
|
// Actually mock at API level — server fetches Komoot, so we intercept browser navigation results
|
|
await page.route(
|
|
(url) => url.href.includes(`api.komoot.de/v007/users/${KOMOOT_USER_ID}/tours`),
|
|
(route) =>
|
|
route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify(MOCK_TOURS_RESPONSE),
|
|
}),
|
|
);
|
|
|
|
await page.goto("/sync/import/komoot");
|
|
// The page will try to load tours server-side; if Komoot is unreachable it shows empty list
|
|
// We just verify the page loads (not a redirect)
|
|
await expect(page).not.toHaveURL(/\/auth\/login/, { timeout: 5000 });
|
|
await expect(page).not.toHaveURL(/\/settings\/connections\/komoot/, { timeout: 5000 });
|
|
});
|
|
|
|
test("import action marks tour as imported", async ({ page, request }) => {
|
|
// Seed Komoot public connection and get a browser session
|
|
const seedResp = await request.post(`${JOURNAL}/api/e2e/komoot`, { data: { mode: "public" } });
|
|
const setCookie = seedResp.headers()["set-cookie"];
|
|
await page.context().addCookies([
|
|
{
|
|
name: "__session",
|
|
value: setCookie.match(/__session=([^;]+)/)?.[1] ?? "",
|
|
domain: "localhost",
|
|
path: "/",
|
|
},
|
|
]);
|
|
|
|
// Load the import page — with resilient importer it shows "No workouts found"
|
|
await page.goto("/sync/import/komoot");
|
|
await expect(page).not.toHaveURL(/\/auth\/login/, { timeout: 5000 });
|
|
await expect(page).not.toHaveURL(/\/settings\/connections\/komoot/, { timeout: 5000 });
|
|
// Page loads successfully (shows import UI, not an error page)
|
|
await expect(page.locator("h1")).toBeVisible({ timeout: 5000 });
|
|
});
|
|
});
|