Repository navigation
Testing Guide
qbt-rules has a real pytest suite covering every module — this page is a map of what's there and how to run it, not a tutorial on pytest itself.
tests/
├── unit/ # One file per module, isolated from external services
│ ├── test_api.py
│ ├── test_arguments.py
│ ├── test_cli.py
│ ├── test_config.py
│ ├── test_engine/
│ ├── test_errors.py
│ ├── test_logging.py
│ ├── test_queue_manager.py
│ ├── test_redis_queue.py
│ ├── test_resolver.py
│ ├── test_resolver_edge_cases.py
│ ├── test_server.py
│ ├── test_sqlite_queue.py
│ ├── test_utils.py
│ └── test_worker.py
└── integration/ # Cross-module, end-to-end scenarios
├── test_advanced_example.py
├── test_full_stack.py
├── test_resolver_e2e.py
├── test_resolver_integration.py
└── test_rule_execution.py
test_engine/ being a directory rather than a single file reflects how much surface engine.py has (ConditionEvaluator, ActionExecutor, RulesEngine — see Architecture Internals).
# Everything
pytest
# One file
pytest tests/unit/test_resolver.py
# Only unit tests, skip integration
pytest tests/unit
# Skip anything marked slow or requiring Redis
pytest -m "not slow and not redis"Three custom markers are registered in pyproject.toml (--strict-markers means an unregistered marker is a hard error, not a silent no-op):
| Marker | Meaning |
|---|---|
slow |
Long-running test; deselect with -m "not slow" for a fast local loop |
integration |
Exercises more than one module together |
redis |
Requires a live Redis instance; deselect with -m "not redis" if you don't have one running |
Coverage is on by default (--cov=qbt_rules --cov-report=term-missing --cov-report=html is baked into pyproject.toml's addopts, so plain pytest always reports it):
pytest
# summary prints to the terminal; full HTML report lands in htmlcov/
open htmlcov/index.htmlIf you're following Extending the Engine to add an operator or action:
- A new operator in
ConditionEvaluator._apply_operatorbelongs intests/unit/test_engine/— assert it directly against both scalar and collection field values (collections exercise the ANY/ALL-match branching described in Conditions). - A new action in
ActionExecutorbelongs in the same directory — assert both the "changes something" path and the idempotent skip path (if applicable) against a mockedapi.pyclient, the same pattern the built-in actions use. - A new queue backend under
queue_backends/should implement the same test contracttest_sqlite_queue.py/test_redis_queue.pyalready exercise againstqueue_manager.py's abstract interface, so it's verified to be a drop-in replacement. - Anything touching the resolver (
refs/$ref/${vars.x}) has both a focused unit suite (test_resolver.py,test_resolver_edge_cases.py) and an end-to-end integration suite (test_resolver_e2e.py,test_resolver_integration.py) — new resolver behavior should get coverage in both, since edge cases (circular refs, type mismatches) are easy to get right in isolation and wrong once a real rule file is involved.
Before opening a pull request, run the full suite plus a real config validation pass — see Contributing for the complete pre-PR checklist.
qbt-rules | Client-server automation engine for qBittorrent
GitHub • Docker Image • Releases • Issues • Discussions • Contributing • License
Requirements: Docker • qBittorrent with Web UI enabled
Released into the public domain under the Unlicense
- Rules Architecture
- Contexts
- Conditions
- Actions
- Available Fields
- Reusable References
- HTTP API Reference
- Dashboard
- Metrics
- 🚀 Client-server architecture (HTTP API + worker)
- 📦 Persistent job queue (SQLite or Redis)
- ♻️ Reusable references (
$ref,${vars.x}) - 🐳 Docker-first deployment