forked from nloui/paperless-mcp
-
Notifications
You must be signed in to change notification settings - Fork 27
feat: E2E tests in CI against real Paperless-ngx #104
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
878c106
feat: add E2E test suite against real Paperless-ngx in CI
claude 986442a
fix: repair E2E CI failures and address CodeRabbit review
claude b07df38
fix: correct tasks API response parsing and update URI assertions
claude 71d30c1
fix: exclude e2e from tsc to restore build/index.js output path
claude 0db608c
fix: handle both paginated and array formats in waitForDocument
claude 2a0abf7
fix(e2e): fix download test, thumbnail assertion, PDF xref offsets, p…
claude efea0b2
fix(e2e): add post-seed delay and capture test output on failure
claude 643edbe
fix(e2e): poll search index instead of fixed delay, add diagnostics
claude 3d7746a
fix(e2e): include error text in bulk_edit assertion message
claude f049fd7
fix(e2e): include tool error text in download/thumbnail assertions
claude c542428
fix(e2e): print actual before() hook error to stderr
claude e44cadd
fix(e2e): replace Whoosh-based wait with direct doc fetch + retry in …
claude eeff3ea
fix(bulk-edit): ensure add_tags and remove_tags default to [] for mod…
claude 0e84dd3
Merge branch 'main' into claude/tender-wozniak-lYl1P
baruchiro 3783285
refactor(e2e): drive scenario via MCP tools, drop separate Paperless …
claude fe3cf16
ci(e2e): migrate Paperless fixture from docker-compose to GitHub Acti…
claude 06d30e1
ci(e2e): drop matrix, run CLI and Docker MCP modes sequentially in on…
claude 8412a89
fix(e2e): include per-run title in PDF bytes so checksums differ
claude 02d1278
ci(e2e): track paperless-ngx :latest and run weekly
claude File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@baruchiro/paperless-mcp": minor | ||
| --- | ||
|
|
||
| Add E2E test suite that runs the compiled MCP server against a real Paperless-ngx instance in CI. Covers list/create for tags, correspondents, document types, list/get/search/download/thumbnail for documents, bulk_edit_documents, and post_document — all with deterministic tool calls and no LLM in the loop. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,163 @@ | ||
| name: E2E Tests | ||
|
|
||
| on: | ||
| push: | ||
| branches: | ||
| - main | ||
| pull_request: | ||
| schedule: | ||
| # Weekly run to catch drift against upstream paperless-ngx :latest. | ||
| - cron: "0 8 * * 1" | ||
|
|
||
| jobs: | ||
| e2e: | ||
| runs-on: ubuntu-latest | ||
|
|
||
| services: | ||
| redis: | ||
| image: redis:7-alpine | ||
| options: >- | ||
| --health-cmd "redis-cli ping" | ||
| --health-interval 5s | ||
| --health-timeout 5s | ||
| --health-retries 10 | ||
|
|
||
| paperless: | ||
| # Track upstream stable. Paperless-ngx publishes the latest stable | ||
| # release under :latest; the weekly schedule catches drift. | ||
| image: ghcr.io/paperless-ngx/paperless-ngx:latest | ||
| env: | ||
| PAPERLESS_REDIS: redis://redis:6379 | ||
| PAPERLESS_ADMIN_USER: admin | ||
| PAPERLESS_ADMIN_PASSWORD: admin123 | ||
| PAPERLESS_ADMIN_MAIL: admin@test.local | ||
| PAPERLESS_SECRET_KEY: e2e-test-secret-not-for-production | ||
| PAPERLESS_OCR_LANGUAGE: eng | ||
| PAPERLESS_OCR_MODE: skip | ||
| PAPERLESS_TIME_ZONE: UTC | ||
| ports: | ||
| - 8000:8000 | ||
| options: >- | ||
| --health-cmd "curl -fsS http://localhost:8000/api/ || exit 1" | ||
| --health-interval 10s | ||
| --health-timeout 10s | ||
| --health-retries 30 | ||
| --health-start-period 60s | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v4 | ||
|
|
||
| - name: Setup Node.js | ||
| uses: actions/setup-node@v6 | ||
| with: | ||
| node-version-file: ".node-version" | ||
|
|
||
| - name: Install dependencies | ||
| run: npm ci | ||
|
|
||
| - name: Build MCP server | ||
| run: npm run build | ||
|
|
||
| - name: Set up Docker Buildx | ||
| uses: docker/setup-buildx-action@v3 | ||
|
|
||
| - name: Build Docker image for E2E | ||
| uses: docker/build-push-action@v6 | ||
| with: | ||
| context: . | ||
| file: ./Dockerfile | ||
| platforms: linux/amd64 | ||
| push: false | ||
| load: true | ||
| tags: paperless-mcp:e2e | ||
| cache-from: type=gha,scope=paperless-mcp-docker | ||
| cache-to: type=gha,scope=paperless-mcp-docker,mode=max | ||
|
|
||
| - name: Get Paperless API token | ||
| run: | | ||
| TOKEN=$(curl -s -X POST http://localhost:8000/api/token/ \ | ||
| -H 'Content-Type: application/json' \ | ||
| -d '{"username":"admin","password":"admin123"}' | jq -r '.token') | ||
| if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then | ||
| echo "Failed to get API token" | ||
| exit 1 | ||
| fi | ||
| echo "PAPERLESS_TOKEN=$TOKEN" >> $GITHUB_ENV | ||
| echo "Got token: ${TOKEN:0:8}..." | ||
|
|
||
| # ---- CLI MCP mode ---- | ||
|
|
||
| - name: Start MCP server (CLI mode) | ||
| run: | | ||
| node build/index.js \ | ||
| --http --port 3001 \ | ||
| --baseUrl http://localhost:8000 \ | ||
| --token "$PAPERLESS_TOKEN" & | ||
| echo "MCP_CLI_PID=$!" >> $GITHUB_ENV | ||
| for i in $(seq 1 20); do | ||
| code=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3001/mcp || true) | ||
| if [ "$code" = "405" ]; then | ||
| echo "MCP server (CLI) ready" | ||
| exit 0 | ||
| fi | ||
| sleep 1 | ||
| done | ||
| echo "MCP server (CLI) failed to start" | ||
| exit 1 | ||
|
|
||
| - name: Run E2E tests (CLI MCP) | ||
| run: node --require ts-node/register --test e2e/e2e.test.ts 2>&1 | tee /tmp/e2e-cli-output.txt; exit "${PIPESTATUS[0]}" | ||
| env: | ||
| PAPERLESS_URL: http://localhost:8000 | ||
| PAPERLESS_TOKEN: ${{ env.PAPERLESS_TOKEN }} | ||
| MCP_URL: http://localhost:3001/mcp | ||
|
|
||
| - name: Stop CLI MCP | ||
| if: always() | ||
| run: kill "$MCP_CLI_PID" 2>/dev/null || true | ||
|
|
||
| # ---- Docker MCP mode ---- | ||
|
|
||
| - name: Start MCP server (Docker mode) | ||
| if: ${{ !cancelled() }} | ||
| run: | | ||
| docker run -d --name paperless-mcp-e2e \ | ||
| --network host \ | ||
| -e PAPERLESS_URL=http://localhost:8000 \ | ||
| -e PAPERLESS_API_KEY="$PAPERLESS_TOKEN" \ | ||
| paperless-mcp:e2e | ||
| for i in $(seq 1 20); do | ||
| code=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/mcp || true) | ||
| if [ "$code" = "405" ]; then | ||
| echo "MCP server (Docker) ready" | ||
| exit 0 | ||
| fi | ||
| sleep 1 | ||
| done | ||
| echo "MCP server (Docker) failed to start" | ||
| docker logs paperless-mcp-e2e | ||
| exit 1 | ||
|
|
||
| - name: Run E2E tests (Docker MCP) | ||
| if: ${{ !cancelled() }} | ||
| run: node --require ts-node/register --test e2e/e2e.test.ts 2>&1 | tee /tmp/e2e-docker-output.txt; exit "${PIPESTATUS[0]}" | ||
| env: | ||
| PAPERLESS_URL: http://localhost:8000 | ||
| PAPERLESS_TOKEN: ${{ env.PAPERLESS_TOKEN }} | ||
| MCP_URL: http://localhost:3000/mcp | ||
|
|
||
| - name: Print logs on failure | ||
| if: failure() | ||
| run: | | ||
| echo "=== E2E CLI test output ===" | ||
| cat /tmp/e2e-cli-output.txt || true | ||
| echo "=== E2E Docker test output ===" | ||
| cat /tmp/e2e-docker-output.txt || true | ||
| echo "=== MCP Docker logs ===" | ||
| docker logs paperless-mcp-e2e --tail=50 || true | ||
|
|
||
| - name: Cleanup | ||
| if: always() | ||
| run: | | ||
| kill "$MCP_CLI_PID" 2>/dev/null || true | ||
| docker rm -f paperless-mcp-e2e 2>/dev/null || true | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| services: | ||
| redis: | ||
| image: redis:7-alpine | ||
| restart: on-failure | ||
|
|
||
| paperless: | ||
| image: ghcr.io/paperless-ngx/paperless-ngx:2.14.7 | ||
| restart: on-failure | ||
| depends_on: | ||
| - redis | ||
| ports: | ||
| - "8000:8000" | ||
| volumes: | ||
| - paperless-data:/usr/src/paperless/data | ||
| - paperless-media:/usr/src/paperless/media | ||
| environment: | ||
| PAPERLESS_REDIS: redis://redis:6379 | ||
| PAPERLESS_ADMIN_USER: admin | ||
| PAPERLESS_ADMIN_PASSWORD: admin123 | ||
| PAPERLESS_ADMIN_MAIL: admin@test.local | ||
| PAPERLESS_SECRET_KEY: e2e-test-secret-not-for-production | ||
| PAPERLESS_OCR_LANGUAGE: eng | ||
| PAPERLESS_OCR_MODE: skip | ||
| PAPERLESS_TIME_ZONE: UTC | ||
| healthcheck: | ||
| test: ["CMD", "curl", "-f", "http://localhost:8000/api/"] | ||
| interval: 10s | ||
| timeout: 10s | ||
| retries: 30 | ||
| start_period: 60s | ||
|
|
||
| volumes: | ||
| paperless-data: | ||
| paperless-media: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| import { Client } from "@modelcontextprotocol/sdk/client/index.js"; | ||
| import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; | ||
|
|
||
| export type ToolResult = { | ||
| content: Array<{ type: string; text?: string; resource?: unknown }>; | ||
| isError?: boolean; | ||
| }; | ||
|
|
||
| export async function connectMcpClient( | ||
| mcpUrl: string, | ||
| token: string | ||
| ): Promise<Client> { | ||
| const transport = new StreamableHTTPClientTransport(new URL(mcpUrl), { | ||
| requestInit: { | ||
| headers: { Authorization: `Bearer ${token}` }, | ||
| }, | ||
| }); | ||
| const client = new Client({ name: "e2e-test", version: "1.0.0" }, {}); | ||
| await client.connect(transport); | ||
| return client; | ||
| } | ||
|
|
||
| export function parseToolText(result: ToolResult): unknown { | ||
| if (result.isError) { | ||
| const errContent = result.content.find((c) => c.type === "text"); | ||
| throw new Error(`Tool call returned isError=true: ${errContent?.text ?? "(no message)"}`); | ||
| } | ||
| const textContent = result.content.find((c) => c.type === "text"); | ||
| if (!textContent || !textContent.text) { | ||
| throw new Error("No text content in tool result"); | ||
| } | ||
| return JSON.parse(textContent.text); | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.