Files
LogMaster/run-all-tests.js
Kevin 378b30cdc8
Some checks failed
LogMaster CI/CD / build-and-test (push) Has been cancelled
LogMaster CI/CD / docker (push) Has been cancelled
Fix: S3 key encoding env quote stripping test assertions remove unused import
2026-05-24 12:50:35 +02:00

189 lines
8.4 KiB
JavaScript

const http = require('http');
function req(port, method, path, data) {
return new Promise((resolve, reject) => {
const payload = data ? (typeof data === 'string' ? data : JSON.stringify(data)) : '';
const isJson = typeof data === 'object';
const r = http.request({
hostname: 'localhost', port, path, method,
headers: { 'Content-Type': isJson ? 'application/json' : 'text/plain', ...(payload ? { 'Content-Length': Buffer.byteLength(payload) } : {}) },
}, (res) => {
let body = '';
res.on('data', c => body += c);
res.on('end', () => { try { resolve({ s: res.statusCode, d: JSON.parse(body) }); } catch { resolve({ s: res.statusCode, d: body }); } });
});
r.on('error', reject);
if (payload) r.write(payload);
r.end();
});
}
let pass = 0, fail = 0;
function ok(n, c) { if (c) { console.log(' ✅ ' + n); pass++; } else { console.log(' ❌ ' + n); fail++; } }
async function main() {
console.log('\n ╔═══════════════════════════════════════╗');
console.log(' ║ Full Feature Test — All Services ║');
console.log(' ╚═══════════════════════════════════════╝');
// ── COLLECTOR ──
console.log('\n ── COLLECTOR (:8081) ──\n');
let r = await req(8081, 'GET', '/api/mode');
ok('Collector running', r.d.mode === 'collector');
r = await req(8081, 'POST', '/collect/app-server', '2026-05-04T10:00:00Z INFO [http] GET /dashboard 200 (12ms)\n2026-05-04T10:00:01Z WARN [http] Slow: GET /reports 3200ms\n2026-05-04T10:00:02Z ERROR [db] Query timeout SELECT * FROM orders\n2026-05-04T10:00:03Z INFO [auth] User admin@corp.com logged in\n2026-05-04T10:00:04Z FATAL [db] Connection lost to primary!');
ok('Ingest plain text (5 lines)', r.d.accepted === 5);
r = await req(8081, 'POST', '/collect/payment-svc/json', ['2026-05-04T10:01:00Z INFO Payment $49.99 processed', '2026-05-04T10:01:01Z ERROR Card declined order-555', '2026-05-04T10:01:02Z WARN Retry 2/3 for order-555']);
ok('Ingest JSON (3 lines)', r.d.accepted === 3);
r = await req(8081, 'POST', '/collect/auth-svc', '2026-05-04T10:02:00Z WARN Failed login: root from 10.0.0.99\n2026-05-04T10:02:01Z ERROR Account locked: root\n2026-05-04T10:02:02Z INFO Password reset: root');
ok('Ingest auth stream (3 lines)', r.d.accepted === 3);
r = await req(8081, 'GET', '/collector/streams');
ok('List streams (>=3)', r.d.length >= 3);
const names = r.d.map(s => s.id);
ok('app-server exists', names.includes('app-server'));
ok('payment-svc exists', names.includes('payment-svc'));
ok('auth-svc exists', names.includes('auth-svc'));
r = await req(8081, 'GET', '/collector/stream/app-server');
ok('Read app-server stream', r.d.lines !== undefined);
r = await req(8081, 'GET', '/collector/stream/payment-svc?tail=1');
ok('Tail=1 returns 1 line', r.d.lines && r.d.lines.length === 1);
r = await req(8081, 'GET', '/collector/config');
ok('Config: rotation exists', !!r.d.rotation);
ok('Config: UDP port 5140', r.d.udpPort === 5140);
r = await req(8081, 'DELETE', '/collector/stream/auth-svc');
ok('Delete auth-svc', r.d.ok === true);
r = await req(8081, 'GET', '/collector/streams');
ok('auth-svc removed', !r.d.some(s => s.id === 'auth-svc'));
// Re-create for viewer test
await req(8081, 'POST', '/collect/auth-svc', 'INFO re-created stream');
// ── VIEWER ──
console.log('\n ── VIEWER (:9090) ──\n');
r = await req(9090, 'GET', '/api/mode');
ok('Viewer running', r.d.mode === 'viewer');
r = await req(9090, 'GET', '/');
ok('HTML served (200)', r.s === 200);
r = await req(9090, 'GET', '/api/sources');
ok('File sources (>=4)', r.d.length >= 4);
r = await req(9090, 'GET', '/api/logs');
ok('All logs loaded (>100)', r.d.length > 100);
ok('Entry has .line', !!r.d[0].line);
ok('Entry has .source', !!r.d[0].source);
ok('Entry has .timestamp', r.d[0].timestamp !== undefined);
ok('Entry has .level', r.d[0].level !== undefined);
if (r.d.length > 0) {
const src = r.d[0].source;
const r2 = await req(9090, 'GET', '/api/logs?source=' + encodeURIComponent(src));
ok('Filter by source works', r2.d.length > 0);
}
r = await req(9090, 'GET', '/api/remote/streams');
ok('Remote streams from collector', r.d.length >= 2);
if (r.d.length > 0) {
const sid = r.d[0].id;
const r2 = await req(9090, 'GET', '/api/remote/stream/' + sid);
ok('Read remote stream: ' + sid, Array.isArray(r2.d) && r2.d.length > 0);
}
r = await req(9090, 'GET', '/api/config');
ok('Viewer config (theme=dark)', r.d.theme === 'dark');
r = await req(9090, 'POST', '/api/sources', { path: 'sample-logs/security-audit.log' });
ok('Add source via API', Array.isArray(r.d));
// ── MONITOR ──
console.log('\n ── MONITOR (:8082) ──\n');
r = await req(8082, 'GET', '/api/mode');
ok('Monitor running', r.d.mode === 'monitor');
r = await req(8082, 'GET', '/');
ok('Monitor HTML (200)', r.s === 200);
// Clean old rules
const old = await req(8082, 'GET', '/api/monitor/rules');
for (const rule of (old.d || [])) { await req(8082, 'DELETE', '/api/monitor/rule/' + rule.id); }
r = await req(8082, 'POST', '/api/monitor/rules', {
id: 'test-errors', name: 'Error & Fatal in app.log', enabled: true,
sources: ['sample-logs/app.log'], patterns: ['\\bERROR\\b', '\\bFATAL\\b'],
patternLogic: 'any', timeRange: { type: 'all' },
notifications: [], maxLinesInMessage: 10, cooldownMs: 5000,
});
ok('Create rule: Errors', r.d.ok);
r = await req(8082, 'POST', '/api/monitor/rules', {
id: 'test-security', name: 'Root Access (Telegram)', enabled: true,
sources: ['sample-logs/security-audit.log'],
patterns: ['root.*login', 'GRANT.*PRIVILEGES', 'DROP TABLE'],
patternLogic: 'any', timeRange: { type: 'all' },
notifications: [{ type: 'telegram', botToken: '8116790669:AAE_llOFDFSwDhPx40vZMBa6dGv3Xxa9ZiQ', chatId: '8599028500' }],
maxLinesInMessage: 5, cooldownMs: 5000,
schedule: { mode: 'daily', value: '07:00' },
});
ok('Create rule: Security + Telegram', r.d.ok);
r = await req(8082, 'POST', '/api/monitor/rules', {
id: 'test-slow', name: 'Slow Queries', enabled: true,
sources: ['sample-logs/app.log'], patterns: ['[Ss]low', 'timeout'],
patternLogic: 'any', timeRange: { type: 'all' },
notifications: [], maxLinesInMessage: 15, cooldownMs: 5000,
schedule: { mode: 'hourly', value: '2' },
});
ok('Create rule: Slow Queries', r.d.ok);
r = await req(8082, 'GET', '/api/monitor/rules');
ok('3 rules listed', r.d.length === 3);
r = await req(8082, 'POST', '/api/monitor/trigger/test-errors', {});
ok('Trigger Errors: ' + (r.d.totalMatches || 0) + ' matches', (r.d.totalMatches || 0) > 0);
r = await req(8082, 'POST', '/api/monitor/trigger/test-security', {});
ok('Trigger Security: ' + (r.d.totalMatches || 0) + ' matches → Telegram', (r.d.totalMatches || 0) > 0);
r = await req(8082, 'POST', '/api/monitor/trigger/test-slow', {});
ok('Trigger Slow: ' + (r.d.totalMatches || 0) + ' matches', (r.d.totalMatches || 0) >= 0);
r = await req(8082, 'GET', '/api/monitor/results');
ok('Results stored (>=' + Math.min(r.d.length, 2) + ')', r.d.length >= 2);
r = await req(8082, 'POST', '/api/monitor/test-notification', {
type: 'telegram', botToken: '8116790669:AAE_llOFDFSwDhPx40vZMBa6dGv3Xxa9ZiQ', chatId: '8599028500',
});
ok('Telegram test notification sent', r.d.ok);
r = await req(8082, 'PUT', '/api/monitor/rule/test-errors', { name: 'Errors (updated)' });
ok('Update rule', r.d.ok);
r = await req(8082, 'DELETE', '/api/monitor/rule/test-slow');
ok('Delete rule', r.d.ok);
r = await req(8082, 'GET', '/api/monitor/config');
ok('Monitor config endpoint', !!r.d.scanIntervalMs);
// ── SUMMARY ──
console.log('\n ═══════════════════════════════════════');
console.log(' ' + pass + ' passed, ' + fail + ' failed');
console.log(' ═══════════════════════════════════════');
console.log('\n Collector → http://localhost:8081');
console.log(' Viewer → http://localhost:9090');
console.log(' Monitor → http://localhost:8082\n');
}
main().catch(e => console.error('FATAL:', e.message));