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

98 lines
4.9 KiB
Markdown

# Testing
## Stack
| Layer | Tool |
|---|---|
| Test runner | [Vitest](https://vitest.dev/) v4 |
| HTTP integration | [supertest](https://github.com/ladjs/supertest) |
| HTTP interception | [nock](https://github.com/nock/nock) (intercepts at Node http layer — works with CJS `require('axios')`) |
| Coverage | V8 (built-in, no Babel needed) |
## Running tests
```bash
# 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.