Files
roa2web-service-auto/reports-app/frontend/tests/e2e/auth/login.spec.js
Marius Mutu f42eff71a6 Fix .gitignore and add missing authentication source files
This commit fixes overly broad .gitignore patterns that were excluding
important source code files from version control. Previously, wildcard
patterns like *auth*, *token*, *secret*, *connection*, and *credential*
were excluding ALL files containing these words, including critical
application code.

Changes:
- Updated .gitignore with specific patterns for sensitive config files
  (*.json, *.txt, *.yml, *.yaml extensions only)
- Removed broad wildcards that excluded source code files

Added missing source files:
- shared/auth/ (9 files): Complete authentication system
  - JWT handler, middleware, auth service, models, routes
- reports-app/backend/app/routers/auth.py: Authentication API router
- reports-app/backend/app/auth_middleware_wrapper.py: Middleware wrapper
- reports-app/frontend/src/stores/auth.js: Vue.js auth store
- reports-app/frontend/tests/: E2E tests and fixtures for auth
- reports-app/telegram-bot/app/auth/: Telegram auth linking module
- deployment/windows/scripts/Setup-ClaudeAuth.ps1: Windows deployment script
- security/secrets_scanner.py: Security scanning utility

These files are essential for the application to function and should
have been included in the initial commit.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-25 15:02:28 +03:00

223 lines
7.5 KiB
JavaScript

import { test, expect } from '@playwright/test';
import { LoginPage } from '../../page-objects/LoginPage.js';
import { testCredentials } from '../../fixtures/auth.js';
test.describe('Authentication - Login Flow', () => {
let loginPage;
test.beforeEach(async ({ page }) => {
loginPage = new LoginPage(page);
await loginPage.navigate();
});
test('should display login page correctly', async ({ page }) => {
// Check page title and main elements
await expect(page).toHaveTitle(/ROA Reports/);
// Check login form elements are visible
await expect(page.locator(loginPage.usernameInput)).toBeVisible();
await expect(page.locator(loginPage.passwordInput)).toBeVisible();
await expect(page.locator(loginPage.loginButton)).toBeVisible();
// Check page title
const title = await loginPage.getPageTitle();
expect(title).toContain('ROA Reports');
});
test('should show validation errors for empty fields', async ({ page }) => {
// Check initial state - button should be disabled
expect(await loginPage.isLoginButtonDisabled()).toBe(true);
// Clear any existing content and verify empty state
await page.fill(loginPage.usernameInput, '');
await page.fill(loginPage.passwordInput, '');
await page.click(loginPage.loginCard); // Click outside to trigger validation
// Wait for Vue reactivity
await page.waitForTimeout(100);
// Button should remain disabled with empty fields
expect(await loginPage.isLoginButtonDisabled()).toBe(true);
// Verify form validation classes are applied
const hasInvalidFields = await loginPage.hasInvalidField();
// Note: validation might not show invalid state until user interaction
});
test('should show validation error for empty username', async ({ page }) => {
// Fill only password
await loginPage.fillCredentials('', 'password123');
// Trigger validation by clicking outside
await page.click(loginPage.loginCard);
// Check that login button is disabled
expect(await loginPage.isLoginButtonDisabled()).toBe(true);
});
test('should show validation error for empty password', async ({ page }) => {
// Fill only username
await loginPage.fillCredentials('username', '');
// Trigger validation by clicking outside
await page.click(loginPage.loginCard);
// Check that login button is disabled
expect(await loginPage.isLoginButtonDisabled()).toBe(true);
});
test('should enable login button with valid input', async ({ page: _page }) => {
// Fill both fields
await loginPage.fillCredentials('testuser', 'testpass');
// Check that login button is enabled
expect(await loginPage.isLoginButtonDisabled()).toBe(false);
});
test('should handle invalid credentials', async ({ page }) => {
// Mock the API response for invalid login
await page.route('**/api/auth/login', async route => {
await route.fulfill({
status: 401,
contentType: 'application/json',
body: JSON.stringify({
detail: 'Invalid credentials'
}),
});
});
// Attempt login with invalid credentials
await loginPage.login(testCredentials.invalid.username, testCredentials.invalid.password);
// Wait for response
await loginPage.waitForLoginResult();
// Check that we're still on login page
expect(await loginPage.isOnLoginPage()).toBe(true);
// Check that error message appears (via toast or error div)
// Note: Error might be shown via PrimeVue Toast, so we need to check for toast messages
const toastError = page.locator('.p-toast-message-error');
if (await toastError.isVisible()) {
const errorText = await toastError.textContent();
expect(errorText).toContain('Eroare');
}
});
test('should handle successful login', async ({ page }) => {
// Mock the API response for successful login
await page.route('**/api/auth/login', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
access_token: 'mock_access_token',
refresh_token: 'mock_refresh_token',
user: {
id: 1,
username: 'testuser',
full_name: 'Test User'
}
}),
});
});
// Mock companies endpoint for dashboard
await page.route('**/api/companies', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([
{ code: 'COMP1', name: 'Company 1' },
{ code: 'COMP2', name: 'Company 2' }
]),
});
});
// Attempt login with valid credentials
await loginPage.login(testCredentials.valid.username, testCredentials.valid.password);
// Wait for redirect to dashboard
await page.waitForURL('/dashboard', { timeout: 10000 });
// Check that we're on dashboard page
expect(page.url()).toContain('/dashboard');
});
test('should show loading state during login', async ({ page }) => {
// Mock slow API response
await page.route('**/api/auth/login', async route => {
// Delay the response to see loading state
await new Promise(resolve => setTimeout(resolve, 1000));
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
access_token: 'mock_access_token',
refresh_token: 'mock_refresh_token',
user: { id: 1, username: 'testuser' }
}),
});
});
// Fill credentials and submit
await loginPage.fillCredentials(testCredentials.valid.username, testCredentials.valid.password);
await loginPage.clickLogin();
// Check loading state appears
await expect(page.locator(loginPage.loadingSpinner)).toBeVisible();
// Wait for loading to finish
await page.waitForLoadState('networkidle');
});
test('should handle network errors gracefully', async ({ page }) => {
// Mock network error
await page.route('**/api/auth/login', async route => {
await route.abort('failed');
});
// Attempt login
await loginPage.login(testCredentials.valid.username, testCredentials.valid.password);
// Wait a bit for error handling
await page.waitForTimeout(2000);
// Should still be on login page
expect(await loginPage.isOnLoginPage()).toBe(true);
// Check for error message in toast summary or detail
const toastSummary = page.locator('.p-toast-summary');
const toastDetail = page.locator('.p-toast-detail');
// Check if either summary or detail contains error text
const summaryVisible = await toastSummary.isVisible();
const detailVisible = await toastDetail.isVisible();
if (summaryVisible || detailVisible) {
let errorFound = false;
if (summaryVisible) {
const summaryText = await toastSummary.textContent();
if (summaryText && summaryText.toLowerCase().includes('eroare')) {
errorFound = true;
}
}
if (detailVisible) {
const detailText = await toastDetail.textContent();
if (detailText && detailText.toLowerCase().includes('eroare')) {
errorFound = true;
}
}
expect(errorFound).toBe(true);
}
});
test('should focus username field on page load', async ({ page }) => {
// Check that username field is focused
const focusedElement = await page.locator(':focus');
await expect(focusedElement).toHaveAttribute('id', 'username');
});
});