Files
sofarr/tests
gronod 3e6af1bff2
Licence Check / Licence compatibility and copyright header verification (push) Successful in 38s
CI / Security audit (push) Successful in 53s
CI / Tests & coverage (push) Successful in 1m9s
Build and Push Docker Image / build (push) Failing after 32s
Add frontend unit tests with Vitest + jsdom
- Create tests/frontend/utils/format.test.js with 24 tests for formatting utilities
- Create tests/frontend/ui/downloads.test.js with 10 tests for DOM rendering functions
- Update vitest.config.js to support jsdom environment for frontend tests
- All 34 tests pass and cover edge cases (null, zero, large numbers, DOM structure)
2026-05-21 00:07:41 +01:00
..

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.