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'
);
});