refactor(webhooks): bound queue waits, route URLs through dynaconf
Addresses round-3 review feedback on PR #747: - webhook_receiver: wrap send_stream.send() in anyio.fail_after(1.0) and return 503 with reason="queue full" if the queue is saturated. Avoids pinning the handler until NC's outbound timeout fires; the 503 retry contract is the same as the existing "sync not running" branch. - webhook_receiver: revise the compare_digest comment to match what the function actually guarantees — it avoids the per-character short-circuit of `==` but is not fully constant-time across length differences. - _get_webhook_uri: read WEBHOOK_INTERNAL_URL and NEXTCLOUD_MCP_SERVER_URL via dynaconf so operators using settings.toml (rather than env vars) aren't silently routed into the docker/localhost fallback. Adds webhook_internal_url to Settings/_DEFAULTS/_field_map; nextcloud_mcp_server_url already existed. Docker-detection markers stay on os.getenv since they're container-runtime signals, not user-facing config. - webhook_routes: sweep remaining f-string logger calls to lazy %s formatting per CLAUDE.md. - client/webhooks: modernise full file's type hints to dict / list / | None per CLAUDE.md. Tests: - New test_returns_503_when_queue_is_full exercises the timeout branch with a saturated buffer and a shortened deadline. - test_webhook_uri tests now patch get_settings (matching the auth-pair tests in the same file) instead of monkeypatching env vars directly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
42675e7c20
commit
c1368b9a7f
@@ -165,6 +165,33 @@ def test_returns_500_when_stream_is_closed():
|
||||
assert response.json()["status"] == "error"
|
||||
|
||||
|
||||
def test_returns_503_when_queue_is_full(monkeypatch):
|
||||
"""When the processor queue is saturated, the handler must time out
|
||||
quickly with 503 instead of pinning until NC's outbound timeout fires."""
|
||||
# Speed up the test — a 1s deadline matches production but is overkill
|
||||
# for a unit test that's specifically exercising the timeout branch.
|
||||
# Capture the original BEFORE patching so the override doesn't recurse
|
||||
# into itself (the receiver imports the same anyio module object).
|
||||
real_fail_after = anyio.fail_after
|
||||
monkeypatch.setattr(
|
||||
"nextcloud_mcp_server.vector.webhook_receiver.anyio.fail_after",
|
||||
lambda _seconds: real_fail_after(0.05),
|
||||
)
|
||||
|
||||
# Buffer of 1, no consumer → first send fills it, second blocks.
|
||||
send_stream, _receive_stream = anyio.create_memory_object_stream(max_buffer_size=1)
|
||||
send_stream.send_nowait("sentinel") # type: ignore[arg-type]
|
||||
app = _make_app(send_stream=send_stream)
|
||||
|
||||
with TestClient(app) as client:
|
||||
response = client.post("/webhooks/nextcloud", json=_NOTE_CREATED)
|
||||
|
||||
assert response.status_code == 503
|
||||
body = response.json()
|
||||
assert body["status"] == "unavailable"
|
||||
assert body["reason"] == "queue full"
|
||||
|
||||
|
||||
# --- WEBHOOK_SECRET authentication ---------------------------------------
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user