db1e0606ad
Consolidate three independent RefreshTokenStorage lazy singletons into a single lock-protected get_shared_storage() function, eliminating race conditions on concurrent first-access. Remove blanket try/except in _get_stored_scopes so storage errors propagate as proper MCP errors instead of silently triggering "please provision" messages. Handle declined/cancelled elicitation results in Login Flow tools by cleaning up sessions and returning clear status. Add update_app_password_scopes() to avoid unnecessary decrypt/re-encrypt when only scopes change. Add unprovisioned-user early exit and no-op detection to nc_auth_update_scopes. Remove four dead config fields and misleading NEXTCLOUD_PASSWORD deprecation warning. Add periodic login flow session cleanup task. Generate separate Fernet keys per service. Add board cleanup in deck integration test. Gate CI unit tests on linting and skip Astrolabe build for single-user profile. Fix test markers from oauth to multi_user_basic for astrolabe integration tests. Update login_flow.py docstrings to document outbound HTTP calls. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
202 lines
7.2 KiB
YAML
202 lines
7.2 KiB
YAML
name: Tests
|
|
|
|
on:
|
|
pull_request:
|
|
branches:
|
|
- master
|
|
|
|
jobs:
|
|
linting:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
- name: Install the latest version of uv
|
|
uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7.3.0
|
|
- name: Check format
|
|
run: uv run --frozen ruff format --diff
|
|
- name: Linting
|
|
run: uv run --frozen ruff check
|
|
- name: Type check
|
|
run: uv run --frozen ty check -- nextcloud_mcp_server
|
|
|
|
unit-test:
|
|
runs-on: ubuntu-latest
|
|
needs: [linting]
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
- name: Install the latest version of uv
|
|
uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7.3.0
|
|
- name: Run unit tests
|
|
run: uv run pytest -v -m unit -o "addopts=-p no:asyncio"
|
|
|
|
integration-test:
|
|
runs-on: ubuntu-latest
|
|
needs: [linting]
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- mode: single-user
|
|
profile: single-user
|
|
markers: "(smoke and not oauth and not keycloak and not login_flow and not multi_user_basic) or (integration and not oauth and not keycloak and not login_flow and not multi_user_basic)"
|
|
wait-port: 8000
|
|
mcp-internal-url: "http://mcp:8000"
|
|
needs-playwright: false
|
|
extra-args: >-
|
|
--ignore=tests/integration/test_qdrant_collection_creation.py
|
|
--ignore=tests/rag_evaluation/
|
|
|
|
- mode: multi-user-basic
|
|
profile: multi-user-basic
|
|
markers: "multi_user_basic"
|
|
wait-port: 8003
|
|
mcp-internal-url: "http://mcp-multi-user-basic:8000"
|
|
needs-playwright: false
|
|
extra-args: ""
|
|
|
|
# NOTE: Playwright browser tests are skipped in CI (no browser grant flow).
|
|
# These entries still run non-Playwright tests marked with the same markers.
|
|
- mode: oauth
|
|
profile: oauth
|
|
markers: "oauth and not keycloak"
|
|
wait-port: 8001
|
|
mcp-internal-url: "http://mcp-oauth:8001"
|
|
needs-playwright: true
|
|
extra-args: ""
|
|
|
|
- mode: login-flow
|
|
profile: login-flow
|
|
markers: "login_flow"
|
|
wait-port: 8004
|
|
mcp-internal-url: "http://mcp-login-flow:8004"
|
|
needs-playwright: true
|
|
extra-args: ""
|
|
|
|
name: integration (${{ matrix.mode }})
|
|
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
submodules: 'true'
|
|
|
|
- name: Set up PHP 8.4
|
|
if: matrix.mode != 'single-user'
|
|
uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # 2.36.0
|
|
with:
|
|
php-version: 8.4
|
|
coverage: none
|
|
|
|
# OIDC app installed from app store (dev mount removed from docker-compose.yml)
|
|
|
|
- name: Set up Node.js
|
|
if: matrix.mode != 'single-user'
|
|
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
|
with:
|
|
node-version: 22
|
|
|
|
- name: Build Astrolabe app
|
|
if: matrix.mode != 'single-user'
|
|
run: |
|
|
cd third_party/astrolabe
|
|
composer install --no-dev --optimize-autoloader
|
|
npm ci
|
|
npm run build
|
|
|
|
# Start services with the appropriate profile
|
|
- name: Run docker compose
|
|
uses: hoverkraft-tech/compose-action@4894d2492015c1774ee5a13a95b1072093087ec3 # v2.5.0
|
|
with:
|
|
compose-file: "./docker-compose.yml"
|
|
compose-flags: "--profile ${{ matrix.profile }}"
|
|
up-flags: "--build"
|
|
env:
|
|
MCP_SERVER_URL: ${{ matrix.mcp-internal-url }}
|
|
|
|
- name: Install the latest version of uv
|
|
uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7.3.0
|
|
|
|
- name: Install Playwright
|
|
if: matrix.needs-playwright
|
|
run: uv run playwright install chromium --with-deps
|
|
|
|
# Wait for Nextcloud to be healthy
|
|
- name: Wait for Nextcloud
|
|
run: |
|
|
echo "Waiting for Nextcloud at http://localhost:8080..."
|
|
max_attempts=60
|
|
attempt=0
|
|
until curl -sSf http://localhost:8080/status.php 2>/dev/null | grep -q '"installed":true'; do
|
|
attempt=$((attempt + 1))
|
|
if [ $attempt -ge $max_attempts ]; then
|
|
echo "Nextcloud did not become ready in time."
|
|
docker compose logs app
|
|
exit 1
|
|
fi
|
|
echo "Attempt $attempt/$max_attempts: Not ready, sleeping 5s..."
|
|
sleep 5
|
|
done
|
|
echo "Nextcloud is ready."
|
|
|
|
# Wait for the MCP service to be healthy
|
|
- name: Wait for MCP service (${{ matrix.mode }})
|
|
run: |
|
|
echo "Waiting for MCP service on port ${{ matrix.wait-port }}..."
|
|
max_attempts=30
|
|
attempt=0
|
|
until curl -o /dev/null -s -w "%{http_code}\n" http://localhost:${{ matrix.wait-port }}/health 2>/dev/null | grep -qE "200|404|405"; do
|
|
attempt=$((attempt + 1))
|
|
if [ $attempt -ge $max_attempts ]; then
|
|
echo "MCP service did not become ready in time."
|
|
docker compose --profile ${{ matrix.profile }} logs
|
|
exit 1
|
|
fi
|
|
echo "Attempt $attempt/$max_attempts: Not ready, sleeping 5s..."
|
|
sleep 5
|
|
done
|
|
echo "MCP service is ready on port ${{ matrix.wait-port }}."
|
|
|
|
- name: Verify OIDC configuration
|
|
if: matrix.needs-playwright
|
|
run: |
|
|
echo "=== OIDC Discovery ==="
|
|
curl -s http://localhost:8080/.well-known/openid-configuration | jq .
|
|
echo "=== OIDC App Status ==="
|
|
docker compose exec -T app php occ app:list --output=json 2>/dev/null | jq '.enabled.oidc // "NOT INSTALLED"'
|
|
|
|
- name: Run tests (${{ matrix.mode }})
|
|
env:
|
|
NEXTCLOUD_HOST: "http://localhost:8080"
|
|
NEXTCLOUD_USERNAME: "admin"
|
|
NEXTCLOUD_PASSWORD: "admin"
|
|
run: |
|
|
uv run pytest -v \
|
|
--log-cli-level=WARN \
|
|
-m '${{ matrix.markers }}' \
|
|
-o "addopts=-p no:asyncio" \
|
|
--timeout=300 \
|
|
${{ matrix.extra-args }}
|
|
|
|
- name: Report skipped Playwright tests
|
|
if: matrix.needs-playwright
|
|
run: |
|
|
echo "::notice::Playwright browser tests are skipped in CI. Run locally with: uv run pytest -m '${{ matrix.markers }}' --browser firefox"
|
|
uv run pytest --collect-only -q \
|
|
-m '${{ matrix.markers }}' \
|
|
${{ matrix.extra-args }} 2>/dev/null \
|
|
| tail -1 || true
|
|
|
|
- name: Collect service logs on failure
|
|
if: failure()
|
|
run: docker compose --profile ${{ matrix.profile }} logs --tail=500 > /tmp/docker-compose-logs.txt 2>&1
|
|
|
|
- name: Upload debug artifacts
|
|
if: failure()
|
|
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
|
with:
|
|
name: debug-${{ matrix.mode }}
|
|
path: |
|
|
/tmp/*.png
|
|
/tmp/docker-compose-logs.txt
|
|
retention-days: 7
|
|
if-no-files-found: ignore
|