test(integration): add login-flow Astrolabe provisioning regression test
Add the missing end-to-end coverage for the seam that PR #773's `ALLOWED_MGMT_CLIENT` ↔ `astrolabeMcpClientOAuth00000000000` drift bug exposed: browser → Astrolabe (NC PHP app) → MCP server's management API on the `mcp-login-flow` profile. Every existing `tests/integration/test_astrolabe_*.py` is marked `multi_user_basic` and exercises the BasicAuth flow, not Login Flow v2 / OAuth. The new test mirrors the production-shaped flow exactly: 1. Log in as admin via Playwright. 2. Navigate to `/settings/user/astrolabe`. 3. Click the "Enable Semantic Search" OAuth link rendered by `oauth-required.php`. (Same selector Astrolabe's own e2e helper uses — `third_party/astrolabe/tests/e2e/helpers/authorize.ts`.) 4. Click "Allow" on the Nextcloud OIDC consent screen. 5. Wait for the redirect back to the Astrolabe settings page. 6. Assert the "Enable Semantic Search" link is no longer visible. Step 6 is the canary for the drift class: if Astrolabe's management API call to `/api/v1/users/{id}/session` is rejected (HTTP 401, the original bug), the session lookup falls back to "no token" and the same `oauth-required.php` template re-renders with the link still present — so the test fails loudly with a message naming the likely cause. Reuses `login_to_nextcloud` and `navigate_to_astrolabe_settings` helpers from `tests/integration/test_astrolabe_multi_user_background_sync.py` (already pattern-imported by the Plotly viz test). No fixture-level OIDC client creation: `app-hooks/before-starting/26-configure-astrolabe-oauth.sh` already provisions `astrolabeMcpClientOAuth00000000000` with the correct redirect URI and scopes when `MCP_SERVER_URL` is set in the shell that runs `docker compose --profile login-flow up`. The test skips cleanly when admin is already authorized (typical state on a re-run against a long-lived dev stack), so it's safe to run repeatedly without manual reset. 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
a27f738dbf
commit
22a2a24941
@@ -0,0 +1,137 @@
|
||||
"""Integration test for Astrolabe's "Enable Semantic Search" OAuth flow on
|
||||
the `mcp-login-flow` profile.
|
||||
|
||||
Cross-system interface test. Brings together Astrolabe (Nextcloud PHP app
|
||||
installed at container start by ``app-hooks/post-installation``) with the
|
||||
``mcp-login-flow`` MCP server over OAuth + the management API. Mirrors
|
||||
the production-shaped flow that PR #773's recent
|
||||
`ALLOWED_MGMT_CLIENT` ↔ `astrolabeMcpClientOAuth00000000000` drift was
|
||||
masking — every management API call from Astrolabe (e.g.
|
||||
``/api/v1/users/admin/session``) was returning 401 because the
|
||||
real-deployment client id was not in the test-fixture allowlist, so the
|
||||
Astrolabe settings page never updated to reflect a successful
|
||||
authorization.
|
||||
|
||||
This test is **regression coverage** for that class of drift. If the
|
||||
Astrolabe client id ever falls out of `mcp-login-flow`'s
|
||||
``ALLOWED_MGMT_CLIENT`` again, the post-redirect assertions here will
|
||||
fail because the page state stays on ``oauth-required.php``.
|
||||
|
||||
Requires the login-flow stack to be running:
|
||||
|
||||
MCP_SERVER_URL=http://mcp-login-flow:8004 \\
|
||||
docker compose --profile login-flow up -d app db mcp-login-flow
|
||||
|
||||
The ``app-hooks/before-starting/26-configure-astrolabe-oauth.sh`` hook
|
||||
creates the OAuth client with the production-shaped id
|
||||
``astrolabeMcpClientOAuth00000000000`` automatically when
|
||||
``MCP_SERVER_URL`` is set, so no fixture-level OIDC client creation is
|
||||
needed here.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
|
||||
import pytest
|
||||
from playwright.async_api import Page
|
||||
|
||||
# Reuse helpers from the multi-user-basic Astrolabe test for login + nav.
|
||||
from tests.integration.test_astrolabe_multi_user_background_sync import (
|
||||
login_to_nextcloud,
|
||||
navigate_to_astrolabe_settings,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
pytestmark = [pytest.mark.integration, pytest.mark.login_flow]
|
||||
|
||||
NEXTCLOUD_URL = "http://localhost:8080"
|
||||
ASTROLABE_SETTINGS_URL = f"{NEXTCLOUD_URL}/settings/user/astrolabe"
|
||||
|
||||
|
||||
async def _click_enable_semantic_search(page: Page) -> None:
|
||||
"""Click the "Enable Semantic Search" OAuth link on the
|
||||
``oauth-required.php`` template that login-flow mode renders to a
|
||||
not-yet-authorized user.
|
||||
|
||||
Astrolabe's own e2e helper (``third_party/astrolabe/tests/e2e/helpers/
|
||||
authorize.ts``) targets the same link by accessible name.
|
||||
"""
|
||||
enable_link = page.get_by_role("link", name="Enable Semantic Search")
|
||||
await enable_link.wait_for(state="visible", timeout=10_000)
|
||||
logger.info("Clicking 'Enable Semantic Search' OAuth link")
|
||||
await enable_link.click()
|
||||
|
||||
|
||||
async def _grant_oidc_consent(page: Page) -> None:
|
||||
"""Click "Allow" on the Nextcloud OIDC consent screen, if shown.
|
||||
|
||||
Nextcloud may auto-redirect for already-trusted clients, in which
|
||||
case the consent button never appears — that's not an error.
|
||||
"""
|
||||
allow_button = page.get_by_role("button", name=re.compile(r"^allow$", re.I))
|
||||
try:
|
||||
await allow_button.wait_for(state="visible", timeout=10_000)
|
||||
logger.info("Clicking 'Allow' on OIDC consent")
|
||||
await allow_button.click(force=True)
|
||||
except Exception:
|
||||
logger.info(
|
||||
"OIDC consent screen not visible — assuming auto-grant for "
|
||||
"already-trusted client"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.timeout(180)
|
||||
async def test_enable_semantic_search_completes_oauth_for_login_flow(browser):
|
||||
"""Click the "Enable Semantic Search" link, grant consent, and assert
|
||||
the post-redirect page reflects a completed authorization.
|
||||
|
||||
The success criterion is intentionally negative: after the OAuth
|
||||
flow, the original "Enable Semantic Search" link must be gone. If
|
||||
Astrolabe's management API call is rejected by the MCP server (HTTP
|
||||
401, the original bug), the page falls back to the same
|
||||
``oauth-required.php`` template and the link reappears — making this
|
||||
test the canary for the drift class.
|
||||
"""
|
||||
admin_password = os.environ.get("NEXTCLOUD_PASSWORD", "admin")
|
||||
page = await browser.new_page()
|
||||
try:
|
||||
await login_to_nextcloud(page, "admin", admin_password)
|
||||
await navigate_to_astrolabe_settings(page)
|
||||
|
||||
# Sanity-check we're on the not-yet-authorized template.
|
||||
enable_link = page.get_by_role("link", name="Enable Semantic Search")
|
||||
if await enable_link.count() == 0:
|
||||
pytest.skip(
|
||||
"Astrolabe is already authorized for admin (oauth-required.php "
|
||||
"not rendered). Reset by clearing the user's OAuth tokens "
|
||||
"before re-running this test."
|
||||
)
|
||||
|
||||
await _click_enable_semantic_search(page)
|
||||
await _grant_oidc_consent(page)
|
||||
|
||||
# OAuth callback returns to /apps/astrolabe/oauth/callback then the
|
||||
# controller redirects to /settings/user/astrolabe.
|
||||
await page.wait_for_url(re.compile(r"/settings/user/astrolabe"), timeout=30_000)
|
||||
await page.wait_for_load_state("networkidle", timeout=15_000)
|
||||
|
||||
# Regression assertion for the ALLOWED_MGMT_CLIENT drift bug:
|
||||
# the page must have moved past oauth-required.php. If
|
||||
# Astrolabe's management API call to /api/v1/users/{id}/session
|
||||
# is rejected (401), the session lookup falls back to "no token",
|
||||
# and the same oauth-required.php template re-renders with the
|
||||
# link still present.
|
||||
post_auth_count = await page.get_by_role(
|
||||
"link", name="Enable Semantic Search"
|
||||
).count()
|
||||
assert post_auth_count == 0, (
|
||||
"'Enable Semantic Search' link still visible after completing "
|
||||
"OAuth flow — Astrolabe could not read the user's session from "
|
||||
"the MCP server. Most likely cause: "
|
||||
"`astrolabeMcpClientOAuth00000000000` missing from "
|
||||
"`ALLOWED_MGMT_CLIENT` on `mcp-login-flow`."
|
||||
)
|
||||
finally:
|
||||
await page.close()
|
||||
Reference in New Issue
Block a user