chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { apiRegister, uiLogin, uniqueEmail, trackRefresh } from './helpers';
|
||||
|
||||
test('login (happy): redirects to dashboard and avoids extra refresh calls', async ({ page, request }) => {
|
||||
const email = uniqueEmail('login');
|
||||
const password = 'Password!123';
|
||||
await apiRegister(request, email, password);
|
||||
|
||||
const refreshHits = trackRefresh(page);
|
||||
|
||||
await uiLogin(page, email, password);
|
||||
await expect(page).toHaveURL(/dashboard/i, { timeout: 10_000 });
|
||||
|
||||
// Should not need an immediate refresh after successful login
|
||||
expect(refreshHits.length).toBeLessThanOrEqual(1);
|
||||
});
|
||||
|
||||
test('login (bad creds): shows error and DOES NOT call /auth/refresh', async ({ page }) => {
|
||||
const refreshHits = trackRefresh(page);
|
||||
|
||||
await uiLogin(page, 'wrong@example.com', 'nope');
|
||||
// expect some error UI; adjust text to your app
|
||||
await expect(page.getByText(/invalid|wrong|credentials|creden/i)).toBeVisible();
|
||||
|
||||
expect(refreshHits.length).toBe(0);
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { apiRegister, uiLogin, uniqueEmail } from './helpers';
|
||||
|
||||
test('logout clears session and allows login page to render', async ({ page, request }) => {
|
||||
const email = uniqueEmail('logout');
|
||||
const password = 'Password!123';
|
||||
await apiRegister(request, email, password);
|
||||
await uiLogin(page, email, password);
|
||||
await expect(page).toHaveURL(/dashboard/);
|
||||
|
||||
// click your logout control; tweak selector to your UI
|
||||
await page.getByRole('button', { name: /log ?out/i }).click();
|
||||
|
||||
// app should route to /login or home, and render the sign-in form again
|
||||
await expect(page).toHaveURL(/login/);
|
||||
await expect(page.getByRole('button', { name: /sign in/i })).toBeVisible();
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { apiRegister, uniqueEmail } from './helpers';
|
||||
|
||||
test('forgot + reset password flow works end-to-end with mock token', async ({ page, request }) => {
|
||||
const email = uniqueEmail('pw');
|
||||
const oldPass = 'OldPass!123';
|
||||
const newPass = 'NewPass!123';
|
||||
|
||||
await apiRegister(request, email, oldPass);
|
||||
|
||||
// 1) Request reset
|
||||
await page.goto('/forgot-password');
|
||||
await page.getByLabel(/email/i).fill(email);
|
||||
await page.getByRole('button', { name: /send|reset|submit/i }).click();
|
||||
|
||||
// The mock returns a token in the response body. Capture it with route interception.
|
||||
// Alternative: call API programmatically here:
|
||||
const apiBase = (process.env.NEXT_PUBLIC_API_URL || '').replace(/\/+$/, '');
|
||||
const resp = await request.post(`${apiBase}/auth/forgot-password`, { data: { email } });
|
||||
expect(resp.ok()).toBeTruthy();
|
||||
const { token } = await resp.json();
|
||||
|
||||
// 2) Complete reset
|
||||
await page.goto('/reset-password');
|
||||
await page.getByLabel(/token/i).fill(token); // adjust if your UI reads token from URL param
|
||||
await page.getByLabel(/^password$/i).fill(newPass);
|
||||
await page.getByRole('button', { name: /reset/i }).click();
|
||||
await expect(page.getByText(/updated|success/i)).toBeVisible();
|
||||
|
||||
// 3) Login with new password
|
||||
await page.goto('/login');
|
||||
await page.getByLabel(/email/i).fill(email);
|
||||
await page.getByLabel(/password/i).fill(newPass);
|
||||
await page.getByRole('button', { name: /sign in/i }).click();
|
||||
await expect(page).toHaveURL(/dashboard/);
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { apiRegister, uiLogin, uniqueEmail, trackRefresh } from './helpers';
|
||||
|
||||
test('visiting /login while authenticated redirects quickly without refresh storms', async ({ page, request }) => {
|
||||
const email = uniqueEmail('redir');
|
||||
const password = 'Password!123';
|
||||
await apiRegister(request, email, password);
|
||||
|
||||
await uiLogin(page, email, password);
|
||||
await expect(page).toHaveURL(/dashboard/);
|
||||
|
||||
const refreshHits = trackRefresh(page);
|
||||
|
||||
// Go to /login again; AuthLayout + context should push back to dashboard
|
||||
await page.goto('/login');
|
||||
await expect(page).toHaveURL(/dashboard/);
|
||||
|
||||
expect(refreshHits.length).toBeLessThanOrEqual(1);
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { apiRegister, uiLogin, uniqueEmail, trackRefresh } from './helpers';
|
||||
|
||||
test('concurrent 401s trigger exactly one refresh (singleton)', async ({ page, request }) => {
|
||||
const email = uniqueEmail('single');
|
||||
const password = 'Password!123';
|
||||
await apiRegister(request, email, password);
|
||||
await uiLogin(page, email, password);
|
||||
await expect(page).toHaveURL(/dashboard/);
|
||||
|
||||
const refreshHits = trackRefresh(page);
|
||||
|
||||
// Trigger two protected fetches in the app at once.
|
||||
// E.g., navigate to a page that loads two protected endpoints on mount.
|
||||
// If you don’t have such a page, add a tiny dev route that does it for tests,
|
||||
// or invoke window.fetch twice via evaluate:
|
||||
await page.evaluate(() => {
|
||||
// these URLs should be protected in your app (adjust to real endpoints)
|
||||
fetch(`${process.env.NEXT_PUBLIC_API_URL!.replace(/\/+$/, '')}/protected/a`, { credentials: 'include' });
|
||||
fetch(`${process.env.NEXT_PUBLIC_API_URL!.replace(/\/+$/, '')}/protected/b`, { credentials: 'include' });
|
||||
});
|
||||
|
||||
// Allow a moment for network to settle
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// The wrapper ensures only one refresh went out even if both 401'd
|
||||
expect(refreshHits.length).toBeLessThanOrEqual(1);
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { uiRegister, uniqueEmail, trackRefresh } from './helpers';
|
||||
|
||||
test('register → creates account, sets session, redirects to dashboard, no extra refresh spam', async ({ page }) => {
|
||||
const email = uniqueEmail('reg');
|
||||
const refreshHits = trackRefresh(page);
|
||||
|
||||
await uiRegister(page, email, 'Password!123', 'Reg User');
|
||||
|
||||
await expect(page).toHaveURL(/dashboard/i, { timeout: 10_000 });
|
||||
|
||||
// sanity: check we appear logged-in (tweak selector to your header/avatar)
|
||||
await expect(page.getByText(/reg user/i)).toBeVisible({ timeout: 10_000 });
|
||||
|
||||
// Registration API already returns tokens/user; no reason to instantly call /auth/refresh
|
||||
expect(refreshHits.length).toBeLessThanOrEqual(1);
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
import { Page, APIRequestContext, expect } from '@playwright/test';
|
||||
|
||||
export function uniqueEmail(prefix = 'user'): string {
|
||||
const rand = Math.random().toString(36).slice(2, 8);
|
||||
return `${prefix}.${Date.now()}.${rand}@example.com`;
|
||||
}
|
||||
|
||||
// Programmatic register via API (fast & reliable seed)
|
||||
export async function apiRegister(request: APIRequestContext, email: string, password: string, name = 'Testy') {
|
||||
const apiBase = (process.env.NEXT_PUBLIC_API_URL || '').replace(/\/+$/, '');
|
||||
const res = await request.post(`${apiBase}/auth/register`, {
|
||||
data: { email, password, name },
|
||||
});
|
||||
expect(res.ok()).toBeTruthy();
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function apiLogout(request: APIRequestContext) {
|
||||
const apiBase = (process.env.NEXT_PUBLIC_API_URL || '').replace(/\/+$/, '');
|
||||
await request.post(`${apiBase}/auth/logout`);
|
||||
}
|
||||
|
||||
// UI helpers
|
||||
export async function uiLogin(page: Page, email: string, password: string) {
|
||||
await page.goto('/login');
|
||||
await page.getByLabel(/email address/i).fill(email);
|
||||
await page.getByLabel(/password/i).fill(password);
|
||||
await page.getByRole('button', { name: /sign in/i }).click();
|
||||
}
|
||||
|
||||
export async function uiRegister(page: Page, email: string, password: string, name = 'Testy McTestface') {
|
||||
await page.goto('/register');
|
||||
await page.getByLabel(/name/i).fill(name);
|
||||
await page.getByLabel(/email/i).fill(email);
|
||||
await page.getByLabel(/password/i).fill(password);
|
||||
await page.getByRole('button', { name: /create|register|sign up/i }).click();
|
||||
}
|
||||
|
||||
// Count refresh calls on a page (works per test)
|
||||
export function trackRefresh(page: Page) {
|
||||
const hits: string[] = [];
|
||||
page.on('request', req => {
|
||||
if (/\/auth\/refresh\b/i.test(req.url()) && req.method() === 'POST') hits.push(req.url());
|
||||
});
|
||||
return hits;
|
||||
}
|
||||
Reference in New Issue
Block a user