chore: add sms otp and password set acceptance tests

This commit is contained in:
Stefan Benz
2024-11-21 18:01:12 +01:00
parent 1baa2409be
commit f38b8b753c
15 changed files with 165 additions and 46 deletions

View File

@@ -1,11 +1,11 @@
import { Page } from "@playwright/test";
import { codeScreen } from "./code-screen";
import { getCodeFromSink } from "./sink";
import { getOtpFromSink } from "./sink";
export async function codeFromSink(page: Page, key: string) {
export async function otpFromSink(page: Page, key: string) {
// wait for send of the code
await page.waitForTimeout(3000);
const c = await getCodeFromSink(key);
const c = await getOtpFromSink(key);
await code(page, c);
}

View File

@@ -1,5 +1,5 @@
import { expect, Page } from "@playwright/test";
import { codeFromSink } from "./code";
import { otpFromSink } from "./code";
import { loginname } from "./loginname";
import { password } from "./password";
@@ -26,10 +26,10 @@ export async function loginScreenExpect(page: Page, fullName: string) {
export async function loginWithPasswordAndEmailOTP(page: Page, username: string, password: string, email: string) {
await loginWithPassword(page, username, password);
await codeFromSink(page, email);
await otpFromSink(page, email);
}
export async function loginWithPasswordAndPhoneOTP(page: Page, username: string, password: string, phone: string) {
await loginWithPassword(page, username, password);
await codeFromSink(page, phone);
await otpFromSink(page, phone);
}

View File

@@ -1,5 +1,7 @@
import { expect, Page } from "@playwright/test";
import { getCodeFromSink } from "./sink";
const codeField = "code-text-input";
const passwordField = "password-text-input";
const passwordConfirmField = "password-confirm-text-input";
const lengthCheck = "length-check";
@@ -55,3 +57,26 @@ async function checkContent(page: Page, testid: string, match: boolean) {
await expect(page.getByTestId(testid)).toContainText(noMatchText);
}
}
export async function resetPasswordScreen(page: Page, username: string, password1: string, password2: string) {
// wait for send of the code
await page.waitForTimeout(3000);
const c = await getCodeFromSink(username);
await page.getByTestId(codeField).pressSequentially(c);
await page.getByTestId(passwordField).pressSequentially(password1);
await page.getByTestId(passwordConfirmField).pressSequentially(password2);
}
export async function resetPasswordScreenExpect(
page: Page,
password1: string,
password2: string,
length: boolean,
symbol: boolean,
number: boolean,
uppercase: boolean,
lowercase: boolean,
equals: boolean,
) {
await changePasswordScreenExpect(page, password1, password2, length, symbol, number, uppercase, lowercase, equals);
}

View File

@@ -1,7 +1,8 @@
import { Page } from "@playwright/test";
import { changePasswordScreen, passwordScreen } from "./password-screen";
import { changePasswordScreen, passwordScreen, resetPasswordScreen } from "./password-screen";
const passwordSubmitButton = "submit-button";
const passwordResetButton = "reset-button";
export async function startChangePassword(page: Page, loginname: string) {
await page.goto("/password/change?" + new URLSearchParams({ loginName: loginname }));
@@ -17,3 +18,13 @@ export async function password(page: Page, password: string) {
await passwordScreen(page, password);
await page.getByTestId(passwordSubmitButton).click();
}
export async function startResetPassword(page: Page) {
await page.getByTestId(passwordResetButton).click();
}
export async function resetPassword(page: Page, username: string, password: string) {
await startResetPassword(page);
await resetPasswordScreen(page, username, password, password);
await page.getByTestId(passwordSubmitButton).click();
}

View File

@@ -1,6 +1,6 @@
import axios from "axios";
export async function getCodeFromSink(key: string): Promise<any> {
export async function getOtpFromSink(key: string): Promise<any> {
try {
const response = await axios.post(
process.env.SINK_NOTIFICATION_URL!,
@@ -26,3 +26,30 @@ export async function getCodeFromSink(key: string): Promise<any> {
throw error;
}
}
export async function getCodeFromSink(key: string): Promise<any> {
try {
const response = await axios.post(
process.env.SINK_NOTIFICATION_URL!,
{
recipient: key,
},
{
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.ZITADEL_SERVICE_USER_TOKEN}`,
},
},
);
if (response.status >= 400) {
const error = `HTTP Error: ${response.status} - ${response.statusText}`;
console.error(error);
throw new Error(error);
}
return response.data.args.code;
} catch (error) {
console.error("Error making request:", error);
throw error;
}
}

View File

@@ -21,8 +21,6 @@ class User {
}
async ensure(page: Page) {
await this.remove();
const body = {
username: this.props.email,
organization: {
@@ -53,7 +51,7 @@ class User {
},
});
if (response.status >= 400 && response.status !== 409) {
if (response.status >= 400) {
const error = `HTTP Error: ${response.status} - ${response.statusText}`;
console.error(error);
throw new Error(error);
@@ -65,7 +63,7 @@ class User {
}
// wait for projection of user
await page.waitForTimeout(3000);
await page.waitForTimeout(2000);
}
async remove() {
@@ -166,7 +164,7 @@ export class PasswordUserWithOTP extends User {
},
);
if (response.status >= 400 && response.status !== 409) {
if (response.status >= 400) {
const error = `HTTP Error: ${response.status} - ${response.statusText}`;
console.error(error);
throw new Error(error);
@@ -204,7 +202,6 @@ export class PasskeyUser extends User {
}
public async ensure(page: Page) {
await this.remove();
const authId = await registerWithPasskey(page, this.getFirstname(), this.getLastname(), this.getUsername());
this.authenticatorId = authId;
@@ -212,10 +209,6 @@ export class PasskeyUser extends User {
await page.waitForTimeout(2000);
}
public async remove() {
await super.remove();
}
public getAuthenticatorId(): string {
return this.authenticatorId;
}

View File

@@ -27,15 +27,6 @@ test("username and passkey login", async ({ user, page }) => {
await loginScreenExpect(page, user.getFullName());
});
test("username and passkey login, if passkey enabled", async ({ page }) => {
// Given passkey is enabled on the organization of the user
// Given the user has only passkey enabled as authentication
// enter username
// passkey popup is directly shown
// user verifies passkey
// user is redirected to app
});
test("username and passkey login, multiple auth methods", async ({ page }) => {
// Given passkey and password is enabled on the organization of the user
// Given the user has password and passkey registered

View File

@@ -42,7 +42,7 @@ test("username and password changed login", async ({ user, page }) => {
*/
});
test("password not with desired complexity", async ({ user, page }) => {
test("password change not with desired complexity", async ({ user, page }) => {
const changedPw1 = "change";
const changedPw2 = "chang";
await loginWithPassword(page, user.getUsername(), user.getPassword());

View File

@@ -2,7 +2,7 @@ import { faker } from "@faker-js/faker";
import { test as base } from "@playwright/test";
import dotenv from "dotenv";
import path from "path";
import { code, codeFromSink, codeResend } from "./code";
import { code, codeResend, otpFromSink } from "./code";
import { codeScreenExpect } from "./code-screen";
import { loginScreenExpect, loginWithPassword, loginWithPasswordAndEmailOTP } from "./login";
import { OtpType, PasswordUserWithOTP } from "./user";
@@ -61,7 +61,7 @@ test("username, password and email otp login, resend code", async ({ user, page
// User is redirected to the app (default redirect url)
await loginWithPassword(page, user.getUsername(), user.getPassword());
await codeResend(page);
await codeFromSink(page, user.getUsername());
await otpFromSink(page, user.getUsername());
await loginScreenExpect(page, user.getFullName());
});

View File

@@ -2,6 +2,9 @@ import { faker } from "@faker-js/faker";
import { test as base } from "@playwright/test";
import dotenv from "dotenv";
import path from "path";
import { code } from "./code";
import { codeScreenExpect } from "./code-screen";
import { loginScreenExpect, loginWithPassword, loginWithPasswordAndPhoneOTP } from "./login";
import { OtpType, PasswordUserWithOTP } from "./user";
// Read from ".env" file.
@@ -14,7 +17,7 @@ const test = base.extend<{ user: PasswordUserWithOTP; sink: any }>({
firstName: faker.person.firstName(),
lastName: faker.person.lastName(),
organization: "",
phone: faker.phone.number(),
phone: faker.phone.number({ style: "international" }),
password: "Password1!",
type: OtpType.sms,
});
@@ -32,10 +35,8 @@ test("username, password and sms otp login, enter code manually", async ({ user,
// User receives a sms with a verification code
// User enters the code into the ui
// User is redirected to the app (default redirect url)
/* TODO fix on login, that sms is sent
await loginWithPasswordAndPhoneOTP(page, user.getUsername(), user.getPassword(), user.getPhone());
await loginScreenExpect(page, user.getFullName());
*/
});
test("username, password and sms otp login, resend code", async ({ user, page }) => {
@@ -47,11 +48,9 @@ test("username, password and sms otp login, resend code", async ({ user, page })
// User clicks resend code
// User receives a new sms with a verification code
// User is redirected to the app (default redirect url)
/* TODO fix on login, that sms is sent
await loginWithPassword(page, user.getUsername(), user.getPassword());
await loginWithPasswordAndPhoneOTP(page, user.getUsername(), user.getPassword(), user.getPhone());
await loginScreenExpect(page, user.getFullName());
*/
await loginWithPassword(page, user.getUsername(), user.getPassword());
await loginWithPasswordAndPhoneOTP(page, user.getUsername(), user.getPassword(), user.getPhone());
await loginScreenExpect(page, user.getFullName());
});
test("username, password and sms otp login, wrong code", async ({ user, page }) => {
@@ -62,10 +61,8 @@ test("username, password and sms otp login, wrong code", async ({ user, page })
// User receives a sms with a verification code
// User enters a wrong code
// Error message - "Invalid code" is shown
/* TODO fix on login, that sms is sent
const c = "wrongcode";
await loginWithPassword(page, user.getUsername(), user.getPassword());
await code(page, c);
await codeScreenExpect(page, c);
*/
const c = "wrongcode";
await loginWithPassword(page, user.getUsername(), user.getPassword());
await code(page, c);
await codeScreenExpect(page, c);
});

View File

@@ -0,0 +1,50 @@
import { faker } from "@faker-js/faker";
import { test as base } from "@playwright/test";
import dotenv from "dotenv";
import path from "path";
import { loginScreenExpect, loginWithPassword, startLogin } from "./login";
import { loginname } from "./loginname";
import { resetPassword, startResetPassword } from "./password";
import { resetPasswordScreen, resetPasswordScreenExpect } from "./password-screen";
import { PasswordUser } from "./user";
// Read from ".env" file.
dotenv.config({ path: path.resolve(__dirname, ".env.local") });
const test = base.extend<{ user: PasswordUser }>({
user: async ({ page }, use) => {
const user = new PasswordUser({
email: faker.internet.email(),
firstName: faker.person.firstName(),
lastName: faker.person.lastName(),
organization: "",
phone: faker.phone.number(),
password: "Password1!",
});
await user.ensure(page);
await use(user);
},
});
test("username and password set login", async ({ user, page }) => {
// commented, fix in https://github.com/zitadel/zitadel/pull/8807
const changedPw = "ChangedPw1!";
await startLogin(page);
await loginname(page, user.getUsername());
await resetPassword(page, user.getUsername(), changedPw);
await loginScreenExpect(page, user.getFullName());
await loginWithPassword(page, user.getUsername(), changedPw);
await loginScreenExpect(page, user.getFullName());
});
test("password set not with desired complexity", async ({ user, page }) => {
const changedPw1 = "change";
const changedPw2 = "chang";
await startLogin(page);
await loginname(page, user.getUsername());
await startResetPassword(page);
await resetPasswordScreen(page, user.getUsername(), changedPw1, changedPw2);
await resetPasswordScreenExpect(page, changedPw1, changedPw2, false, false, false, false, true, false);
});