Files
gronod 7690d959b3
CI / Security audit (push) Successful in 1m52s
Docs Check / Markdown lint (push) Successful in 1m37s
Build and Push Docker Image / build (push) Successful in 2m2s
Licence Check / Licence compatibility and copyright header verification (push) Failing after 2m33s
CI / Swagger Validation & Coverage (push) Successful in 3m17s
Docs Check / Mermaid diagram parse check (push) Successful in 3m31s
CI / Tests & coverage (push) Successful in 4m5s
fix: blocklist-search lookup against queue cache instead of downloadClientRegistry
Fixes the root cause of the regression from v1.7.16. The v1.7.16 fix
correctly cast arrQueueId to String, but the lookup was performed
against downloadClientRegistry.getAllDownloads() which returns raw
download client data (qBittorrent, SABnzbd, etc.) that never has
arrQueueId populated.

The fix now looks up the queue record directly from the Sonarr/Radarr
queue cache where record.id is the numeric queue ID, using String()
casting on both sides to handle the DOM-dataset (string) vs API
response (number) type difference.

Resolves Gitea Issue #48
Closes #48
2026-05-24 22:48:17 +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.