Files
sofarr/tests
Gronod 5fd55b4e1a
Some checks failed
Build and Push Docker Image / build (push) Successful in 49s
CI / Security audit (push) Successful in 1m23s
CI / Tests & coverage (push) Failing after 2m13s
test: add comprehensive test suite (115 tests, Vitest + supertest + nock)
Framework:
- Vitest v4 as test runner (fast ESM/CJS support, V8 coverage built-in)
- supertest for integration tests against createApp() factory
- nock for HTTP interception (works with CJS require('axios'), unlike vi.mock)

New files:
- vitest.config.js          — test config: node env, isolate, V8 coverage, per-file thresholds
- tests/setup.js             — isolated DATA_DIR per worker, SKIP_RATE_LIMIT, console suppression
- tests/README.md            — approach, structure, design decisions
- server/app.js              — testable Express factory (extracted from index.js side-effects)

Unit tests (91 tests):
- tests/unit/sanitizeError.test.js  — secret redaction: apikey, token, bearer, basic-auth URLs
- tests/unit/config.test.js         — JSON array + legacy single-instance config parsing
- tests/unit/requireAuth.test.js    — valid/invalid/tampered cookies, schema validation
- tests/unit/verifyCsrf.test.js     — double-submit pattern, timing-safe compare, safe methods
- tests/unit/qbittorrent.test.js    — formatBytes, formatEta, mapTorrentToDownload state map
- tests/unit/tokenStore.test.js     — store/get/clear lifecycle, TTL expiry, atomic disk write

Integration tests (24 tests):
- tests/integration/health.test.js  — /health and /ready endpoints
- tests/integration/auth.test.js    — full login/logout/me/csrf flows, input validation,
                                      cookie attributes, no token leakage, Emby mock via nock

Production code changes (minimal, no behaviour change):
- server/routes/auth.js: EMBY_URL captured at request-time (not module load) for testability
- server/routes/auth.js: loginLimiter max → Number.MAX_SAFE_INTEGER when SKIP_RATE_LIMIT set
- server/utils/sanitizeError.js: fix HEADER_PATTERN to redact full line (not just first token)

CI:
- .gitea/workflows/ci.yml: add parallel 'test' job (npm run test:coverage, artifact upload)
- package.json: add test/test:watch/test:coverage/test:ui scripts
- .gitignore: add coverage/
2026-05-17 07:45:33 +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
└── integration/
    ├── health.test.js         # GET /health and /ready endpoints
    └── auth.test.js           # Full login/logout/me/csrf flows via supertest + nock

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

The tested files meet these per-file minimums (enforced in CI):

File Lines Branches
server/app.js 85% 65%
server/routes/auth.js 85% 70%
server/middleware/requireAuth.js 75% 80%
server/utils/sanitizeError.js 60%
server/utils/config.js 50% 55%

dashboard.js and poller.js are large files requiring complex external-service mocks (Sonarr, Radarr, qBittorrent, Emby) and are tracked as future test coverage work.