e8037afbb8
Build and Push Docker Image / build (push) Successful in 1m7s
Licence Check / Licence compatibility and copyright header verification (push) Successful in 2m20s
CI / Security audit (push) Successful in 2m45s
CI / Tests & coverage (push) Successful in 3m18s
CI / Swagger Validation & Coverage (push) Successful in 3m29s
Add arrType field to Sonarr and Radarr history items for admin users to enable proper icon display in the recently downloaded section. This mirrors the existing behavior in active downloads where arrType is set by DownloadMatcher. Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Testing
Stack
| Layer | Tool |
|---|---|
| Test runner | Vitest v4 |
| HTTP integration | supertest |
| HTTP interception | nock (intercepts at Node http layer — works with CJS require('axios')) |
| Coverage | V8 (built-in, no Babel needed) |
Running tests
# Run all tests once
npm test
# Watch mode (re-runs on file change)
npm run test:watch
# With coverage report
npm run test:coverage
# Interactive UI
npm run test:ui
Coverage output lands in coverage/ (gitignored). Open coverage/index.html for the HTML report.
Structure
tests/
├── setup.js # Global setup: isolated DATA_DIR, SKIP_RATE_LIMIT, console suppression
├── unit/
│ ├── sanitizeError.test.js # Secret redaction patterns (API keys, tokens, passwords)
│ ├── config.test.js # JSON array + legacy single-instance config parsing
│ ├── requireAuth.test.js # Auth middleware: valid/invalid/tampered cookies
│ ├── verifyCsrf.test.js # CSRF double-submit cookie pattern + timing-safe compare
│ ├── qbittorrent.test.js # Pure utils: formatBytes, formatEta, mapTorrentToDownload
│ ├── tokenStore.test.js # JSON file token store: store/get/clear, TTL expiry
│ └── dashboard.test.js # Pure helper functions: getCoverArt, extractAllTags,
│ # extractUserTag, sanitizeTagLabel, tagMatchesUser,
│ # getImportIssues, getSonarrLink, getRadarrLink,
│ # canBlocklist, extractEpisode, gatherEpisodes, buildTagBadges
└── integration/
├── health.test.js # GET /health and /ready endpoints
├── auth.test.js # Full login/logout/me/csrf flows via supertest + nock
├── history.test.js # GET /api/history/recent: auth, filtering, deduplication
├── webhook.test.js # POST /api/webhook/sonarr+radarr: secret, validation,
│ # replay protection, metrics, security assertions
├── dashboard.test.js # GET /user-downloads (SAB+Sonarr, SAB+Radarr, qBit, showAll,
│ # paused queue, history, importIssues), GET /status,
│ # GET /webhook-metrics, GET /cover-art, POST /blocklist-search
├── emby.test.js # GET /sessions, /users, /users/:id, /session/:id/user
└── arrRoutes.test.js # Sonarr + Radarr: queue, history, series/movies, notifications
# CRUD, /test, /schema, /sofarr-webhook (create + update)
# SABnzbd: queue, history
Key design decisions
server/app.js— Express factory extracted fromserver/index.js. Tests importcreateApp()without triggering the log-file setup,process.exit()calls, or background poller in the entry point.- nock over
vi.mock('axios')— Vitest'svi.mockonly intercepts ESMimportstatements. Sinceauth.jsuses CJSrequire('axios'), nock (which patches Node'shttp/httpsmodules) is the correct tool for intercepting outbound requests. SKIP_RATE_LIMIT=1— All supertest requests originate from127.0.0.1, which would quickly exhaust per-IP rate-limit windows. Setting this env var raises the limits toNumber.MAX_SAFE_INTEGERin both the API limiter and the login limiter.- Isolated
DATA_DIR— Each test worker gets a unique temp directory sotokenStore.jsfile I/O never conflicts with a running dev server. createApp({ skipRateLimits: true })— The app factory accepts an option to disable the general API rate limiter in addition to the env var for the login-specific limiter.
Coverage targets
Global thresholds (enforced in CI via vitest.config.js):
| Metric | Threshold |
|---|---|
| Statements | 55% |
| Functions | 55% |
| Branches | 40% |
| Lines | 55% |
Notable per-file coverage after the current suite:
| File | Lines | Branches | Notes |
|---|---|---|---|
server/app.js |
~92% | ~71% | |
server/routes/auth.js |
~88% | ~78% | |
server/routes/dashboard.js |
~42% | ~25% | SSE /stream endpoint intentionally untested |
server/routes/emby.js |
100% | 100% | |
server/routes/radarr.js |
~87% | ~77% | |
server/routes/sonarr.js |
~89% | ~82% | |
server/routes/sabnzbd.js |
100% | 100% | |
server/routes/webhook.js |
~85% | ~79% | |
server/middleware/requireAuth.js |
~92% | ~81% | |
server/middleware/verifyCsrf.js |
100% | 80% | |
server/utils/sanitizeError.js |
100% | 75% | |
server/utils/config.js |
~70% | ~58% |
poller.js (background polling engine) and the SSE /stream endpoint in dashboard.js require time-based behaviour and long-lived HTTP connections; they remain tracked as future test coverage work.