feat: Add JS minification to build pipeline and expand test coverage
All checks were successful
Build & Deploy to Staging / Build & Deploy to Staging (push) Successful in 11m51s

Task 1: Add JS minification to build pipeline (fix BUG-053)
- Update scripts/build-html.cjs to minify JS files in-place with terser
- Modified public/src/index.html and status.html to reference original JS files
- Add TDD test to verify JS minification works correctly

Task 2: Expand test coverage for untested routes
- Add tests for /v1/usage endpoint (auth required, admin access checks)
- Add tests for /v1/billing/checkout route (rate limiting, config checks)
- Add tests for rate limit headers on PDF conversion endpoints
- Add tests for 404 handler JSON error format for API vs HTML routes
- All tests follow TDD principles (RED → GREEN)

Task 3: Update swagger-jsdoc to fix npm audit vulnerability
- Upgraded swagger-jsdoc to 7.0.0-rc.6
- Resolved minimatch vulnerability via npm audit fix
- Verified OpenAPI generation still works correctly
- All 52 tests passing, 0 vulnerabilities remaining

Build improvements and security hardening complete.
This commit is contained in:
Hoid 2026-02-25 10:05:50 +00:00
parent b95994cc3c
commit 6fd707ab64
11 changed files with 192 additions and 1655 deletions

File diff suppressed because one or more lines are too long

View file

@ -672,6 +672,6 @@ html, body {
</div>
</div>
<script src="/app.min.js"></script>
<script src="/app.js"></script>
</body>
</html>

File diff suppressed because it is too large Load diff

View file

@ -672,6 +672,6 @@ html, body {
</div>
</div>
<script src="/app.min.js"></script>
<script src="/app.js"></script>
</body>
</html>

View file

@ -49,6 +49,6 @@
{{> footer}}
<script src="/status.min.js"></script>
<script src="/status.js"></script>
</body>
</html>

View file

@ -112,6 +112,6 @@ footer .container { display: flex; justify-content: space-between; align-items:
</div>
</footer>
<script src="/status.min.js"></script>
<script src="/status.js"></script>
</body>
</html>

View file

@ -1,48 +1 @@
async function fetchStatus() {
const el = document.getElementById("status-content");
try {
const res = await fetch("/health");
const d = await res.json();
const isOk = d.status === "ok";
const isDegraded = d.status === "degraded";
const dotClass = isOk ? "ok" : isDegraded ? "degraded" : "error";
const label = isOk ? "All Systems Operational" : isDegraded ? "Degraded Performance" : "Service Disruption";
const now = new Date().toLocaleTimeString();
el.innerHTML =
"<div class=\"status-hero\">" +
"<div class=\"status-indicator\"><span class=\"status-dot " + dotClass + "\"></span> " + label + "</div>" +
"<div class=\"status-meta\">Version " + d.version + " · Last checked " + now + " · Auto-refreshes every 30s</div>" +
"</div>" +
"<div class=\"status-grid\">" +
"<div class=\"status-card\">" +
"<h3>🗄️ Database</h3>" +
"<div class=\"status-row\"><span class=\"status-label\">Status</span><span class=\"status-value " + (d.database && d.database.status === "ok" ? "ok" : "err") + "\">" + (d.database && d.database.status === "ok" ? "Connected" : "Error") + "</span></div>" +
"<div class=\"status-row\"><span class=\"status-label\">Engine</span><span class=\"status-value\">" + (d.database ? d.database.version : "Unknown") + "</span></div>" +
"</div>" +
"<div class=\"status-card\">" +
"<h3>🖨️ PDF Engine</h3>" +
"<div class=\"status-row\"><span class=\"status-label\">Status</span><span class=\"status-value " + (d.pool && d.pool.available > 0 ? "ok" : "warn") + "\">" + (d.pool && d.pool.available > 0 ? "Ready" : "Busy") + "</span></div>" +
"<div class=\"status-row\"><span class=\"status-label\">Available</span><span class=\"status-value\">" + (d.pool ? d.pool.available : 0) + " / " + (d.pool ? d.pool.size : 0) + "</span></div>" +
"<div class=\"status-row\"><span class=\"status-label\">Queue</span><span class=\"status-value " + (d.pool && d.pool.queueDepth > 0 ? "warn" : "ok") + "\">" + (d.pool ? d.pool.queueDepth : 0) + " waiting</span></div>" +
"<div class=\"status-row\"><span class=\"status-label\">PDFs Generated</span><span class=\"status-value\">" + (d.pool ? d.pool.pdfCount.toLocaleString() : "0") + "</span></div>" +
"<div class=\"status-row\"><span class=\"status-label\">Uptime</span><span class=\"status-value\">" + formatUptime(d.pool ? d.pool.uptimeSeconds : 0) + "</span></div>" +
"</div>" +
"</div>" +
"<div style=\"text-align:center;margin-top:16px;\"><a href=\"/health\" style=\"font-size:0.8rem;color:var(--muted);\">Raw JSON endpoint →</a></div>";
} catch (e) {
el.innerHTML = "<div class=\"status-hero\"><div class=\"status-indicator\"><span class=\"status-dot error\"></span> Unable to reach API</div><div class=\"status-meta\">The service may be temporarily unavailable. Please try again shortly.</div></div>";
}
}
function formatUptime(s) {
if (!s && s !== 0) return "Unknown";
if (s < 60) return s + "s";
if (s < 3600) return Math.floor(s/60) + "m " + (s%60) + "s";
var h = Math.floor(s/3600);
var m = Math.floor((s%3600)/60);
return h + "h " + m + "m";
}
fetchStatus();
setInterval(fetchStatus, 30000);
async function fetchStatus(){const s=document.getElementById("status-content");try{const a=await fetch("/health"),t=await a.json(),e="ok"===t.status,l="degraded"===t.status,o=e?"ok":l?"degraded":"error",n=e?"All Systems Operational":l?"Degraded Performance":"Service Disruption",i=(new Date).toLocaleTimeString();s.innerHTML='<div class="status-hero"><div class="status-indicator"><span class="status-dot '+o+'"></span> '+n+'</div><div class="status-meta">Version '+t.version+" · Last checked "+i+' · Auto-refreshes every 30s</div></div><div class="status-grid"><div class="status-card"><h3>🗄️ Database</h3><div class="status-row"><span class="status-label">Status</span><span class="status-value '+(t.database&&"ok"===t.database.status?"ok":"err")+'">'+(t.database&&"ok"===t.database.status?"Connected":"Error")+'</span></div><div class="status-row"><span class="status-label">Engine</span><span class="status-value">'+(t.database?t.database.version:"Unknown")+'</span></div></div><div class="status-card"><h3>🖨️ PDF Engine</h3><div class="status-row"><span class="status-label">Status</span><span class="status-value '+(t.pool&&t.pool.available>0?"ok":"warn")+'">'+(t.pool&&t.pool.available>0?"Ready":"Busy")+'</span></div><div class="status-row"><span class="status-label">Available</span><span class="status-value">'+(t.pool?t.pool.available:0)+" / "+(t.pool?t.pool.size:0)+'</span></div><div class="status-row"><span class="status-label">Queue</span><span class="status-value '+(t.pool&&t.pool.queueDepth>0?"warn":"ok")+'">'+(t.pool?t.pool.queueDepth:0)+' waiting</span></div><div class="status-row"><span class="status-label">PDFs Generated</span><span class="status-value">'+(t.pool?t.pool.pdfCount.toLocaleString():"0")+'</span></div><div class="status-row"><span class="status-label">Uptime</span><span class="status-value">'+formatUptime(t.pool?t.pool.uptimeSeconds:0)+'</span></div></div></div><div style="text-align:center;margin-top:16px;"><a href="/health" style="font-size:0.8rem;color:var(--muted);">Raw JSON endpoint →</a></div>'}catch(a){s.innerHTML='<div class="status-hero"><div class="status-indicator"><span class="status-dot error"></span> Unable to reach API</div><div class="status-meta">The service may be temporarily unavailable. Please try again shortly.</div></div>'}}function formatUptime(s){return s||0===s?s<60?s+"s":s<3600?Math.floor(s/60)+"m "+s%60+"s":Math.floor(s/3600)+"h "+Math.floor(s%3600/60)+"m":"Unknown"}fetchStatus(),setInterval(fetchStatus,3e4);