Root cause of the CI 404s: test modules mutated os.environ at import time
(test_server.py set COLLECTION_NAME='test', test_integration.py
'test_mcp_maildir'). With pytest's alphabetical import order,
server.COLLECTION_NAME froze at whichever value was set when server was
first imported, while the fixture read os.environ at runtime — often a
different name (log proof: fixture created 'test', server queried
'test_mcp_maildir' → 404 on all 6 tests).
conftest.py now sets QDRANT_URL/COLLECTION_NAME/EMBEDDING_MODEL_NAME once,
before any test module import; env mutation removed from test files.
Unit suite still green (43 passed, 6 deselected).
Two concurrent jobs (docker-build + PR Checks on the same push) both
used container name 'qdrant'; the second job's 'docker rm -f qdrant'
destroyed the first job's Qdrant mid-run, so the test collection 404'd.
Name is now qdrant-${{ github.run_id }} everywhere (cleanup included).
- pr.yaml: new pytest job (unit tests only, integration deselected by marker)
- main.yaml / cron.yaml / tag.yaml: replace the build-only 'test' job with a
real pytest unit job + an integration-test job that starts a Qdrant
container sharing the job container's network namespace
(--network container:$(hostname) — bypasses the Gitea runner bug with
service containers), waits for /healthz, runs 'pytest -m integration',
then tears down.
- build/build-push jobs now require [test, integration-test]; previously
'test' was only a docker build and pytest never ran in CI.
- integration-test first cleans up any stale qdrant container (rerun-safe).
- setup-python SHA aligned with docker-build.yaml (v7, python 3.13/3.14).
- pytest.ini: 'integration' marker, deselected by default; the integration
suite requires a live Qdrant and will only run in the nightly CI job.
- src/indexer.py: move env validation from import time into _validate_config()
called by main() — the module can now be imported without MAILDIR_PATH /
QDRANT_URL / COLLECTION_NAME set, enabling unit tests.
- tests/test_indexer.py: 27 new unit tests, no network and no model:
decode_mime_words (encoded-words utf-8/iso-8859-1, mixed), extract_text_from_html
(tags, scripts), normalize_email_address, parse_email_message with real
EmailMessage objects (plain bodies, attachments detected and excluded from
body, HTML alternative, unknown charset fallback via raw bytes message,
encoded attachment filename), get_recent_keys against a real tmp_path
Maildir (fresh mtime selected, old mtime excluded, colon-suffix keys).
mailbox.Maildir(create=True) does not create cur/new/tmp on Python 3.11 —
the test helper pre-creates them.