189 lines
8.4 KiB
JavaScript
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 (5 lines)', r.d.lines && r.d.lines.length === 5);
|
|
|
|
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));
|