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
- 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
98 lines
4.9 KiB
Markdown
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.
|