fix: address PR #836 round-3 review (lock-key invariant, single open)

🟡 Document the _doc_queueing_lock ":" delimiter invariant (user_id and the
   controlled doc_type enum are colon-free, so the key is collision-safe; a
   future doc_type with ":" must not be added).
🟡 API pod no longer opens the procrastinate connector twice on startup: add
   ProcrastinateTaskProducer.ensure_schema() (applies the schema on the
   already-open pool) and have both lifespan branches build the producer then
   ensure_schema — one open/close cycle, matching the worker. build_producer now
   returns the concrete producer type.
🟢 Document in ports.py that a long-lived-connection producer may optionally
   provide drain() (lifespan probes via getattr).
🟢 Add a unit test that a non-credential pipeline error propagates (for
   procrastinate's RetryStrategy) and still closes the client via finally.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Chris Coutinho
2026-06-03 15:32:16 +02:00
co-authored by Claude Opus 4.8
parent 820b98dac1
commit b10ce15032
5 changed files with 75 additions and 25 deletions
@@ -122,6 +122,32 @@ class TestProcessDocumentTask:
assert captured["max_retries"] == 1
fake_client.close.assert_awaited_once()
async def test_pipeline_error_propagates_and_closes_client(self, monkeypatch):
# A non-credential failure must propagate (so procrastinate's
# RetryStrategy picks it up) and still close the client via finally.
fake_client = AsyncMock()
async def fake_resolve(user_id):
return fake_client
async def fake_process(task, nc_client, *, max_retries):
raise RuntimeError("transient qdrant failure")
monkeypatch.setattr(pq, "_resolve_client", fake_resolve)
monkeypatch.setattr(
"nextcloud_mcp_server.vector.processor.process_document", fake_process
)
with pytest.raises(RuntimeError, match="transient qdrant failure"):
await pq.process_document_task(
user_id="alice",
doc_id="42",
doc_type="note",
operation="index",
modified_at=100,
)
fake_client.close.assert_awaited_once()
async def test_skips_on_missing_credentials(self, monkeypatch):
from nextcloud_mcp_server.vector.oauth_sync import NotProvisionedError