fieldwitness/tests/e2e/test_dropbox.py
Aaron D. Lee 16318daea3
Some checks failed
CI / lint (push) Failing after 12s
CI / typecheck (push) Failing after 12s
Add comprehensive test suite: integration tests + Playwright e2e
Integration tests (350 passing):
- test_evidence_summary.py: HTML/PDF generation, XSS safety, anchor rendering
- test_tor.py: Tor module unit tests (mocked, no Tor needed)
- test_c2pa_importer.py: Import result dataclass, trust evaluation, graceful degradation
- test_file_attestation.py: All file types (PNG, PDF, CSV, empty, large), determinism
- test_paths.py: Registry correctness, env var override, all paths under BASE_DIR
- test_killswitch_coverage.py: Tor keys, trusted keys, carrier history destruction

Playwright e2e infrastructure:
- tests/e2e/ with conftest (live server, auth fixtures), helpers (test file generators)
- test_auth.py: Setup flow, login/logout, protected routes
- test_attest.py: Image/PDF/CSV attestation, verify, attestation log
- test_dropbox.py: Token creation, source upload, branding check
- test_keys.py: Identity display, trust store
- test_fieldkit.py: Status dashboard, killswitch page
- test_navigation.py: All nav links, responsive layout

Run: pytest (unit/integration) or pytest -m e2e tests/e2e/ (browser)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 20:22:12 -04:00

187 lines
7.0 KiB
Python

"""
e2e tests for the source drop box feature.
The drop box is split into two distinct surfaces:
- Admin surface (/dropbox/admin) — authenticated, token management
- Source surface (/dropbox/upload/<token>) — unauthenticated, CSRF-exempt
Tests that exercise the source surface navigate in a fresh browser context
(or just navigate directly to the upload URL) to confirm there is no
session/authentication requirement on that path.
"""
from __future__ import annotations
import re
import time
import pytest
from playwright.sync_api import Page, expect
from tests.e2e.helpers import create_test_image, create_test_txt
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _create_token(page: Page, live_server: str, label: str = "e2e source", hours: int = 24) -> str:
"""Create a drop box token via the admin UI and return the full upload URL."""
page.goto(f"{live_server}/dropbox/admin")
page.wait_for_load_state("networkidle")
label_input = page.locator("input[name='label']")
if label_input.count():
label_input.fill(label)
page.fill("input[name='hours']", str(hours))
page.fill("input[name='max_files']", "5")
page.click("button[type='submit']")
page.wait_for_load_state("networkidle")
# Flash message contains the upload URL
flash = page.locator("[class*='alert']").first.inner_text()
match = re.search(r"http://\S+", flash)
if not match:
raise RuntimeError(f"No upload URL found in flash message: {flash!r}")
return match.group(0)
# ---------------------------------------------------------------------------
# Admin panel tests
# ---------------------------------------------------------------------------
@pytest.mark.e2e
def test_dropbox_admin_page(live_server: str, authenticated_page: Page) -> None:
"""The /dropbox/admin page loads and shows the token creation form."""
page = authenticated_page
page.goto(f"{live_server}/dropbox/admin")
page.wait_for_load_state("networkidle")
expect(page.locator("input[name='label']")).to_be_visible()
expect(page.locator("input[name='hours']")).to_be_visible()
expect(page.locator("input[name='max_files']")).to_be_visible()
expect(page.locator("button[type='submit']")).to_be_visible()
@pytest.mark.e2e
def test_create_upload_token(live_server: str, authenticated_page: Page) -> None:
"""Creating a token shows a success flash and the token appears in the active list."""
page = authenticated_page
upload_url = _create_token(page, live_server, label="my-e2e-source")
# The upload URL must contain the expected prefix
assert "/dropbox/upload/" in upload_url, f"Unexpected upload URL: {upload_url}"
# The token should now appear in the active token table
# (token[:12] is shown in the table as per the template)
token_slug = upload_url.split("/dropbox/upload/")[1].split("?")[0]
table = page.locator("table")
if table.count() > 0:
expect(table).to_contain_text(token_slug[:12])
@pytest.mark.e2e
def test_source_upload_page_accessible_without_auth(
live_server: str, authenticated_page: Page, page: Page
) -> None:
"""The source upload page is accessible without any authentication.
We get the URL via the admin (authenticated), then open it in a *separate*
fresh page that has no session cookie.
"""
upload_url = _create_token(authenticated_page, live_server, label="anon-test")
# Navigate to the upload URL in the unauthenticated page
page.goto(upload_url)
page.wait_for_load_state("networkidle")
# Must not redirect to login
assert "/login" not in page.url, (
f"Source upload page redirected to login: {page.url}"
)
# Upload form must be present
expect(page.locator("input[type='file'], input[name='files']")).to_be_visible()
@pytest.mark.e2e
def test_source_upload_file(live_server: str, authenticated_page: Page, page: Page) -> None:
"""A source can upload a file via the drop box and receives a receipt code."""
upload_url = _create_token(authenticated_page, live_server, label="upload-test")
# Submit a file as the anonymous source (unauthenticated page)
page.goto(upload_url)
page.wait_for_load_state("networkidle")
txt_bytes = create_test_txt()
page.set_input_files(
"input[type='file'], input[name='files']",
files=[{"name": "tip.txt", "mimeType": "text/plain", "buffer": txt_bytes}],
)
page.click("button[type='submit']")
page.wait_for_load_state("networkidle")
# The response page should show a receipt code
body_text = page.locator("body").inner_text()
assert any(word in body_text.lower() for word in ("receipt", "success", "received", "upload")), (
f"No success/receipt indication found after upload. Body: {body_text[:300]}"
)
@pytest.mark.e2e
def test_invalid_token_rejected(live_server: str, page: Page) -> None:
"""A request with an invalid/missing token returns 404, not a login redirect."""
page.goto(f"{live_server}/dropbox/upload/totally-invalid-token-xyz")
page.wait_for_load_state("networkidle")
# Should be a 404 / plain-text "expired or invalid" message, NOT a redirect to login
assert "/login" not in page.url, (
f"Invalid token redirected to login instead of showing 404: {page.url}"
)
body = page.locator("body").inner_text()
assert any(word in body.lower() for word in ("expired", "invalid", "not found")), (
f"Expected 'expired or invalid' message, got: {body[:200]}"
)
@pytest.mark.e2e
def test_revoke_token(live_server: str, authenticated_page: Page, page: Page) -> None:
"""An admin can revoke a token; after revocation the upload URL returns 404."""
admin_page = authenticated_page
upload_url = _create_token(admin_page, live_server, label="revoke-test")
token = upload_url.split("/dropbox/upload/")[1].split("?")[0]
# Verify the token works before revocation
page.goto(upload_url)
page.wait_for_load_state("networkidle")
assert "/login" not in page.url
# Revoke via admin UI
admin_page.goto(f"{live_server}/dropbox/admin")
admin_page.wait_for_load_state("networkidle")
# Find the revoke button for this token and click it
revoke_form = admin_page.locator(f"form input[name='token'][value='{token}']").locator("..")
if revoke_form.count() == 0:
# Try by partial token match in the table row
revoke_form = admin_page.locator("form").filter(
has=admin_page.locator(f"input[value='{token}']")
)
if revoke_form.count() > 0:
revoke_form.locator("button[type='submit']").click()
admin_page.wait_for_load_state("networkidle")
# Now the upload URL should return 404
page.goto(upload_url)
page.wait_for_load_state("networkidle")
body = page.locator("body").inner_text()
assert any(word in body.lower() for word in ("expired", "invalid", "not found")), (
f"Expected 404 after revocation, got: {body[:200]}"
)