- 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.
106 lines
No EOL
3.4 KiB
TypeScript
106 lines
No EOL
3.4 KiB
TypeScript
import { readFile } from 'fs/promises';
|
|
import { Client, SFTPWrapper } from 'ssh2';
|
|
import { STAGING_CONFIG } from '../../../playwright.config';
|
|
|
|
export interface LogEntry {
|
|
timestamp: string;
|
|
level: 'INFO' | 'WARNING' | 'ERROR';
|
|
component: string;
|
|
message: string;
|
|
context?: Record<string, unknown>;
|
|
}
|
|
|
|
export class LogParser {
|
|
private sshConfig = {
|
|
host: STAGING_CONFIG.ip,
|
|
username: STAGING_CONFIG.sshUser,
|
|
path: STAGING_CONFIG.path
|
|
};
|
|
|
|
async parseLogFile(path: string): Promise<LogEntry[]> {
|
|
const logContent = await this.fetchLogContent(path);
|
|
return this.parseLogContent(logContent);
|
|
}
|
|
|
|
async findRelatedEntries(entry: LogEntry): Promise<LogEntry[]> {
|
|
const allLogs = await this.parseLogFile('wordpress.log');
|
|
return allLogs.filter(log =>
|
|
log.component === entry.component &&
|
|
Math.abs(new Date(log.timestamp).getTime() - new Date(entry.timestamp).getTime()) < 5000
|
|
);
|
|
}
|
|
|
|
async validateLogSequence(entries: LogEntry[], expectedFlow: string[]): Promise<boolean> {
|
|
let currentIndex = 0;
|
|
|
|
for (const expected of expectedFlow) {
|
|
let found = false;
|
|
while (currentIndex < entries.length) {
|
|
if (entries[currentIndex].message.includes(expected)) {
|
|
found = true;
|
|
break;
|
|
}
|
|
currentIndex++;
|
|
}
|
|
if (!found) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private async fetchLogContent(path: string): Promise<string> {
|
|
const conn = new Client();
|
|
|
|
return new Promise<string>((resolve, reject) => {
|
|
conn.on('ready', () => {
|
|
conn.sftp((err: Error | undefined, sftp: SFTPWrapper) => {
|
|
if (err) {
|
|
reject(err);
|
|
return;
|
|
}
|
|
|
|
const remotePath = `${this.sshConfig.path}/wp-content/debug.log`;
|
|
sftp.readFile(remotePath, (err: Error | undefined, data: Buffer) => {
|
|
if (err) {
|
|
reject(err);
|
|
return;
|
|
}
|
|
resolve(data.toString());
|
|
conn.end();
|
|
});
|
|
});
|
|
}).connect(this.sshConfig);
|
|
});
|
|
}
|
|
|
|
private parseLogContent(content: string): LogEntry[] {
|
|
const entries: LogEntry[] = [];
|
|
|
|
content.split('\n')
|
|
.filter(line => line.trim())
|
|
.forEach(line => {
|
|
const match = line.match(/\[(.*?)\]\s+(\w+):\s+\[(\w+)\]\s+(.*)/);
|
|
if (!match) return;
|
|
|
|
entries.push({
|
|
timestamp: match[1],
|
|
level: match[2] as 'INFO' | 'WARNING' | 'ERROR',
|
|
component: match[3],
|
|
message: match[4],
|
|
context: this.extractContext(match[4])
|
|
});
|
|
});
|
|
|
|
return entries;
|
|
}
|
|
|
|
private extractContext(message: string): Record<string, unknown> {
|
|
const contextMatch = message.match(/\{.*\}/);
|
|
if (!contextMatch) return {};
|
|
|
|
try {
|
|
return JSON.parse(contextMatch[0]);
|
|
} catch {
|
|
return {};
|
|
}
|
|
}
|
|
} |