tests/ at the monorepo root for IRC infrastructure, integration, and protocol tests, and apps/bridge/tests/ for the bridge service. You run everything with just test-all, or target individual categories for faster feedback.
Quick reference
| Command | What it runs | Docker needed? |
|---|---|---|
just test | Root pytest suite (tests/) | Integration and e2e tests build images |
just test-all | Root suite + bridge tests | Same as above |
just bridge test | Bridge tests only (apps/bridge/tests/) | No |
uv run pytest tests/unit/ | Unit tests only | No |
uv run pytest tests/integration/ | Integration tests | Yes (builds images) |
uv run pytest tests/e2e/ | End-to-end tests | Yes (builds images) |
uv run pytest tests/protocol/ | IRC protocol message tests | No |
uv run pytest apps/bridge/tests/ | All bridge tests | No |
Test directory layout
Running tests by category
Unit tests
Unit tests validate configuration parsing, environment variables, and Docker client logic without starting any containers. They are the fastest tests to run.Integration tests
Integration tests build Docker images and spin up real UnrealIRCd and Atheme containers usingpytest-docker-tools. They test IRC protocol compliance, service integration, and monitoring.
Warning: Integration tests build fresh Docker images on each run. In constrained environments (CI runners, VMs with limited resources), they may time out. Prefer tests/unit/ for quick validation.
End-to-end tests
E2E tests exercise full-stack workflows with real containers.Protocol tests
Protocol-level tests validate IRC message parsing and formatting without network connections.Bridge tests
Bridge tests mock at the adapter level — no real Discord, IRC, or XMPP connections are made. TheBridgeTestHarness sets up the event bus, channel router, relay, and mock adapters so you can simulate messages and verify routing.
Filtering by marker
pytest markers let you select tests by category:pyproject.toml under [tool.pytest.ini_options]. Key markers include: unit, integration, docker, irc, e2e, slow, network, atheme, webpanel, ssl, performance, protocol, and various IRC specification markers (RFC1459, RFC2812, IRCv3).
Pytest fixtures
Fixtures are defined intests/conftest.py (root) and apps/bridge/tests/conftest.py (bridge). The root conftest provides a rich set of shared fixtures.
Session-scoped fixtures
These are created once per test session:| Fixture | Description |
|---|---|
docker_client | Docker API client (skips if Docker is unavailable) |
project_root / repo_root | Monorepo root Path |
compose_file | Path to root compose.yaml |
setup_test_environment | Sets TESTING=true and creates temp directories (autouse) |
Function-scoped fixtures
These are created fresh for each test:| Fixture | Description |
|---|---|
prepared_config_dir | Temp directory with copied UnrealIRCd config files |
temp_dir | Clean temporary directory (tmp_path alias) |
| `sampl | |
controller | UnrealIRCd controller with Docker container support |
mock_requests_get | Patched requests.get returning {"status": "ok"} |
cleanup_files | Track and auto-cleanup files/dirs created during tests |
test_config | Parametrised fixture providing "minimal" and "full" configs |
Docker container fixtures
The root conftest usespytest-docker-tools to build and run containers:
unrealircd_container in your test to get a running UnrealIRCd instance with dynamically mapped ports.
Mocking patterns
Root test suite
The root suite usespytest-mock (the mocker fixture) for standard mocking:
BaseServerTestCase class (in tests/utils/base_test_cases.py) provides an irctest-style interface with connectClient(), sendLine(), getMessage(), and assertion helpers like assertMessageMatch().
Bridge test suite
The bridge uses a custom mock adapter pattern inspired by dpytest. Instead of connecting to real services, mock adapters capture events through the sameaccept_event/push_event interface:
Property-based testing
The bridge includes property-based tests using Hypothesis. These live inapps/bridge/tests/test_property_based.py and verify properties like message formatting invariants across randomly generated inputs.
The root pyproject.toml includes hypothesis>=6.151.9 in dev dependencies.
Writing new tests
Adding a root test
- Create a file in the appropriate directory (
tests/unit/,tests/integration/, etc.) - Name it
test_*.pyso pytest discovers it - Use fixtures from
tests/conftest.py— request them as function parameters - Add markers if the test has special requirements:
Adding a bridge test
- Create a file in
apps/bridge/tests/ - Use the
BridgeTestHarnessfor message routing tests, or test components directly - Bridge tests are async — use
@pytest.mark.asyncio:
Running a subset of tests
CI pipeline integration
The CI pipeline is defined in.github/workflows/ci.yml. It uses path-based filtering so only relevant jobs run when files change.
Pipeline structure
Thechanges job uses dorny/paths-filter to detect which areas of the monorepo were modified, then triggers downstream jobs conditionally:
| Job | Trigger paths | What it does |
|---|---|---|
lint-irc | apps/unrealircd/**, apps/atheme/**, tests/**, scripts/** | Shell linting (shellcheck) |
lint-xmpp | apps/prosody/** | Lua linting (luacheck) |
lint-bridge | apps/bridge/** | ruff check + ruff format --check |
test-bridge | apps/bridge/** | uv run pytest tests -v --tb=short |
lint-web | apps/web/** | pnpm run check (Biome/ultracite) |
build-web | apps/web/** | pnpm run build (Next.js build) |
security-global | IRC or XMPP changes | Security scanning (Gitleaks, Trivy) |
docker-irc | apps/unrealircd/** | Docker image build for UnrealIRCd |
docker-xmpp | apps/prosody/** | Docker image build for Prosody |
release | Push to main only | Semantic release via pnpm run release |
What runs on every PR
When you open a PR againstmain or develop, the pipeline:
- Detects changed paths
- Runs lint jobs for affected areas (shell, Lua, Python, TypeScript)
- Runs
test-bridgeif bridge code changed - Builds Docker images if service Dockerfiles changed
- Builds the web app if web code changed
Running CI checks locally
You can replicate most CI checks locally before pushing:Pytest configuration
All pytest settings live inpyproject.toml under [tool.pytest.ini_options]:
testpaths = ["tests"]— default test discovery rootaddopts— strict markers, short tracebacks, verbose output, colour, duration reporting, auto async modetimeout = 300— 5-minute timeout per testasyncio_mode = "auto"— async tests run without explicit event loop setupnorecursedirs— excludes.git,.venv,__pycache__,data, andtests/legacy/**filterwarnings— treats warnings as errors exceptUserWarningandDeprecationWarning
Key dev dependencies
Test tooling is declared inpyproject.toml under [dependency-groups] dev:
| Package | Purpose |
|---|---|
pytest | Test framework |
pytest-mock | mocker fixture for patching |
pytest-asyncio | Async test support |
pytest-docker | Docker Compose integration |
pytest-docker-tools | Declarative Docker container fixtures |
pytest-xdist | Parallel test execution (-n auto) |
pytest-timeout | Per-test timeout enforcement |
pytest-sugar | Pretty test output |
pytest-html | HTML test reports |
hypothesis | Property-based testing |
Related pages
- Contributing — PR workflow, commit conventions, and pre-commit hooks
- Adding a Service — checklist for adding a new service including test requirements
- Bridge Overview — bridge architecture (the bridge has its own test suite)