Files
roa2web-service-auto/e2e/single-server-login.spec.js
Claude Agent b137e80b71 feat: multi-Oracle server support with runtime switching
Complete implementation of multi-server Oracle database support:

Backend:
- Multi-pool Oracle with lazy loading per server
- Email-to-server cache for automatic server discovery
- JWT tokens include server_id claim
- /auth/check-identity and /auth/check-email endpoints
- /auth/my-servers endpoint for listing user's accessible servers
- Server switch with password re-authentication

Frontend:
- New ServerSelector component for header dropdown
- Multi-step login flow (identity → server → password)
- Server switching from header with password modal
- Mobile drawer menu with server selection
- Dark mode support for all new components
- URL bookmark support with ?server= query param

Scripts:
- Unified start.sh replacing start-prod.sh/start-test.sh
- Unified ssh-tunnel.sh with multi-server support
- Updated status.sh for new architecture

Tests:
- E2E tests for multi-server and single-server login flows
- Backend unit tests for all new endpoints
- Oracle multi-pool integration tests

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 22:39:06 +00:00

210 lines
7.4 KiB
JavaScript

import { test, expect } from '@playwright/test';
/**
* E2E Tests for Backward Compatibility - Single-Server Login (US-011)
*
* These tests verify that the classic username/password login flow works
* when ORACLE_SERVERS is NOT configured (single-server mode).
*
* Prerequisites:
* 1. Backend running WITHOUT ORACLE_SERVERS env variable
* 2. Start test environment: ./start.sh test
* 3. Frontend running on port 3000
*
* Run:
* npm run test:e2e -- single-server-login.spec.js
* npm run test:e2e:headed -- single-server-login.spec.js
*/
// Test configuration for single-server mode
const TEST_USER = {
username: 'MARIUS M',
password: '123',
company: 'MARIUSM AUTO'
};
test.describe('Single-Server Login Backward Compatibility (US-011)', () => {
test('should show username/password form in single-server mode', async ({ page }) => {
// Navigate to login page
await page.goto('/');
// Wait for auth mode detection to complete
// The login form should show username field (not email) in single-server mode
await page.waitForSelector('input#identity', { timeout: 10000 });
// Verify username field is visible (single-server mode)
const usernameField = page.locator('input#identity');
const emailField = page.locator('input#identity');
// In single-server mode, username field should be visible
// In multi-server mode, email field would be visible instead
const isUsernameVisible = await usernameField.isVisible().catch(() => false);
const isEmailVisible = await emailField.isVisible().catch(() => false);
// At least one should be visible
expect(isUsernameVisible || isEmailVisible).toBe(true);
if (isUsernameVisible) {
// Single-server mode - verify password field is also present
const passwordField = page.locator('#password input');
await expect(passwordField).toBeVisible();
// Verify "Autentificare" button exists (not "Continuă")
await expect(page.locator('button:has-text("Autentificare")')).toBeVisible();
}
});
test('should successfully login with username/password', async ({ page }) => {
await page.goto('/');
// Wait for form to load
await page.waitForSelector('input#identity', { timeout: 10000 });
// Check which mode we're in
const usernameField = page.locator('input#identity');
const isUsernameVisible = await usernameField.isVisible().catch(() => false);
if (isUsernameVisible) {
// Single-server mode: fill username and password
await page.fill('input#identity', TEST_USER.username);
await page.fill('#password input', TEST_USER.password);
// Click login button
await page.click('button:has-text("Autentificare")');
// Wait for redirect after successful login
await page.waitForURL(/\/(reports|data-entry|dashboard)/, { timeout: 15000 });
// Verify we're logged in (check for logout button or user menu)
const logoutButton = page.locator('button:has-text("Deconectare"), [class*="logout"]');
const userMenu = page.locator('[class*="user"], [class*="profile"]');
// Should be redirected away from login page
expect(page.url()).not.toContain('/login');
} else {
// Multi-server mode: use email flow (existing tests cover this)
console.log('Multi-server mode detected - skipping single-server login test');
}
});
test('should show error for invalid credentials', async ({ page }) => {
await page.goto('/');
// Wait for form to load
await page.waitForSelector('input#identity', { timeout: 10000 });
const usernameField = page.locator('input#identity');
const isUsernameVisible = await usernameField.isVisible().catch(() => false);
if (isUsernameVisible) {
// Single-server mode: test invalid credentials
await page.fill('input#identity', 'INVALID_USER');
await page.fill('#password input', 'wrong_password');
// Click login button
await page.click('button:has-text("Autentificare")');
// Wait for error message
await page.waitForTimeout(2000); // Wait for API response
// Check for error toast or message
const errorToast = page.locator('.p-toast-message-error, .p-toast-error, [class*="error"]');
const errorMessage = page.locator('[class*="error-message"], .p-message-error');
// Should show some form of error
const hasError = await errorToast.isVisible().catch(() => false) ||
await errorMessage.isVisible().catch(() => false);
// Should still be on login page (not redirected)
expect(page.url()).toMatch(/\/$|\/login/);
}
});
test('should preserve JWT in localStorage after login', async ({ page }) => {
await page.goto('/');
await page.waitForSelector('input#identity', { timeout: 10000 });
const usernameField = page.locator('input#identity');
const isUsernameVisible = await usernameField.isVisible().catch(() => false);
if (isUsernameVisible) {
await page.fill('input#identity', TEST_USER.username);
await page.fill('#password input', TEST_USER.password);
await page.click('button:has-text("Autentificare")');
await page.waitForURL(/\/(reports|data-entry|dashboard)/, { timeout: 15000 });
// Check localStorage for JWT
const accessToken = await page.evaluate(() => localStorage.getItem('access_token'));
const user = await page.evaluate(() => localStorage.getItem('user'));
// JWT should be stored
expect(accessToken).toBeTruthy();
expect(accessToken.split('.').length).toBe(3); // Valid JWT format
// User should be stored
expect(user).toBeTruthy();
const userData = JSON.parse(user);
expect(userData).toHaveProperty('username');
}
});
test('should access reports after login', async ({ page }) => {
await page.goto('/');
await page.waitForSelector('input#identity', { timeout: 10000 });
const usernameField = page.locator('input#identity');
const isUsernameVisible = await usernameField.isVisible().catch(() => false);
if (isUsernameVisible) {
// Login first
await page.fill('input#identity', TEST_USER.username);
await page.fill('#password input', TEST_USER.password);
await page.click('button:has-text("Autentificare")');
await page.waitForURL(/\/(reports|data-entry|dashboard)/, { timeout: 15000 });
// Navigate to reports if not already there
await page.goto('/reports');
// Wait for page to load
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
// Should not be redirected back to login
await page.waitForTimeout(2000);
expect(page.url()).not.toMatch(/\/$|\/login/);
}
});
});
test.describe('Auth Mode Detection', () => {
test('should return valid auth-mode response', async ({ request }) => {
// Test the auth-mode endpoint directly
const response = await request.get('/api/system/auth-mode');
expect(response.ok()).toBe(true);
const data = await response.json();
// Should have required fields
expect(data).toHaveProperty('mode');
expect(data).toHaveProperty('supports_email_login');
// Mode should be either single-server or multi-server
expect(['single-server', 'multi-server']).toContain(data.mode);
// supports_email_login should match mode
if (data.mode === 'single-server') {
expect(data.supports_email_login).toBe(false);
} else {
expect(data.supports_email_login).toBe(true);
}
});
});