Files
sofarr/tests/README.md
T
gronod 7d3e6e6a47
Build and Push Docker Image / build (push) Successful in 39s
Docs Check / Markdown lint (push) Successful in 45s
Licence Check / Licence compatibility and copyright header verification (push) Successful in 1m12s
CI / Security audit (push) Successful in 1m30s
CI / Tests & coverage (push) Failing after 1m39s
Docs Check / Mermaid diagram parse check (push) Successful in 1m59s
test: add integration and unit tests for dashboard, emby, sonarr, radarr, sabnzbd routes
- tests/unit/dashboard.test.js: 58 unit tests covering all 12 pure helper
  functions in dashboard.js (sanitizeTagLabel, tagMatchesUser, getCoverArt,
  extractAllTags, extractUserTag, getImportIssues, getSonarrLink, getRadarrLink,
  canBlocklist, extractEpisode, gatherEpisodes, buildTagBadges)

- tests/integration/dashboard.test.js: 35 integration tests for
  /user-downloads (SAB+Sonarr, SAB+Radarr, qBit, showAll, paused queue,
  history matching, importIssues, wrong-user filtering), /status (admin guard,
  webhook check, failure handling), /webhook-metrics, /cover-art (all
  validation/proxy paths), /blocklist-search (guards, Sonarr, Radarr, failure)

- tests/integration/emby.test.js: 13 integration tests covering all 4 Emby
  routes (sessions, users, users/:id, session/:id/user) with auth guard,
  happy path, and upstream failure cases

- tests/integration/arrRoutes.test.js: 64 integration tests for Sonarr +
  Radarr (queue, history, series/movies, notifications CRUD, /test, /schema,
  /sofarr-webhook create+update+missing-config+failure) and SABnzbd (queue,
  history with custom params)

- vitest.config.js: raise global coverage thresholds (statements/functions/
  lines 20->55, branches 8->40) to reflect improved coverage
  (62.5% stmts, 42.6% branches, 64.1% funcs, 65.6% lines)

- tests/README.md: document new test files and update coverage table
2026-05-20 21:37:57 +01:00

4.9 KiB

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 from server/index.js. Tests import createApp() without triggering the log-file setup, process.exit() calls, or background poller in the entry point.
  • nock over vi.mock('axios') — Vitest's vi.mock only intercepts ESM import statements. Since auth.js uses CJS require('axios'), nock (which patches Node's http/https modules) is the correct tool for intercepting outbound requests.
  • SKIP_RATE_LIMIT=1 — All supertest requests originate from 127.0.0.1, which would quickly exhaust per-IP rate-limit windows. Setting this env var raises the limits to Number.MAX_SAFE_INTEGER in both the API limiter and the login limiter.
  • Isolated DATA_DIR — Each test worker gets a unique temp directory so tokenStore.js file 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.