Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 15 additions & 22 deletions e2e/auth.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const uniqueTag = () => `e2e${Date.now()}`;

test.describe('Authentication', () => {
test('register form renders correctly', async ({ page }) => {
await page.goto('/register');
await page.goto('/v2/register');
await expect(page.getByLabel('Username')).toBeVisible();
await expect(page.getByLabel('Email')).toBeVisible();
await expect(page.getByLabel('Password')).toBeVisible();
Expand All @@ -13,7 +13,7 @@ test.describe('Authentication', () => {

test('register new user shows success message', async ({ page }) => {
const tag = uniqueTag();
await page.goto('/register');
await page.goto('/v2/register');
await page.getByLabel('Username').fill(`user_${tag}`);
await page.getByLabel('Email').fill(`${tag}@commonly.test`);
await page.getByLabel('Password').fill('TestPass123!');
Expand All @@ -25,23 +25,18 @@ test.describe('Authentication', () => {
});

test('login with wrong password shows error', async ({ page }) => {
await page.goto('/login');
await page.goto('/v2/login');
await page.getByLabel('Email').fill('nobody@commonly.test');
await page.getByLabel('Password').fill('wrongpassword');
await page.getByRole('button', { name: 'Login' }).click();
await page.getByRole('button', { name: 'Sign in' }).click();

// MUI v5: error text appears as Typography with color="error" — selector via style or role="alert"
// Fall back to any visible text containing common error keywords
await expect(
page.locator('[role="alert"], .MuiAlert-root').or(
page.locator('.MuiTypography-root').filter({ hasText: /invalid|incorrect|wrong|error|failed|not found/i })
)
).toBeVisible({ timeout: 8000 });
// URL must still be /login — not redirected
expect(page.url()).toContain('/login');
// v2 login surfaces the failure in a .v2-login__error div
await expect(page.locator('.v2-login__error')).toBeVisible({ timeout: 8000 });
// URL must still be the login page — not redirected
expect(page.url()).toContain('/v2/login');
});

test('login with valid credentials redirects to /feed', async ({ page, request }) => {
test('login with valid credentials redirects into /v2', async ({ page, request }) => {
// Register a fresh user (auto-verified when SENDGRID_API_KEY not set)
const tag = uniqueTag();
const email = `login_${tag}@commonly.test`;
Expand All @@ -51,13 +46,13 @@ test.describe('Authentication', () => {
{ data: { username: `loginuser_${tag}`, email, password, invitationCode: '' } },
);

await page.goto('/login');
await page.goto('/v2/login');
await page.getByLabel('Email').fill(email);
await page.getByLabel('Password').fill(password);
await page.getByRole('button', { name: 'Login' }).click();
await page.getByRole('button', { name: 'Sign in' }).click();

await page.waitForURL('**/feed', { timeout: 15000 });
expect(page.url()).toContain('/feed');
await page.waitForURL((url) => url.pathname.startsWith('/v2') && !url.pathname.startsWith('/v2/login'), { timeout: 15000 });
expect(page.url()).not.toContain('/login');
});

test('protected route redirects unauthenticated user', async ({ page }) => {
Expand All @@ -68,9 +63,7 @@ test.describe('Authentication', () => {
});
await page.goto('/feed');

// Either redirected to /login or shows a login prompt
await page.waitForURL(url => url.pathname === '/login' || url.pathname === '/feed', { timeout: 8000 });
// If still on /feed: the login button/form should appear (not full authenticated UI)
// This is acceptable as long as protected content is not directly visible
// v2 default: /feed → /v2/feed → V2RequireAuth → /v2/login when unauthenticated
await page.waitForURL((url) => url.pathname === '/v2/login', { timeout: 8000 });
});
});
8 changes: 5 additions & 3 deletions e2e/fixtures/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,13 @@ export const test = base.extend<AuthFixtures>({
authenticatedPage: async ({ page, request }, use) => {
await ensureTestUser(request);

await page.goto('/login');
await page.goto('/v2/login');
await page.getByLabel('Email').fill(TEST_USER.email);
await page.getByLabel('Password').fill(TEST_USER.password);
await page.getByRole('button', { name: 'Login' }).click();
await page.waitForURL('**/feed', { timeout: 15000 });
await page.getByRole('button', { name: 'Sign in' }).click();
// Wait for the post-login redirect OFF the login page (v2 login lands on
// /v2). A bare /v2 regex would match /v2/login and resolve before login.
await page.waitForURL((url) => url.pathname.startsWith('/v2') && !url.pathname.startsWith('/v2/login'), { timeout: 15000 });

await use(page);
},
Expand Down
4 changes: 2 additions & 2 deletions e2e/health.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ test.describe('Health endpoints', () => {
});

test('frontend login page renders', async ({ page }) => {
await page.goto('/login');
await page.goto('/v2/login');
await expect(page.getByLabel('Email')).toBeVisible();
await expect(page.getByLabel('Password')).toBeVisible();
await expect(page.getByRole('button', { name: 'Login' })).toBeVisible();
await expect(page.getByRole('button', { name: 'Sign in' })).toBeVisible();
});
});
10 changes: 5 additions & 5 deletions e2e/pods.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ import { test, expect } from '././fixtures/auth';

test.describe('Pods', () => {
test('authenticated user can view pod listing', async ({ authenticatedPage: page }) => {
await page.goto('/pods');
// Page should render pod UI — at minimum no crash/blank page
await page.goto('/v2');
// Page should render the v2 pod UI — at minimum no crash/blank page
await expect(page.locator('#root')).toBeAttached();
// URL should remain on /pods (not redirected to /login)
expect(page.url()).toContain('/pods');
// v2 is the default shell; an authenticated user should not bounce to login
expect(page.url()).toContain('/v2');
});

test('authenticated user can navigate to feed', async ({ authenticatedPage: page }) => {
await page.goto('/feed');
await page.goto('/v2/feed');
await expect(page.locator('#root')).toBeAttached();
expect(page.url()).toContain('/feed');
});
Expand Down
21 changes: 9 additions & 12 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import Login from './components/Login';
import Register from './components/Register';
import RegistrationInviteRequired from './components/RegistrationInviteRequired';
import LandingPage from './components/landing/LandingPage';
import V2LandingPage from './v2/landing/V2LandingPage';
import UseCasePage from './components/landing/UseCasePage';
import VerifyEmail from './components/VerifyEmail';
import PostFeed from './components/PostFeed';
Expand Down Expand Up @@ -225,17 +224,15 @@ function NavigationHandler(): null {
const navigate = useNavigate();

useEffect(() => {
try {
const v2Active = sessionStorage.getItem('commonly.v2.active') === '1';
if (v2Active && !location.pathname.startsWith('/v2')) {
const v2Path = getV2EquivalentPath(location.pathname, location.search);
if (v2Path) {
navigate(v2Path, { replace: true });
return;
}
// v2 is the default UI: redirect any non-/v2 path that has a v2
// equivalent into the v2 shell. /v2/* stays directly routable; paths
// without a v2 equivalent (e.g. /legacy-landing) render as-is.
if (!location.pathname.startsWith('/v2')) {
const v2Path = getV2EquivalentPath(location.pathname, location.search);
if (v2Path) {
navigate(v2Path, { replace: true });
return;
}
} catch {
// sessionStorage may be blocked; navigation still works normally.
}

// Force a re-render when the location changes
Expand Down Expand Up @@ -282,7 +279,7 @@ function App(): React.ReactElement {
<div className="App">
<Routes>
<Route path="/v2/*" element={<V2App />} />
<Route path="/" element={<V2LandingPage />} />
<Route path="/" element={<Navigate to="/v2" replace />} />
<Route path="/legacy-landing" element={<LandingPage />} />
<Route path="/use-cases/:useCaseId" element={<UseCasePage />} />
<Route path="/login" element={<Login />} />
Expand Down
Loading
Loading