Files
pleno-vue/tests/e2e/userAuth.spec.ts
T

525 lines
18 KiB
TypeScript

import { expect, test } from "@playwright/test";
import { mockApi } from "./support/network.js";
const PASSWORD_RESET_MESSAGE = "If the customer exists, a password reset email has been sent.";
const INVALID_CREDENTIALS = "Invalid credentials";
const INVALID_RESET_TOKEN = "Invalid or expired token";
const SUBUSER_PASSWORD_POLICY_MESSAGE =
"Password must contain at least one uppercase letter, one lowercase letter, and one number.";
const USER_TOKEN = "mock-user-session-token";
const SUBUSER_TOKEN = "mock-subuser-session-token";
const OPERATOR_TOKEN = "mock-operator-session-token";
const AUTH_FORM_TIMEOUT = 15_000;
function json(route, body, status = 200) {
return route.fulfill({
status,
contentType: "application/json",
body: JSON.stringify(body),
});
}
function createSessionData(overrides = {}) {
return {
id: 1,
customer_number: 12345679,
group_id: 1,
email: "user@example.com",
phone: {
number: "12345678",
country_code: 45,
},
notifications: {
wash_certificate_email: null,
email_notifications_enabled: true,
sms_notifications_enabled: false,
},
created_at: "2026-01-01T00:00:00.000Z",
updated_at: "2026-01-01T00:00:00.000Z",
display_name: "E2E User",
permissions: ["user"],
economic_customer: [],
...overrides,
};
}
function isBenignNavigationError(error) {
const message = error instanceof Error ? error.message : String(error);
return (
message.includes("interrupted by another navigation") ||
message.includes("ERR_ABORTED") ||
message.includes("Frame load interrupted")
);
}
async function readStoredToken(page) {
try {
return await page.evaluate(() => window.localStorage.getItem("token"));
} catch {
return null;
}
}
async function settleAuthenticatedNavigation(page, token, targetPath, targetUrl) {
await expect.poll(() => readStoredToken(page)).toBe(token);
if (!targetUrl.test(page.url())) {
try {
await page.goto(targetPath, { waitUntil: "domcontentloaded" });
} catch (error) {
if (!isBenignNavigationError(error)) {
throw error;
}
}
}
await expect.poll(() => page.url(), { timeout: 15_000 }).toMatch(targetUrl);
}
async function openAuthPage(page, path, selector) {
const field = page.locator(selector);
await page.goto(path);
for (let attempt = 0; attempt < 2; attempt++) {
if (await field.isVisible().catch(() => false)) {
return field;
}
if (attempt === 0) {
try {
await page.goto(path, { waitUntil: "domcontentloaded" });
} catch (error) {
if (!isBenignNavigationError(error)) {
throw error;
}
}
}
}
await expect(field).toBeVisible({ timeout: AUTH_FORM_TIMEOUT });
return field;
}
async function installSessionRoute(page, { token, sessionData }) {
await page.route("**/auth/session", async (route) => {
const authorization = route.request().headers().authorization;
if (authorization !== `Bearer ${token}`) {
await json(route, { message: "Unauthenticated" }, 401);
return;
}
await json(route, {
data: createSessionData(sessionData),
});
});
}
async function installUserLoginRoute(
page,
{ outcome = "success", token = USER_TOKEN, errorMessage = INVALID_CREDENTIALS } = {}
) {
await page.route("**/auth/login", async (route) => {
if (outcome === "error") {
await json(route, { data: { message: errorMessage } }, 401);
return;
}
await json(route, {
data: {
token,
},
});
});
}
async function installSubuserLoginRoute(
page,
{ outcome = "success", token = SUBUSER_TOKEN, errorMessage = "Subuser not found" } = {}
) {
await page.route("**/subusers/auth/password", async (route) => {
if (outcome === "error") {
await json(route, { data: { message: errorMessage } }, 401);
return;
}
await json(route, {
data: {
session: token,
},
});
});
}
async function installOperatorLoginRoute(
page,
{ outcome = "success", token = OPERATOR_TOKEN, errorMessage = INVALID_CREDENTIALS } = {}
) {
await page.route("**/auth/employee/login", async (route) => {
if (outcome === "error") {
await json(route, { data: { message: errorMessage } }, 401);
return;
}
await json(route, {
data: {
token,
},
});
});
}
async function installPasswordResetRequestRoute(page, message = PASSWORD_RESET_MESSAGE) {
await page.route("**/auth/password-reset/request", async (route) => {
await json(route, {
data: {
message,
},
});
});
}
async function installPasswordResetValidationRoute(page, { invalidToken = "invalidtoken" } = {}) {
await page.route("**/auth/password-reset/validate*", async (route) => {
const url = new URL(route.request().url());
if (url.searchParams.get("token") === invalidToken) {
await json(route, { data: { message: INVALID_RESET_TOKEN } }, 422);
return;
}
await json(route, { data: { valid: true } });
});
}
async function installCustomerRegistrationRoute(page, { status = 422, message }) {
await page.route("**/auth/register/cvr", async (route) => {
if (status >= 400) {
await json(route, { data: { message } }, status);
return;
}
await json(route, { data: { success: true } }, status);
});
}
async function installDriverRegistrationRoute(page, { status = 422, message }) {
await page.route(/\/subusers(?:\?.*)?$/, async (route) => {
if (route.request().method() !== "POST") {
await route.fallback();
return;
}
if (status >= 400) {
await json(route, { data: { message } }, status);
return;
}
await json(route, { data: { success: true } }, status);
});
}
async function preparePublicAuthFlow(page) {
await mockApi(page, { authenticated: false });
}
async function loginAsMockUser(page) {
await preparePublicAuthFlow(page);
await installSessionRoute(page, { token: USER_TOKEN, sessionData: { permissions: ["user"] } });
await installUserLoginRoute(page);
await openAuthPage(page, "/login", 'input[name="customer_number"]');
await page.fill('input[name="customer_number"]', "12345679");
await page.fill('input[name="password"]', "5679");
await page.click("#login-button");
await settleAuthenticatedNavigation(page, USER_TOKEN, "/user", /\/user(?:\/)?(?:[?#].*)?$/);
}
async function loginAsMockSubuserByPhone(page) {
await preparePublicAuthFlow(page);
await installSessionRoute(page, {
token: SUBUSER_TOKEN,
sessionData: {
permissions: ["user"],
display_name: "E2E Driver",
username: "driver-user",
name: "E2E Driver",
},
});
await installSubuserLoginRoute(page);
await openAuthPage(page, "/login/driver", 'input[name="phone_country_code"]');
await page.fill('input[name="phone_country_code"]', "45");
await page.fill('input[name="phone"]', "42331128");
await page.fill('input[name="password"]', "Test1234");
await page.click("#subuser-login-button");
await settleAuthenticatedNavigation(page, SUBUSER_TOKEN, "/user", /\/user(?:\/)?(?:[?#].*)?$/);
}
async function loginAsMockSubuserByUsername(page) {
await preparePublicAuthFlow(page);
await installSessionRoute(page, {
token: SUBUSER_TOKEN,
sessionData: {
permissions: ["user"],
display_name: "E2E Driver",
username: "testsubuser",
name: "E2E Driver",
},
});
await installSubuserLoginRoute(page);
await openAuthPage(page, "/login/driver", 'button[id="subuser_login_method_username_button"]');
await page.click("#subuser_login_method_username_button");
await expect(page.locator('input[name="username"]')).toBeVisible({ timeout: AUTH_FORM_TIMEOUT });
await page.fill('input[name="username"]', "testsubuser");
await page.fill('input[name="password"]', "Test1234");
await page.click("#subuser-login-button");
await settleAuthenticatedNavigation(page, SUBUSER_TOKEN, "/user", /\/user(?:\/)?(?:[?#].*)?$/);
}
async function loginAsMockOperator(page) {
await preparePublicAuthFlow(page);
await installSessionRoute(page, {
token: OPERATOR_TOKEN,
sessionData: {
permissions: ["admin"],
display_name: "E2E Operator",
},
});
await installOperatorLoginRoute(page);
await openAuthPage(page, "/admin/login", 'input[name="user_id"]');
await page.fill('input[name="user_id"]', "11");
await page.fill('input[name="password"]', "aef18KHAPGiu90");
await page.click("#operator_login_button");
await settleAuthenticatedNavigation(page, OPERATOR_TOKEN, "/admin", /\/admin(?:\/)?(?:[?#].*)?$/);
}
test("[AUTH][User][Customer number] should login successfully", async ({ page }) => {
await loginAsMockUser(page);
});
test("[AUTH][User][Customer number] should fail to login using invalid password", async ({ page }) => {
await preparePublicAuthFlow(page);
await installUserLoginRoute(page, { outcome: "error" });
await openAuthPage(page, "/login", 'input[name="customer_number"]');
await page.fill('input[name="customer_number"]', "12345679");
await page.fill('input[name="password"]', "invalidpassword");
await page.click("#login-button");
await expect(page.locator("#user_auth_alert_error")).toHaveText(INVALID_CREDENTIALS);
});
test("[AUTH][User][Customer number] should fail to login using invalid customer number", async ({ page }) => {
await preparePublicAuthFlow(page);
await installUserLoginRoute(page, { outcome: "error" });
await openAuthPage(page, "/login", 'input[name="customer_number"]');
await page.fill('input[name="customer_number"]', "99999999999");
await page.fill('input[name="password"]', "5679");
await page.click("#login-button");
await expect(page.locator("#user_auth_alert_error")).toHaveText(INVALID_CREDENTIALS);
});
test("[AUTH][User][Customer number] should request password reset successfully", async ({ page }) => {
await preparePublicAuthFlow(page);
await installPasswordResetRequestRoute(page);
await openAuthPage(page, "/login", 'input[name="customer_number"]');
await page.click("#forgot-password-button");
await expect(page).toHaveURL("/auth/password-reset");
await page.fill('input[name="customer_number"]', "12345679");
await page.click("#password-reset-button");
await expect(page.locator("#password_reset_alert_success")).toHaveText(PASSWORD_RESET_MESSAGE);
});
test("[AUTH][User][Customer number] should not show failure to request password reset using invalid customer number", async ({
page,
}) => {
await preparePublicAuthFlow(page);
await installPasswordResetRequestRoute(page);
await openAuthPage(page, "/login", 'input[name="customer_number"]');
await page.click("#forgot-password-button");
await expect(page).toHaveURL("/auth/password-reset");
await page.fill('input[name="customer_number"]', "99999999999");
await page.click("#password-reset-button");
await expect(page.locator("#password_reset_alert_success")).toHaveText(PASSWORD_RESET_MESSAGE);
});
test("[AUTH][User][Customer number] should fail to reset password using invalid token", async ({ page }) => {
await preparePublicAuthFlow(page);
await installPasswordResetValidationRoute(page);
await page.goto("/auth/password-reset/invalidtoken");
await expect(page.locator("#password_reset_alert_error")).toHaveText(INVALID_RESET_TOKEN);
});
test("[AUTH][User][Customer number] should fail to register already existing cvr", async ({ page }) => {
await preparePublicAuthFlow(page);
await installCustomerRegistrationRoute(page, {
message:
"CVR already registered under customer number 42331128. The submitted phone number must match the customer id. Manual cleanup or reassignment is required before retrying.",
});
await page.goto("/customer-creation");
await page.fill("#customer_cvr", "41004355");
await page.fill("#email", "2jepp9350@gmail.com");
await page.fill("#customer_phone", "42331129");
await page.click("#customer-register-button");
await expect(page.locator("#customer_creation_alert_error")).toHaveText(
"CVR already registered under customer number 42331128. The submitted phone number must match the customer id. Manual cleanup or reassignment is required before retrying."
);
});
test("[AUTH][User][Customer number] should fail to register already existing company phone number", async ({
page,
}) => {
await preparePublicAuthFlow(page);
await installCustomerRegistrationRoute(page, {
message: "Company phone number already registered",
});
await page.goto("/customer-creation");
await page.fill("#customer_cvr", "43423010");
await page.fill("#email", "2jepp9350@gmail.com");
await page.fill("#customer_phone", "42331128");
await page.click("#customer-register-button");
await expect(page.locator("#customer_creation_alert_error")).toHaveText("Company phone number already registered");
});
test("[AUTH][Subuser][Phone number] should login successfully", async ({ page }) => {
await loginAsMockSubuserByPhone(page);
});
test("[AUTH][Subuser][Phone number] should fail to login using invalid phone number", async ({ page }) => {
await preparePublicAuthFlow(page);
await installSubuserLoginRoute(page, { outcome: "error", errorMessage: "Subuser not found" });
await page.goto("/login/driver");
await page.fill('input[name="phone_country_code"]', "45");
await page.fill('input[name="phone"]', "123456789");
await page.fill('input[name="password"]', "Test1234");
await page.click("#subuser-login-button");
await expect(page.locator("#subuser_auth_alert_error")).toHaveText("Subuser not found");
});
test("[AUTH][Subuser][Phone number] should fail to login using invalid password", async ({ page }) => {
await preparePublicAuthFlow(page);
await installSubuserLoginRoute(page, { outcome: "error", errorMessage: "Invalid password" });
await page.goto("/login/driver");
await page.fill('input[name="phone_country_code"]', "45");
await page.fill('input[name="phone"]', "42331128");
await page.fill('input[name="password"]', "Wrong1234");
await page.click("#subuser-login-button");
await expect(page.locator("#subuser_auth_alert_error")).toHaveText("Invalid password");
});
test("[AUTH][Subuser][Phone number] should reject passwords that fail the registration policy", async ({ page }) => {
await preparePublicAuthFlow(page);
await page.goto("/login/driver");
await page.fill('input[name="phone_country_code"]', "45");
await page.fill('input[name="phone"]', "42331128");
await page.fill('input[name="password"]', "invalidpassword");
await page.click("#subuser-login-button");
await expect(page.locator("#subuser_auth_alert_error")).toHaveText(SUBUSER_PASSWORD_POLICY_MESSAGE);
});
test("[AUTH][Subuser][Username] should login successfully", async ({ page }) => {
await loginAsMockSubuserByUsername(page);
});
test("[AUTH][Subuser][Username] should fail to login using invalid username", async ({ page }) => {
await preparePublicAuthFlow(page);
await installSubuserLoginRoute(page, { outcome: "error", errorMessage: "Subuser not found" });
await page.goto("/login/driver");
await page.click("#subuser_login_method_username_button");
await page.fill('input[name="username"]', "invalidusername");
await page.fill('input[name="password"]', "Test1234");
await page.click("#subuser-login-button");
await expect(page.locator("#subuser_auth_alert_error")).toHaveText("Subuser not found");
});
test("[AUTH][Subuser][Username] should fail to login using invalid password", async ({ page }) => {
await preparePublicAuthFlow(page);
await installSubuserLoginRoute(page, { outcome: "error", errorMessage: "Invalid password" });
await page.goto("/login/driver");
await page.click("#subuser_login_method_username_button");
await page.fill('input[name="username"]', "testsubuser");
await page.fill('input[name="password"]', "Wrong1234");
await page.click("#subuser-login-button");
await expect(page.locator("#subuser_auth_alert_error")).toHaveText("Invalid password");
});
test("[AUTH][Subuser][Username] should fail to register with already existing phone number", async ({ page }) => {
await preparePublicAuthFlow(page);
await installDriverRegistrationRoute(page, {
message: "Account already exists with this phone number",
});
await page.goto("/customer-creation");
await page.fill("#driver_cvr", "41004355");
await page.fill("#driver_phone", "42331128");
await page.click("#driver-register-button");
await expect(page.locator("#driver_creation_alert_error")).toHaveText(
"Account already exists with this phone number"
);
});
test("[AUTH][Subuser][Username] should fail to register with invalid (7-char) CVR number", async ({ page }) => {
await preparePublicAuthFlow(page);
await installDriverRegistrationRoute(page, {
message: "Parameter cvr must be at least 8 characters long",
});
await page.goto("/customer-creation");
await page.fill("#driver_cvr", "1234567");
await page.fill("#driver_phone", "42331128");
await page.click("#driver-register-button");
await expect(page.locator("#driver_creation_alert_error")).toHaveText(
"Parameter cvr must be at least 8 characters long"
);
});
test("[AUTH][Subuser][Username] should fail to register with invalid (9-char) CVR number", async ({ page }) => {
await preparePublicAuthFlow(page);
await installDriverRegistrationRoute(page, {
message: "Parameter cvr must be at most 8 characters long",
});
await page.goto("/customer-creation");
await page.fill("#driver_cvr", "1234567890");
await page.fill("#driver_phone", "42331128");
await page.click("#driver-register-button");
await expect(page.locator("#driver_creation_alert_error")).toHaveText(
"Parameter cvr must be at most 8 characters long"
);
});
test("[AUTH][Operator][User ID] should login successfully", async ({ page }) => {
await loginAsMockOperator(page);
});
test("[AUTH][Operator][User ID] should fail to login using invalid user ID", async ({ page }) => {
await preparePublicAuthFlow(page);
await installOperatorLoginRoute(page, { outcome: "error" });
await page.goto("/admin/login");
await page.fill('input[name="user_id"]', "99999999999");
await page.fill('input[name="password"]', "aef18KHAPGiu90");
await page.click("#operator_login_button");
await expect(page.locator("#user_auth_alert_error")).toHaveText(INVALID_CREDENTIALS);
});
test("[AUTH][Operator][User ID] should fail to login using invalid password", async ({ page }) => {
await preparePublicAuthFlow(page);
await installOperatorLoginRoute(page, { outcome: "error" });
await page.goto("/admin/login");
await page.fill('input[name="user_id"]', "11");
await page.fill('input[name="password"]', "0000");
await page.click("#operator_login_button");
await expect(page.locator("#user_auth_alert_error")).toHaveText(INVALID_CREDENTIALS);
});