feat: log failures, validate env at startup, load .env (findings 3, 4, 7)
Finding 3: fetch_error, not_found, mail_error and de onverwachte-fout branch loggen nu naam/url plus reden naar stderr; runCheck logt na elke run een regel met de tellingen per status, zodat een cron-run zichtbaar is in de container-logs. De recordCheckSuccess/recordCheckFailure-condities en alle return-waarden zijn ongewijzigd. Finding 4: server.js controleert bij het opstarten dat BASIC_AUTH_USER, BASIC_AUTH_PASS, SMTP_FROM en NOTIFY_EMAIL gezet zijn en stopt anders met exit code 1 in plaats van later opake 500's te geven. Finding 7: dotenv toegevoegd en als eerste regel in server.js geladen, zodat het gedocumenteerde `cp .env.example .env && npm start` echt werkt. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013gwc3aCiyQxmCWQke8RvEz
This commit is contained in:
@@ -17,6 +17,14 @@ function makeDeps(overrides = {}) {
|
||||
return { deps, failures, successes, mailCalls };
|
||||
}
|
||||
|
||||
// Failure paths log to stderr; capture it so the test output stays clean
|
||||
// while still proving the logging happened.
|
||||
function captureErrorLog(t) {
|
||||
const logged = [];
|
||||
t.mock.method(console, 'error', (...args) => { logged.push(args.join(' ')); });
|
||||
return logged;
|
||||
}
|
||||
|
||||
test('checkProduct stores baseline price without sending mail', async () => {
|
||||
const { deps, successes, mailCalls } = makeDeps();
|
||||
const product = { id: 1, name: 'X', url: 'https://x', last_price: null };
|
||||
@@ -48,7 +56,8 @@ test('checkProduct does not send mail or update when price unchanged', async ()
|
||||
assert.equal(successes.length, 1);
|
||||
});
|
||||
|
||||
test('checkProduct records fetch_error and does not touch price on network failure', async () => {
|
||||
test('checkProduct records fetch_error and does not touch price on network failure', async (t) => {
|
||||
const logged = captureErrorLog(t);
|
||||
const { deps, failures, successes } = makeDeps({
|
||||
fetchAndExtractPrice: async () => ({ price: null, method: null, error: new Error('down') }),
|
||||
});
|
||||
@@ -57,9 +66,12 @@ test('checkProduct records fetch_error and does not touch price on network failu
|
||||
assert.equal(result.status, 'fetch_error');
|
||||
assert.deepEqual(failures, [{ id: 4, status: 'fetch_error' }]);
|
||||
assert.equal(successes.length, 0);
|
||||
assert.equal(logged.length, 1);
|
||||
assert.match(logged[0], /fetch_error.*https:\/\/x.*down/);
|
||||
});
|
||||
|
||||
test('checkProduct records not_found when no price could be extracted', async () => {
|
||||
test('checkProduct records not_found when no price could be extracted', async (t) => {
|
||||
const logged = captureErrorLog(t);
|
||||
const { deps, failures, successes } = makeDeps({
|
||||
fetchAndExtractPrice: async () => ({ price: null, method: null, error: null }),
|
||||
});
|
||||
@@ -68,9 +80,12 @@ test('checkProduct records not_found when no price could be extracted', async ()
|
||||
assert.equal(result.status, 'not_found');
|
||||
assert.deepEqual(failures, [{ id: 5, status: 'not_found' }]);
|
||||
assert.equal(successes.length, 0);
|
||||
assert.equal(logged.length, 1);
|
||||
assert.match(logged[0], /not_found.*https:\/\/x/);
|
||||
});
|
||||
|
||||
test('checkProduct records mail_error and keeps old price when sending mail fails', async () => {
|
||||
test('checkProduct records mail_error and keeps old price when sending mail fails', async (t) => {
|
||||
const logged = captureErrorLog(t);
|
||||
const { deps, failures, successes } = makeDeps({
|
||||
sendMail: async () => { throw new Error('smtp down'); },
|
||||
});
|
||||
@@ -79,9 +94,12 @@ test('checkProduct records mail_error and keeps old price when sending mail fail
|
||||
assert.equal(result.status, 'mail_error');
|
||||
assert.deepEqual(failures, [{ id: 6, status: 'mail_error' }]);
|
||||
assert.equal(successes.length, 0);
|
||||
assert.equal(logged.length, 1);
|
||||
assert.match(logged[0], /mail_error.*https:\/\/x.*smtp down/);
|
||||
});
|
||||
|
||||
test('checkAllProducts processes every product independently, isolating failures', async () => {
|
||||
test('checkAllProducts processes every product independently, isolating failures', async (t) => {
|
||||
captureErrorLog(t);
|
||||
const { deps } = makeDeps();
|
||||
let calls = 0;
|
||||
const throwingDeps = {
|
||||
|
||||
Reference in New Issue
Block a user