The Astrolabe webhooks UI hits /api/v1/apps on the MCP server, which forwarded the OAuth bearer token to /ocs/v1.php/cloud/apps?filter=enabled. That OCS endpoint is admin-only AND @PasswordConfirmationRequired — neither requirement is satisfiable via an OAuth bearer token, so even an admin user's token returns a silent 401 (no entry in nextcloud.log). Switch to /ocs/v2.php/cloud/capabilities, which has no admin or password- confirmation gate, accepts the existing bearer token, and returns a capabilities map keyed by app id (notes, files, tables, forms, etc.). This is sufficient for the webhook presets UI to gate available presets against the running Nextcloud instance's enabled apps. Bearer is preserved on the outbound call because anonymous capabilities omits notes/tables/forms — only authenticated capabilities exposes them. Tests: - New unit test covers the regression (asserts /ocs/v2.php/cloud/capabilities is hit, NOT /cloud/apps), response parsing, sanitized error messages, and missing-config paths. - New integration test under tests/server/login_flow/ drives a real OAuth flow against mcp-login-flow with a static OIDC client (nextcloudMcpServerUIPublicClient) and asserts /api/v1/apps returns 200 with core/files in the response. docker-compose.yml: aligns mcp-login-flow's ALLOWED_MGMT_CLIENT with mcp-multi-user-basic so the same static-client test fixture works for both. Follow-up to homelab-argocd #1608, which set ALLOWED_MGMT_CLIENT in production but didn't unblock the webhooks flow. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
53 lines
2.0 KiB
Python
53 lines
2.0 KiB
Python
"""Integration tests for the management API on the login-flow MCP server.
|
|
|
|
These tests drive a real OAuth flow against Nextcloud's `oidc` app using the
|
|
static `nextcloudMcpServerUIPublicClient` client (which is allowlisted on the
|
|
`mcp-login-flow` container via `ALLOWED_MGMT_CLIENT`), then hit the
|
|
management API endpoints with the resulting bearer token.
|
|
|
|
Regression coverage for the bug where /api/v1/apps proxied to OCS v1
|
|
/cloud/apps and always 401'd. The handler now uses /ocs/v2.php/cloud/capabilities,
|
|
which is reachable for OAuth bearer tokens.
|
|
"""
|
|
|
|
import httpx
|
|
import pytest
|
|
|
|
LOGIN_FLOW_API_BASE_URL = "http://localhost:8004"
|
|
|
|
pytestmark = [pytest.mark.integration, pytest.mark.oauth]
|
|
|
|
|
|
async def test_get_installed_apps_returns_capability_keys(
|
|
login_flow_static_client_token: str,
|
|
):
|
|
"""GET /api/v1/apps returns 200 with a list of enabled-app capability keys."""
|
|
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
response = await client.get(
|
|
f"{LOGIN_FLOW_API_BASE_URL}/api/v1/apps",
|
|
headers={"Authorization": f"Bearer {login_flow_static_client_token}"},
|
|
)
|
|
|
|
assert response.status_code == 200, (
|
|
f"/api/v1/apps returned {response.status_code}: {response.text}"
|
|
)
|
|
|
|
data = response.json()
|
|
assert "apps" in data
|
|
assert isinstance(data["apps"], list)
|
|
|
|
# Anonymous capabilities always exposes core; authenticated also exposes
|
|
# files. Both should be present whether or not the oidc app's
|
|
# BearerAuthMiddleware ran for this OCS route.
|
|
apps = data["apps"]
|
|
assert "core" in apps, f"expected 'core' in apps, got {apps}"
|
|
assert "files" in apps, f"expected 'files' in apps, got {apps}"
|
|
|
|
|
|
async def test_get_installed_apps_requires_bearer_token():
|
|
"""No Authorization header → 401 (handler's token validator rejects it)."""
|
|
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
response = await client.get(f"{LOGIN_FLOW_API_BASE_URL}/api/v1/apps")
|
|
|
|
assert response.status_code == 401
|