const test = require('node:test'); const assert = require('node:assert/strict'); const { checkProduct, checkAllProducts } = require('../src/checkProducts'); function makeDeps(overrides = {}) { const failures = []; const successes = []; const mailCalls = []; const deps = { fetchAndExtractPrice: async () => ({ price: 10, method: 'meta', error: null }), sendMail: async (payload) => { mailCalls.push(payload); }, recordCheckFailure: (id, status) => failures.push({ id, status }), recordCheckSuccess: (id, data) => successes.push({ id, ...data }), now: () => '2026-09-05T08:00:00.000Z', ...overrides, }; return { deps, failures, successes, mailCalls }; } 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 }; const result = await checkProduct(product, deps); assert.equal(result.status, 'ok'); assert.equal(result.changed, false); assert.equal(mailCalls.length, 0); assert.deepEqual(successes, [{ id: 1, price: 10, checkedAt: '2026-09-05T08:00:00.000Z' }]); }); test('checkProduct sends mail and updates price when price changed', async () => { const { deps, successes, mailCalls } = makeDeps(); const product = { id: 2, name: 'X', url: 'https://x', last_price: 15 }; const result = await checkProduct(product, deps); assert.equal(result.status, 'ok'); assert.equal(result.changed, true); assert.equal(mailCalls.length, 1); assert.deepEqual(mailCalls[0], { name: 'X', url: 'https://x', oldPrice: 15, newPrice: 10 }); assert.equal(successes.length, 1); }); test('checkProduct does not send mail or update when price unchanged', async () => { const { deps, successes, mailCalls } = makeDeps(); const product = { id: 3, name: 'X', url: 'https://x', last_price: 10 }; const result = await checkProduct(product, deps); assert.equal(result.status, 'ok'); assert.equal(result.changed, false); assert.equal(mailCalls.length, 0); assert.equal(successes.length, 1); }); test('checkProduct records fetch_error and does not touch price on network failure', async () => { const { deps, failures, successes } = makeDeps({ fetchAndExtractPrice: async () => ({ price: null, method: null, error: new Error('down') }), }); const product = { id: 4, name: 'X', url: 'https://x', last_price: 10 }; const result = await checkProduct(product, deps); assert.equal(result.status, 'fetch_error'); assert.deepEqual(failures, [{ id: 4, status: 'fetch_error' }]); assert.equal(successes.length, 0); }); test('checkProduct records not_found when no price could be extracted', async () => { const { deps, failures, successes } = makeDeps({ fetchAndExtractPrice: async () => ({ price: null, method: null, error: null }), }); const product = { id: 5, name: 'X', url: 'https://x', last_price: 10 }; const result = await checkProduct(product, deps); assert.equal(result.status, 'not_found'); assert.deepEqual(failures, [{ id: 5, status: 'not_found' }]); assert.equal(successes.length, 0); }); test('checkProduct records mail_error and keeps old price when sending mail fails', async () => { const { deps, failures, successes } = makeDeps({ sendMail: async () => { throw new Error('smtp down'); }, }); const product = { id: 6, name: 'X', url: 'https://x', last_price: 15 }; const result = await checkProduct(product, deps); assert.equal(result.status, 'mail_error'); assert.deepEqual(failures, [{ id: 6, status: 'mail_error' }]); assert.equal(successes.length, 0); }); test('checkAllProducts processes every product independently, isolating failures', async () => { const { deps } = makeDeps(); let calls = 0; const throwingDeps = { ...deps, fetchAndExtractPrice: async () => { calls += 1; if (calls === 1) throw new Error('boom'); return { price: 10, method: 'meta', error: null }; }, }; const products = [ { id: 1, name: 'A', url: 'https://a', last_price: null }, { id: 2, name: 'B', url: 'https://b', last_price: null }, ]; const results = await checkAllProducts(products, throwingDeps); assert.equal(results.length, 2); assert.equal(results[0].status, 'error'); assert.equal(results[1].status, 'ok'); });