feat: validate Nextcloud webhook schemas and document findings
Manual testing of Nextcloud webhook_listeners app to validate webhook payloads against ADR-010 expected schemas and document implementation requirements for webhook-based vector synchronization. ## Changes - Add test webhook endpoint at /webhooks/nextcloud in app.py - Captures and logs webhook payloads for analysis - Returns 200 OK immediately for webhook delivery confirmation - Create webhook-testing-findings.md with comprehensive test results - Captured payloads for 5/6 webhook event types - Critical findings: missing node.id in deletions, type mismatches - Implementation recommendations with code examples - Update ADR-010 with Appendix A: Manual Webhook Testing Results - Document actual vs expected webhook behavior - Update event mapping table with tested webhook status - Add 6 specific implementation recommendations - Include testing implications for future development ## Testing Results ✅ NodeCreatedEvent - fires correctly, includes node.id (integer) ✅ NodeWrittenEvent - fires correctly, includes node.id (integer) ✅ NodeDeletedEvent - fires but missing node.id field (path only) ✅ CalendarObjectCreatedEvent - fires correctly with full iCal ✅ CalendarObjectUpdatedEvent - fires correctly with full iCal ❌ CalendarObjectDeletedEvent - does not fire (potential NC bug) ## Key Findings 1. NodeDeletedEvent missing node.id field - requires path-based fallback 2. node.id returns integer not string - needs casting for consistency 3. Multiple webhooks fire per operation - needs deduplication logic 4. Calendar deletion webhooks don't fire - reported as issue #53497 5. Calendar webhooks include full iCal content - enables rich parsing ## GitHub Issues - Created issue #56371: NodeDeletedEvent missing node.id field - Commented on issue #53497: CalendarObjectDeletedEvent not firing Closes #283 --- _This commit was generated with the help of AI, and reviewed by a Human_
This commit is contained in:
@@ -1212,6 +1212,31 @@ def get_app(transport: str = "sse", enabled_apps: list[str] | None = None):
|
||||
status_code=status_code,
|
||||
)
|
||||
|
||||
async def handle_nextcloud_webhook(request):
|
||||
"""Test webhook endpoint to capture and log Nextcloud webhook payloads.
|
||||
|
||||
This is a temporary endpoint for testing webhook schemas and payloads.
|
||||
It logs the full payload and returns 200 OK immediately.
|
||||
"""
|
||||
import json
|
||||
|
||||
try:
|
||||
payload = await request.json()
|
||||
logger.info("=" * 80)
|
||||
logger.info("🔔 Webhook received from Nextcloud:")
|
||||
logger.info(json.dumps(payload, indent=2, sort_keys=True))
|
||||
logger.info("=" * 80)
|
||||
|
||||
return JSONResponse(
|
||||
{"status": "received", "timestamp": payload.get("time")},
|
||||
status_code=200,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Failed to parse webhook payload: {e}")
|
||||
return JSONResponse(
|
||||
{"error": "invalid_payload", "message": str(e)}, status_code=400
|
||||
)
|
||||
|
||||
# Add Protected Resource Metadata (PRM) endpoint for OAuth mode
|
||||
routes = []
|
||||
|
||||
@@ -1220,6 +1245,12 @@ def get_app(transport: str = "sse", enabled_apps: list[str] | None = None):
|
||||
routes.append(Route("/health/ready", health_ready, methods=["GET"]))
|
||||
logger.info("Health check endpoints enabled: /health/live, /health/ready")
|
||||
|
||||
# Add test webhook endpoint (for development/testing)
|
||||
routes.append(
|
||||
Route("/webhooks/nextcloud", handle_nextcloud_webhook, methods=["POST"])
|
||||
)
|
||||
logger.info("Test webhook endpoint enabled: /webhooks/nextcloud")
|
||||
|
||||
# Note: Metrics endpoint is NOT exposed on main HTTP port for security reasons.
|
||||
# Metrics are served on dedicated port via setup_metrics() (default: 9090)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user