refactor(routes): analytics + tracker → throw pattern (batch 1)

Drop try/catch swallows + standardizeError; throw ValidationError/ApiError
(tracker preserves result.status via ApiError). Test harness updated to
simulate the terminal handler. 236/236.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
null 2026-07-06 14:59:29 -05:00
parent 26d976e272
commit 59c2917e1d
3 changed files with 17 additions and 29 deletions

View File

@ -2,13 +2,12 @@
import type { Req, Res, Next } from '../types/http'; import type { Req, Res, Next } from '../types/http';
const express = require('express'); const express = require('express');
const router = express.Router(); const router = express.Router();
const { standardizeError } = require('../middleware/errorFormatter.cts'); const { ValidationError } = require('../utils/apiError.cts');
const { getAnalyticsSummary } = require('../services/analyticsService.cts'); const { getAnalyticsSummary } = require('../services/analyticsService.cts');
router.get('/summary', (req: Req, res: Res) => { router.get('/summary', (req: Req, res: Res) => {
const result = getAnalyticsSummary(req.user.id, req.query); const result = getAnalyticsSummary(req.user.id, req.query);
if (result.error) if (result.error) throw ValidationError(result.error, 'month');
return res.status(400).json(standardizeError(result.error, 'VALIDATION_ERROR', 'month'));
res.json(result); res.json(result);
}); });

View File

@ -3,42 +3,23 @@ import type { Req, Res, Next } from '../types/http';
const express = require('express'); const express = require('express');
const router = express.Router(); const router = express.Router();
const { getTracker, getUpcomingBills, getOverdueCount } = require('../services/trackerService.cts'); const { getTracker, getUpcomingBills, getOverdueCount } = require('../services/trackerService.cts');
const { standardizeError } = require('../middleware/errorFormatter.cts'); const { ApiError, ValidationError } = require('../utils/apiError.cts');
// GET /api/tracker/overdue-count — lightweight count for sidebar badge // GET /api/tracker/overdue-count — lightweight count for sidebar badge
router.get('/overdue-count', (req: Req, res: Res) => { router.get('/overdue-count', (req: Req, res: Res) => {
try { res.json(getOverdueCount(req.user.id));
res.json(getOverdueCount(req.user.id));
} catch (err) {
console.error('[tracker/overdue-count]', err.message);
res.status(500).json(standardizeError('Failed to load overdue count', 'INTERNAL_ERROR'));
}
}); });
// GET /api/tracker?year=2026&month=5 // GET /api/tracker?year=2026&month=5
router.get('/', (req: Req, res: Res) => { router.get('/', (req: Req, res: Res) => {
try { const result = getTracker(req.user.id, req.query);
const result = getTracker(req.user.id, req.query); if (result.error) throw new ApiError('VALIDATION_ERROR', result.error, result.status || 400);
if (result.error) { res.json(result);
return res
.status(result.status || 400)
.json(standardizeError(result.error, 'VALIDATION_ERROR'));
}
res.json(result);
} catch (err) {
console.error('[tracker]', err.message);
res.status(500).json(standardizeError('Failed to load tracker data', 'INTERNAL_ERROR'));
}
}); });
// GET /api/tracker/upcoming?days=30 // GET /api/tracker/upcoming?days=30
router.get('/upcoming', (req: Req, res: Res) => { router.get('/upcoming', (req: Req, res: Res) => {
try { res.json(getUpcomingBills(req.user.id, req.query));
res.json(getUpcomingBills(req.user.id, req.query));
} catch (err) {
console.error('[tracker/upcoming]', err.message);
res.status(500).json(standardizeError('Failed to load upcoming bills', 'INTERNAL_ERROR'));
}
}); });
module.exports = router; module.exports = router;

View File

@ -164,7 +164,15 @@ test('GET /tracker returns a standardized error for an invalid month', async ()
resolve({ status: this.statusCode, data: d }); resolve({ status: this.statusCode, data: d });
}, },
}; };
handler(req, res); // Handlers now throw ApiError; simulate the terminal error handler.
try {
handler(req, res);
} catch (err) {
resolve({
status: err.status || 500,
data: { error: err.code, message: err.message, code: err.code, field: err.field ?? null },
});
}
}); });
assert.equal(result.status, 400); assert.equal(result.status, 400);
assert.equal(result.data.code, 'VALIDATION_ERROR', 'standardized shape, not a plain {error}'); assert.equal(result.data.code, 'VALIDATION_ERROR', 'standardized shape, not a plain {error}');