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>
223 lines
7.5 KiB
JavaScript
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');
|
|
});
|
|
}); |