const assert = require('node:assert/strict'); const http = require('node:http'); const test = require('node:test'); const { AgrarmonitorConnector, MemoryCookieStore } = require('../dist'); const PASSWORT = 'sup3rgeheim-passwort'; const SESSION_VALUE = 'abcdef0123456789abcdef'; const loginPage = ` Anmeldung - AGRARMONITOR
`; function createServer() { let loggedIn = false; const requests = []; const server = http.createServer((req, res) => { requests.push({ method: req.method, url: req.url }); if (req.method === 'GET' && req.url === '/') { res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); res.end(loggedIn ? 'Mein AM' : loginPage); return; } // Laeuft die Session ab, liefert die Seite wieder das Login-Markup aus. if (req.method === 'GET' && req.url === '/geschuetzt') { if (!loggedIn) { res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); res.end(loginPage); return; } res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); res.end('Geschuetzter Inhalt'); return; } if (req.method === 'POST' && req.url === '/login/api/login.php') { let body = ''; req.on('data', chunk => { body += chunk; }); req.on('end', () => { loggedIn = true; res.writeHead(200, { 'Content-Type': 'application/json', 'Set-Cookie': `agrar_session=${SESSION_VALUE}; Path=/`, }); res.end(JSON.stringify({ success: true })); }); return; } res.writeHead(404); res.end('not found'); }); return { server, requests, expireSession() { loggedIn = false; }, }; } function createRecordingLogger() { const entries = []; const record = level => (message, meta) => entries.push({ level, message, meta }); return { entries, debug: record('debug'), info: record('info'), warn: record('warn'), error: record('error'), debugEntries: () => entries.filter(entry => entry.level === 'debug'), text: () => entries.map(entry => `${entry.message} ${JSON.stringify(entry.meta ?? null)}`).join('\n'), }; } async function startConnector(t, { logger } = {}) { const harness = createServer(); await new Promise(resolve => harness.server.listen(0, '127.0.0.1', resolve)); t.after(() => new Promise(resolve => harness.server.close(resolve))); const connector = new AgrarmonitorConnector({ baseUrl: `http://127.0.0.1:${harness.server.address().port}`, username: 'demo', password: PASSWORT, cookieStore: new MemoryCookieStore(), logger, }); await connector.init(); return { connector, ...harness }; } function loginPostEntries(logger) { return logger.debugEntries().filter(entry => entry.meta?.url === '/login/api/login.php'); } test('logs cookie snapshots before and after the login request', async t => { const logger = createRecordingLogger(); await startConnector(t, { logger }); const [request, response, ...rest] = loginPostEntries(logger); assert.equal(rest.length, 0, 'genau ein Request- und ein Response-Eintrag fuer den Login-POST'); assert.equal(request?.meta.phase, 'request'); assert.equal(request?.meta.method, 'POST'); assert.ok(Array.isArray(request?.meta.cookies), 'Cookies vor dem Request werden protokolliert'); assert.equal(response?.meta.phase, 'response'); assert.equal(response?.meta.status, 200); const cookieNames = response.meta.cookies.map(cookie => cookie.name); assert.ok(cookieNames.includes('agrar_session'), 'Cookies nach dem Request enthalten die neue Session'); assert.deepEqual(response.meta.cookieChanges.added, ['agrar_session']); }); test('masks cookie values and never logs the password', async t => { const logger = createRecordingLogger(); await startConnector(t, { logger }); const text = logger.text(); assert.ok(!text.includes(PASSWORT), 'das Passwort darf in keinem Log-Eintrag stehen'); assert.ok(!text.includes(SESSION_VALUE), 'der volle Cookie-Wert darf nicht im Log stehen'); const response = loginPostEntries(logger).at(-1); const session = response.meta.cookies.find(cookie => cookie.name === 'agrar_session'); assert.equal(session.value, `abcd…(len=${SESSION_VALUE.length})`); assert.equal(session.path, '/'); }); test('logs the re-login triggered by an expired session', async t => { const logger = createRecordingLogger(); const { connector, expireSession } = await startConnector(t, { logger }); logger.entries.length = 0; expireSession(); const response = await connector.http.get('/geschuetzt'); assert.equal(response.status, 200); assert.match(response.data, /Geschuetzter Inhalt/); const retry = logger .debugEntries() .find(entry => entry.meta?.phase === 'retry' && entry.meta?.url === '/geschuetzt'); assert.ok(retry, 'der wiederholte Request wird protokolliert'); assert.equal(retry.meta.method, 'GET'); assert.ok(Array.isArray(retry.meta.cookies), 'Cookies des Wiederholungs-Requests werden protokolliert'); assert.ok(loginPostEntries(logger).length >= 2, 'der erneute Login wird ebenfalls protokolliert'); }); test('stays silent when the logger has no debug method', async t => { const entries = []; const logger = { info: (message, meta) => entries.push({ message, meta }) }; await startConnector(t, { logger }); assert.ok(entries.length > 0, 'info-Meldungen bleiben erhalten'); assert.ok( entries.every(entry => entry.meta?.phase === undefined), 'ohne debug-Methode werden keine Request-Details ausgegeben' ); });