- Add HVAC_Test_User_Factory class with: * User creation with specific roles * Multiple role support * Persona management system * Account cleanup integration - Create comprehensive test suite in HVAC_Test_User_Factory_Test.php - Update testing improvement plan documentation - Add implementation decisions to project memory bank - Restructure .gitignore with: * Whitelist approach for better file management * Explicit backup exclusions * Specific bin directory inclusions Part of the Account Management component from the testing framework improvement plan.
133 lines
No EOL
4.1 KiB
TypeScript
133 lines
No EOL
4.1 KiB
TypeScript
import { LogParser, LogEntry } from './logParser';
|
|
import fs from 'fs/promises';
|
|
import path from 'path';
|
|
import { STAGING_CONFIG } from '../../../playwright.config';
|
|
import { Client } from 'ssh2';
|
|
|
|
export interface LogSource {
|
|
name: string;
|
|
path: string;
|
|
type: 'wordpress' | 'php' | 'nginx-access' | 'nginx-error';
|
|
}
|
|
|
|
export class LogIntegrator {
|
|
private logParser: LogParser;
|
|
private sources: LogSource[] = [
|
|
{
|
|
name: 'WordPress Debug',
|
|
path: `${STAGING_CONFIG.path}/wp-content/debug.log`,
|
|
type: 'wordpress'
|
|
},
|
|
{
|
|
name: 'PHP Error',
|
|
path: '/var/log/php_errors.log',
|
|
type: 'php'
|
|
},
|
|
{
|
|
name: 'Nginx Access',
|
|
path: '/var/log/nginx/access.log',
|
|
type: 'nginx-access'
|
|
},
|
|
{
|
|
name: 'Nginx Error',
|
|
path: '/var/log/nginx/error.log',
|
|
type: 'nginx-error'
|
|
}
|
|
];
|
|
|
|
constructor() {
|
|
this.logParser = new LogParser();
|
|
}
|
|
|
|
async collectLogs(testStartTime: Date, testEndTime: Date): Promise<Map<string, LogEntry[]>> {
|
|
const logs = new Map<string, LogEntry[]>();
|
|
|
|
for (const source of this.sources) {
|
|
const entries = await this.fetchAndFilterLogs(source, testStartTime, testEndTime);
|
|
logs.set(source.name, entries);
|
|
}
|
|
|
|
return logs;
|
|
}
|
|
|
|
async assertLogCondition(condition: {
|
|
source: string;
|
|
level?: 'INFO' | 'WARNING' | 'ERROR';
|
|
message?: string | RegExp;
|
|
component?: string;
|
|
timeWindow?: number;
|
|
}): Promise<boolean> {
|
|
const source = this.sources.find(s => s.name === condition.source);
|
|
if (!source) throw new Error(`Unknown log source: ${condition.source}`);
|
|
|
|
const entries = await this.logParser.parseLogFile(source.path);
|
|
|
|
return entries.some(entry => {
|
|
if (condition.level && entry.level !== condition.level) return false;
|
|
if (condition.component && entry.component !== condition.component) return false;
|
|
if (condition.message) {
|
|
if (condition.message instanceof RegExp) {
|
|
if (!condition.message.test(entry.message)) return false;
|
|
} else {
|
|
if (entry.message !== condition.message) return false;
|
|
}
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
|
|
private async fetchAndFilterLogs(
|
|
source: LogSource,
|
|
startTime: Date,
|
|
endTime: Date
|
|
): Promise<LogEntry[]> {
|
|
const entries = await this.logParser.parseLogFile(source.path);
|
|
|
|
return entries.filter(entry => {
|
|
const timestamp = new Date(entry.timestamp);
|
|
return timestamp >= startTime && timestamp <= endTime;
|
|
});
|
|
}
|
|
|
|
async correlateTestAction(
|
|
actionName: string,
|
|
timestamp: Date,
|
|
timeWindow: number = 5000
|
|
): Promise<LogEntry[]> {
|
|
const correlatedEntries: LogEntry[] = [];
|
|
const startTime = new Date(timestamp.getTime() - timeWindow);
|
|
const endTime = new Date(timestamp.getTime() + timeWindow);
|
|
|
|
for (const source of this.sources) {
|
|
const entries = await this.fetchAndFilterLogs(source, startTime, endTime);
|
|
correlatedEntries.push(...entries);
|
|
}
|
|
|
|
return correlatedEntries.sort((a, b) =>
|
|
new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
|
|
);
|
|
}
|
|
|
|
async saveLogsToFile(
|
|
outputPath: string,
|
|
logs: Map<string, LogEntry[]>
|
|
): Promise<void> {
|
|
const output: Record<string, any> = {};
|
|
|
|
logs.forEach((entries, source) => {
|
|
output[source] = entries.map(entry => ({
|
|
timestamp: entry.timestamp,
|
|
level: entry.level,
|
|
component: entry.component,
|
|
message: entry.message,
|
|
context: entry.context
|
|
}));
|
|
});
|
|
|
|
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
await fs.writeFile(
|
|
outputPath,
|
|
JSON.stringify(output, null, 2)
|
|
);
|
|
}
|
|
} |