Files
mcp-nextcloud/tests/unit/test_webhook_uri.py
T
Chris CoutinhoandClaude Opus 4.7 2e2a098bee fix(webhooks): wire receiver to vector sync queue and fix registered URI
The /webhooks/nextcloud endpoint was a no-op stub that logged the
payload and returned 200 OK; webhook deletions never reached Qdrant.
Compounding that, _get_webhook_uri() registered the docker-compose
internal hostname (http://mcp:8000) with Nextcloud whenever
/.dockerenv existed — including ECS Fargate — so cloud deployments
were registering a URL NC could not resolve.

- New vector/webhook_parser.py extracts a DocumentTask from
  NodeCreatedEvent / NodeWrittenEvent / BeforeNodeDeletedEvent
  payloads scoped to */files/Notes/*.md (matching the registered
  preset filters).
- New vector/webhook_receiver.py pushes that task onto the same
  send-stream the scanner uses (app.state.document_send_stream),
  with 503 when sync is not running so NC retries delivery.
- _get_webhook_uri() now prefers NEXTCLOUD_MCP_SERVER_URL over the
  /.dockerenv branch, so the explicit public URL set on cloud tasks
  wins; docker-compose dev still falls back to the internal name when
  no public URL is configured.

Calendar / Tables event parsing is intentionally out of scope here.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 03:13:50 +02:00

96 lines
2.9 KiB
Python

"""Unit tests for ``_get_webhook_uri`` priority order.
Cloud deployments register the webhook URI returned by this function with
Nextcloud. ECS Fargate also exposes ``/.dockerenv``, so an explicit public
URL must win over the docker auto-detection branch.
"""
import pytest
from nextcloud_mcp_server.auth.webhook_routes import _get_webhook_uri
ENV_VARS = (
"WEBHOOK_INTERNAL_URL",
"NEXTCLOUD_MCP_SERVER_URL",
"NEXTCLOUD_MCP_SERVICE_NAME",
"NEXTCLOUD_MCP_PORT",
"DOCKER_CONTAINER",
)
@pytest.fixture(autouse=True)
def _clean_env(monkeypatch):
for name in ENV_VARS:
monkeypatch.delenv(name, raising=False)
def _no_docker_markers(monkeypatch):
monkeypatch.setattr(
"nextcloud_mcp_server.auth.webhook_routes.os.path.exists",
lambda _path: False,
)
def _docker_markers(monkeypatch):
monkeypatch.setattr(
"nextcloud_mcp_server.auth.webhook_routes.os.path.exists",
lambda path: path == "/.dockerenv",
)
@pytest.mark.unit
def test_webhook_internal_url_wins_over_everything(monkeypatch):
monkeypatch.setenv("WEBHOOK_INTERNAL_URL", "https://internal.example.com")
monkeypatch.setenv("NEXTCLOUD_MCP_SERVER_URL", "https://public.example.com")
_docker_markers(monkeypatch)
assert _get_webhook_uri() == "https://internal.example.com/webhooks/nextcloud"
@pytest.mark.unit
def test_public_url_wins_over_docker_detection(monkeypatch):
"""The bug-fix case: ECS containers have /.dockerenv but a public URL is
set. Docker auto-detection must NOT clobber the explicit public URL."""
monkeypatch.setenv(
"NEXTCLOUD_MCP_SERVER_URL", "https://holy-bluegill.astrolabecloud.com"
)
_docker_markers(monkeypatch)
assert (
_get_webhook_uri()
== "https://holy-bluegill.astrolabecloud.com/webhooks/nextcloud"
)
@pytest.mark.unit
def test_docker_detection_used_when_no_public_url(monkeypatch):
"""docker-compose dev: no public URL set, /.dockerenv exists → use the
docker-compose service name."""
_docker_markers(monkeypatch)
assert _get_webhook_uri() == "http://mcp:8000/webhooks/nextcloud"
@pytest.mark.unit
def test_docker_detection_honors_service_name_and_port_overrides(monkeypatch):
monkeypatch.setenv("NEXTCLOUD_MCP_SERVICE_NAME", "mcp-login-flow")
monkeypatch.setenv("NEXTCLOUD_MCP_PORT", "8004")
_docker_markers(monkeypatch)
assert _get_webhook_uri() == "http://mcp-login-flow:8004/webhooks/nextcloud"
@pytest.mark.unit
def test_docker_container_env_var_triggers_docker_branch(monkeypatch):
monkeypatch.setenv("DOCKER_CONTAINER", "true")
_no_docker_markers(monkeypatch)
assert _get_webhook_uri() == "http://mcp:8000/webhooks/nextcloud"
@pytest.mark.unit
def test_localhost_fallback_when_nothing_set(monkeypatch):
_no_docker_markers(monkeypatch)
assert _get_webhook_uri() == "http://localhost:8000/webhooks/nextcloud"