upskill-event-manager/tests/framework/core/AuthManager.js
Ben 7c9ca65cf2
Some checks are pending
HVAC Plugin CI/CD Pipeline / Security Analysis (push) Waiting to run
HVAC Plugin CI/CD Pipeline / Code Quality & Standards (push) Waiting to run
HVAC Plugin CI/CD Pipeline / Unit Tests (push) Waiting to run
HVAC Plugin CI/CD Pipeline / Integration Tests (push) Waiting to run
HVAC Plugin CI/CD Pipeline / Deploy to Staging (push) Blocked by required conditions
HVAC Plugin CI/CD Pipeline / Deploy to Production (push) Blocked by required conditions
HVAC Plugin CI/CD Pipeline / Notification (push) Blocked by required conditions
Security Monitoring & Compliance / Dependency Vulnerability Scan (push) Waiting to run
Security Monitoring & Compliance / Secrets & Credential Scan (push) Waiting to run
Security Monitoring & Compliance / WordPress Security Analysis (push) Waiting to run
Security Monitoring & Compliance / Static Code Security Analysis (push) Waiting to run
Security Monitoring & Compliance / Security Compliance Validation (push) Waiting to run
Security Monitoring & Compliance / Security Summary Report (push) Blocked by required conditions
Security Monitoring & Compliance / Security Team Notification (push) Blocked by required conditions
feat: add comprehensive test framework and test files
- Add 90+ test files including E2E, unit, and integration tests
- Implement Page Object Model (POM) architecture
- Add Docker testing environment with comprehensive services
- Include modernized test framework with error recovery
- Add specialized test suites for master trainer and trainer workflows
- Update .gitignore to properly track test infrastructure

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-29 23:23:26 -03:00

517 lines
No EOL
18 KiB
JavaScript

/**
* Enhanced Authentication Manager for HVAC Testing Framework
*
* Provides comprehensive authentication management with:
* - Storage state pre-generation and management
* - Role-based authentication with WordPress integration
* - Session persistence and validation
* - Parallel test execution support with isolated auth states
*
* @package HVAC_Community_Events
* @version 2.0.0
* @created 2025-08-27
*/
const fs = require('fs').promises;
const path = require('path');
const { chromium } = require('playwright');
const ConfigManager = require('./ConfigManager');
class AuthManager {
/**
* Authentication manager instance
*/
static instance = null;
/**
* Get authentication manager instance (Singleton pattern)
*/
static getInstance() {
if (!AuthManager.instance) {
AuthManager.instance = new AuthManager();
}
return AuthManager.instance;
}
constructor() {
this.config = ConfigManager;
this.storageStatesDir = path.resolve(__dirname, '../../fixtures/storage-states');
this.authValidated = new Map(); // Cache for validated auth states
this.initializeStorageDirectory();
}
/**
* Initialize storage states directory
*/
async initializeStorageDirectory() {
try {
await fs.mkdir(this.storageStatesDir, { recursive: true });
} catch (error) {
console.warn('Could not create storage states directory:', error.message);
}
}
/**
* Pre-generate storage states for all user roles
* This method should be called during test setup to create auth states
*/
async preGenerateStorageStates() {
console.log('🔐 Pre-generating authentication storage states...');
const roles = ['trainer', 'masterTrainer', 'admin'];
const results = [];
for (const role of roles) {
try {
console.log(` 📋 Generating storage state for: ${role}`);
const storageState = await this.generateStorageState(role);
results.push({ role, success: true, path: storageState });
console.log(` ✅ Generated: ${role}`);
} catch (error) {
console.error(` ❌ Failed to generate ${role}:`, error.message);
results.push({ role, success: false, error: error.message });
}
}
// Summary
const successful = results.filter(r => r.success).length;
const failed = results.filter(r => !r.success).length;
console.log(`\n📊 Storage state generation complete:`);
console.log(` ✅ Successful: ${successful}`);
console.log(` ❌ Failed: ${failed}`);
if (failed > 0) {
console.warn('Some storage states failed to generate. Tests may fall back to fresh login.');
}
return results;
}
/**
* Generate storage state for a specific user role
*/
async generateStorageState(role) {
const browser = await chromium.launch(this.config.getBrowserConfig());
const context = await browser.newContext();
const page = await context.newPage();
try {
// Get user configuration
const userConfig = this.config.getUserConfig(role);
if (!userConfig || !userConfig.username || !userConfig.password) {
throw new Error(`Missing user configuration for role: ${role}`);
}
// Navigate to login page
const loginUrl = this.getLoginUrl(role);
await page.goto(loginUrl, { waitUntil: 'networkidle' });
// Perform login
await this.performLogin(page, userConfig, role);
// Verify authentication
await this.verifyAuthentication(page, role);
// Save storage state
const storageStatePath = this.getStorageStatePath(role);
await context.storageState({ path: storageStatePath });
console.log(`💾 Saved storage state: ${storageStatePath}`);
return storageStatePath;
} finally {
await browser.close();
}
}
/**
* Load authentication storage state for user role
*/
async loadStorageState(role, context) {
const storageStatePath = this.getStorageStatePath(role);
try {
// Check if storage state file exists and is recent
const stats = await fs.stat(storageStatePath);
const ageHours = (Date.now() - stats.mtime) / (1000 * 60 * 60);
if (ageHours > 24) {
console.log(`🔄 Storage state for ${role} is ${ageHours.toFixed(1)} hours old, regenerating...`);
return await this.generateStorageState(role);
}
// Load storage state
await context.addInitScript(`
// Set storage state metadata
window.HVAC_AUTH_ROLE = '${role}';
window.HVAC_AUTH_LOADED = true;
`);
const storageState = JSON.parse(await fs.readFile(storageStatePath, 'utf8'));
await context.addCookies(storageState.cookies || []);
console.log(`📂 Loaded storage state for: ${role}`);
return storageStatePath;
} catch (error) {
console.log(`⚠️ Could not load storage state for ${role}: ${error.message}`);
console.log('Falling back to fresh authentication...');
return null;
}
}
/**
* Validate that authentication storage state is still valid
*/
async validateStorageState(page, role) {
// Check cache first
const cacheKey = `${role}-${this.getStorageStatePath(role)}`;
if (this.authValidated.has(cacheKey)) {
const cached = this.authValidated.get(cacheKey);
const ageMinutes = (Date.now() - cached.timestamp) / (1000 * 60);
if (ageMinutes < 5) { // Cache for 5 minutes
return cached.valid;
}
}
try {
await this.verifyAuthentication(page, role);
this.authValidated.set(cacheKey, { valid: true, timestamp: Date.now() });
return true;
} catch (error) {
this.authValidated.set(cacheKey, { valid: false, timestamp: Date.now() });
console.log(`❌ Storage state validation failed for ${role}: ${error.message}`);
return false;
}
}
/**
* Authenticate user with storage state management
*/
async authenticate(page, role, options = {}) {
const context = page.context();
// Try to load existing storage state
if (!options.forceLogin) {
const storageStatePath = await this.loadStorageState(role, context);
if (storageStatePath) {
// Navigate to expected page and validate
const defaultPage = this.config.getUserConfig(role).defaultPage;
await page.goto(`${this.config.get('app.baseUrl')}${defaultPage}`);
if (await this.validateStorageState(page, role)) {
console.log(`✅ Authentication restored for ${role} using storage state`);
return { method: 'storage-state', role, valid: true };
}
}
}
// Fall back to fresh login
console.log(`🔑 Performing fresh login for: ${role}`);
return await this.freshLogin(page, role, options);
}
/**
* Perform fresh login and optionally save storage state
*/
async freshLogin(page, role, options = {}) {
const userConfig = this.config.getUserConfig(role);
if (!userConfig || !userConfig.username || !userConfig.password) {
throw new Error(`Missing user configuration for role: ${role}`);
}
// Navigate to login page
const loginUrl = this.getLoginUrl(role);
await page.goto(loginUrl, { waitUntil: 'networkidle' });
// Perform login
await this.performLogin(page, userConfig, role);
// Verify authentication
await this.verifyAuthentication(page, role);
// Save storage state for future use
if (!options.skipStorageStateSave) {
try {
const storageStatePath = this.getStorageStatePath(role);
await page.context().storageState({ path: storageStatePath });
console.log(`💾 Updated storage state for: ${role}`);
} catch (error) {
console.warn('Could not save storage state:', error.message);
}
}
console.log(`✅ Fresh login successful for: ${role}`);
return { method: 'fresh-login', role, valid: true };
}
/**
* Perform login on page with user configuration
*/
async performLogin(page, userConfig, role) {
// Wait for login form
await page.waitForSelector('input[type="email"], input[name="log"], #user_login', { timeout: 10000 });
// Fill email/username
const emailSelectors = [
'input[type="email"]',
'input[name="log"]',
'#user_login',
'input[name="username"]'
];
let emailFilled = false;
for (const selector of emailSelectors) {
try {
const field = page.locator(selector);
if (await field.isVisible()) {
await field.fill(userConfig.username || userConfig.email);
emailFilled = true;
break;
}
} catch (error) {
// Continue to next selector
}
}
if (!emailFilled) {
throw new Error('Could not find email/username field');
}
// Fill password
const passwordSelectors = [
'input[type="password"]',
'input[name="pwd"]',
'#user_pass',
'input[name="password"]'
];
let passwordFilled = false;
for (const selector of passwordSelectors) {
try {
const field = page.locator(selector);
if (await field.isVisible()) {
await field.fill(userConfig.password);
passwordFilled = true;
break;
}
} catch (error) {
// Continue to next selector
}
}
if (!passwordFilled) {
throw new Error('Could not find password field');
}
// Submit form
const submitSelectors = [
'button[type="submit"]',
'input[type="submit"]',
'#wp-submit',
'.login-submit button',
'.submit input'
];
let submitted = false;
for (const selector of submitSelectors) {
try {
const button = page.locator(selector);
if (await button.isVisible()) {
await Promise.all([
page.waitForURL(url =>
!url.includes('/wp-login.php') &&
!url.includes('/training-login/'),
{ timeout: this.config.get('framework.timeout') }
),
button.click()
]);
submitted = true;
break;
}
} catch (error) {
// Continue to next selector
}
}
if (!submitted) {
throw new Error('Could not find or click submit button');
}
// Wait for page to load
await page.waitForLoadState('networkidle', { timeout: 10000 });
}
/**
* Verify authentication for user role
*/
async verifyAuthentication(page, role) {
const userConfig = this.config.getUserConfig(role);
const expectedPage = userConfig.defaultPage;
// Check if we're on the expected page or similar
const currentUrl = page.url();
// Role-specific verification
switch (role) {
case 'trainer':
if (!currentUrl.includes('/trainer/')) {
// Navigate to trainer dashboard
await page.goto(`${this.config.get('app.baseUrl')}/trainer/dashboard/`);
}
await page.waitForSelector('.hvac-trainer-nav, .trainer-dashboard, h1:has-text("Dashboard")', { timeout: 10000 });
break;
case 'masterTrainer':
if (!currentUrl.includes('/master-trainer/')) {
// Navigate to master trainer dashboard
await page.goto(`${this.config.get('app.baseUrl')}/master-trainer/master-dashboard/`);
}
await page.waitForSelector('.hvac-master-dashboard, .master-trainer-nav, h1:has-text("Master Dashboard")', { timeout: 10000 });
break;
case 'admin':
if (!currentUrl.includes('/wp-admin/')) {
// Navigate to WordPress admin
await page.goto(`${this.config.get('app.baseUrl')}/wp-admin/`);
}
await page.waitForSelector('#wpadminbar, .wp-admin, #wpbody', { timeout: 10000 });
break;
default:
throw new Error(`Unknown role: ${role}`);
}
// Check for login error indicators
const errorSelectors = [
'.error', '.login-error', '#login_error',
'.error-message', 'text=Invalid username or password'
];
for (const selector of errorSelectors) {
if (await page.locator(selector).isVisible().catch(() => false)) {
throw new Error('Login failed - error message visible');
}
}
}
/**
* Get login URL for role
*/
getLoginUrl(role) {
const baseUrl = this.config.get('app.baseUrl');
if (role === 'admin') {
return `${baseUrl}${this.config.get('wordpress.loginPath')}`;
} else {
return `${baseUrl}${this.config.get('wordpress.customLoginPath')}`;
}
}
/**
* Get storage state file path for role
*/
getStorageStatePath(role) {
const environment = this.config.getEnvironment();
return path.join(this.storageStatesDir, `${role}-${environment}.json`);
}
/**
* Clear all storage states
*/
async clearStorageStates() {
try {
const files = await fs.readdir(this.storageStatesDir);
const jsonFiles = files.filter(f => f.endsWith('.json'));
for (const file of jsonFiles) {
await fs.unlink(path.join(this.storageStatesDir, file));
}
console.log(`🗑️ Cleared ${jsonFiles.length} storage state files`);
} catch (error) {
console.warn('Could not clear storage states:', error.message);
}
}
/**
* Clear validation cache
*/
clearValidationCache() {
this.authValidated.clear();
console.log('🗑️ Cleared authentication validation cache');
}
/**
* Get authentication status summary
*/
async getAuthStatus() {
const roles = ['trainer', 'masterTrainer', 'admin'];
const status = {};
for (const role of roles) {
const storageStatePath = this.getStorageStatePath(role);
try {
const stats = await fs.stat(storageStatePath);
const ageHours = (Date.now() - stats.mtime) / (1000 * 60 * 60);
status[role] = {
exists: true,
path: storageStatePath,
ageHours: ageHours.toFixed(2),
fresh: ageHours < 1
};
} catch (error) {
status[role] = {
exists: false,
path: storageStatePath,
error: error.message
};
}
}
return status;
}
/**
* Logout current user
*/
async logout(page) {
const logoutSelectors = [
'a[href*="logout"]',
'text=Logout',
'text=Log Out',
'text=Sign Out',
'.logout-link'
];
for (const selector of logoutSelectors) {
try {
const logoutLink = page.locator(selector);
if (await logoutLink.isVisible()) {
await logoutLink.click();
await page.waitForURL(url =>
url.includes('/wp-login.php') ||
url.includes('/training-login/') ||
url.includes('/logged-out'),
{ timeout: 5000 }
);
console.log('✅ Successfully logged out');
return;
}
} catch (error) {
// Continue to next selector
}
}
// Fallback: clear cookies and navigate to login
await page.context().clearCookies();
await page.goto(`${this.config.get('app.baseUrl')}/wp-login.php`);
console.log('⚠️ Forced logout by clearing cookies');
}
}
// Export singleton instance
module.exports = AuthManager.getInstance();