c7d3ec57c3
Commitd2217531(google-labs-jules SSRF fix) catastrophically deleted 11,500+ lines across 100+ files while fixing an unrelated IMAP issue. Restored from d2217531^ (pre-bad-commit state): Deleted files (fully restored): - app/api/{automation,classification_rules,comments,sharing}.py - app/middleware/upload_rate_limit.py - app/tasks/{automation_tasks,classify_document}.py - app/utils/{automation_hooks,classification_rules}.py - docs/AppleAppStoreCompliance.md - frontend/input.css, package.json, package-lock.json, tailwind.config.js - frontend/static/js/{annotations,claim,comments,sharing}.js - frontend/templates/{admin_connections,file_annotations,file_summary}.html - tests/{test_api_files_comprehensive,test_auth_extended,test_sharing, test_comments,test_connections,test_imap_profiles,test_api_sessions, test_automation,test_classification_rules,test_api_advanced_filters, test_api_classification_rules,test_upload_rate_limit,test_api_dropbox, test_classify_document,test_comments_ui,test_upload_to_icloud, test_api_onedrive_comprehensive,test_frontend_build,test_sentry, test_diagnostic,test_database,test_views_dropbox,test_local_auth}.py Truncated files (content restored): - app/{auth,config,main,models,celery_worker,database}.py - app/api/{__init__,api_tokens,diagnostic,dropbox,files,google_drive, integrations,local_auth,mobile,onedrive,pipelines,qr_auth, settings,url_upload}.py - app/middleware/upload_rate_limit.py - app/tasks/upload_to_nextcloud.py - app/utils/{allowed_types,settings_service,settings_sync,user_scope,webhook}.py - app/views/{base,dropbox,files,google_drive,onedrive,settings}.py - docs/{API,AuthenticationSetup,ConfigurationGuide,DatabaseConfiguration, DeploymentGuide,DropboxSetup,GoogleDriveSetup,KubernetesDeployment, MobileApp,OneDriveSetup,ProductionReadiness,SentrySetup, SocialLoginSetup,UserGuide}.md - frontend/static/{js/upload.js,styles.css} - frontend/templates/{api_tokens,base,devices,dropbox,dropbox_callback, file_view,files,google_drive,onedrive,onedrive_callback, signup}.html - frontend/translations/en.json - migrations/env.py - tests/{conftest,test_api_integrations,test_api_mobile,test_api_settings, test_api_tokens,test_audit_logs,test_duplicates,test_imap_tasks, test_setup_wizard,test_views_files_comprehensive}.py Security fixes kept from post-d2217531 commits: - app/utils/network.py: DNS SSRF fail-secure fix (06b0fced) - app/utils/file_operations.py: path traversal fix (1018ea17) - tests/test_imap_tasks.py: re-applied 4 is_private_ip mock patches Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com> Agent-Logs-Url: https://github.com/christianlouis/DocuElevate/sessions/51133dd8-9bec-41ab-aa10-3de753634187
211 lines
9.7 KiB
Python
211 lines
9.7 KiB
Python
"""Tests for the comments and annotations UI on the file annotations page."""
|
|
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
|
|
from app.models import FileRecord
|
|
|
|
|
|
def _create_file(db_session, tmp_path) -> FileRecord:
|
|
"""Create a minimal FileRecord with a real file path for the annotations page."""
|
|
file_path = tmp_path / "test.pdf"
|
|
file_path.write_bytes(b"%PDF-1.4")
|
|
f = FileRecord(
|
|
filehash="uihash123",
|
|
original_filename="test.pdf",
|
|
local_filename=str(file_path),
|
|
original_file_path=str(file_path),
|
|
file_size=1024,
|
|
mime_type="application/pdf",
|
|
)
|
|
db_session.add(f)
|
|
db_session.commit()
|
|
db_session.refresh(f)
|
|
return f
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestCommentsUIRendering:
|
|
"""Verify the file annotations page includes the comments panel HTML."""
|
|
|
|
def test_annotations_page_contains_comments_section(self, client: TestClient, db_session, tmp_path):
|
|
"""The annotations page should render the comments panel container."""
|
|
f = _create_file(db_session, tmp_path)
|
|
resp = client.get(f"/files/{f.id}/annotations")
|
|
assert resp.status_code == 200
|
|
html = resp.text
|
|
assert 'id="comments-list"' in html
|
|
assert 'id="comment-form"' in html
|
|
assert 'id="comment-input"' in html
|
|
|
|
def test_annotations_page_contains_annotations_section(self, client: TestClient, db_session, tmp_path):
|
|
"""The annotations page should render the annotations panel container."""
|
|
f = _create_file(db_session, tmp_path)
|
|
resp = client.get(f"/files/{f.id}/annotations")
|
|
assert resp.status_code == 200
|
|
html = resp.text
|
|
assert 'id="annotations-list"' in html
|
|
assert 'id="annotation-form"' in html
|
|
assert 'id="annotation-content-input"' in html
|
|
|
|
def test_annotations_page_loads_comments_js(self, client: TestClient, db_session, tmp_path):
|
|
"""The annotations page should include the comments JavaScript file."""
|
|
f = _create_file(db_session, tmp_path)
|
|
resp = client.get(f"/files/{f.id}/annotations")
|
|
assert resp.status_code == 200
|
|
assert "js/comments.js" in resp.text
|
|
|
|
def test_annotations_page_loads_annotations_js(self, client: TestClient, db_session, tmp_path):
|
|
"""The annotations page should include the annotations JavaScript file."""
|
|
f = _create_file(db_session, tmp_path)
|
|
resp = client.get(f"/files/{f.id}/annotations")
|
|
assert resp.status_code == 200
|
|
assert "js/annotations.js" in resp.text
|
|
|
|
def test_annotations_page_has_mention_dropdown(self, client: TestClient, db_session, tmp_path):
|
|
"""The mention autocomplete dropdown should be present."""
|
|
f = _create_file(db_session, tmp_path)
|
|
resp = client.get(f"/files/{f.id}/annotations")
|
|
assert resp.status_code == 200
|
|
assert 'id="mention-dropdown"' in resp.text
|
|
|
|
def test_annotations_page_has_annotation_form_fields(self, client: TestClient, db_session, tmp_path):
|
|
"""Annotation form should have page, type, and color inputs."""
|
|
f = _create_file(db_session, tmp_path)
|
|
resp = client.get(f"/files/{f.id}/annotations")
|
|
assert resp.status_code == 200
|
|
html = resp.text
|
|
assert 'id="annotation-page-input"' in html
|
|
assert 'id="annotation-type-input"' in html
|
|
assert 'id="annotation-color-input"' in html
|
|
|
|
def test_annotations_page_has_collab_grid(self, client: TestClient, db_session, tmp_path):
|
|
"""Comments and annotations should be in a side-by-side grid layout."""
|
|
f = _create_file(db_session, tmp_path)
|
|
resp = client.get(f"/files/{f.id}/annotations")
|
|
assert resp.status_code == 200
|
|
assert "collab-grid" in resp.text
|
|
|
|
def test_annotations_page_no_comments_for_missing_file(self, client: TestClient):
|
|
"""When file is not found, no comments section should appear."""
|
|
resp = client.get("/files/99999/annotations")
|
|
assert resp.status_code == 200
|
|
# The error block is shown, not the main content
|
|
assert 'id="comments-list"' not in resp.text
|
|
|
|
def test_annotations_page_annotation_type_options(self, client: TestClient, db_session, tmp_path):
|
|
"""Annotation type selector should include all four types."""
|
|
f = _create_file(db_session, tmp_path)
|
|
resp = client.get(f"/files/{f.id}/annotations")
|
|
assert resp.status_code == 200
|
|
html = resp.text
|
|
assert 'value="note"' in html
|
|
assert 'value="highlight"' in html
|
|
assert 'value="underline"' in html
|
|
assert 'value="strikethrough"' in html
|
|
|
|
def test_annotations_page_comments_panel_accessibility(self, client: TestClient, db_session, tmp_path):
|
|
"""Comments panel should have proper ARIA attributes."""
|
|
f = _create_file(db_session, tmp_path)
|
|
resp = client.get(f"/files/{f.id}/annotations")
|
|
assert resp.status_code == 200
|
|
html = resp.text
|
|
assert 'aria-live="polite"' in html
|
|
assert 'role="listbox"' in html
|
|
|
|
def test_annotations_page_init_script(self, client: TestClient, db_session, tmp_path):
|
|
"""The init script should call initComments and initAnnotations."""
|
|
f = _create_file(db_session, tmp_path)
|
|
resp = client.get(f"/files/{f.id}/annotations")
|
|
assert resp.status_code == 200
|
|
html = resp.text
|
|
assert "initComments" in html
|
|
assert "initAnnotations" in html
|
|
|
|
def test_comments_url_redirects_to_annotations(self, client: TestClient, db_session, tmp_path):
|
|
"""The /comments URL should redirect to /annotations."""
|
|
f = _create_file(db_session, tmp_path)
|
|
resp = client.get(f"/files/{f.id}/comments", follow_redirects=False)
|
|
assert resp.status_code == 302
|
|
assert f"/files/{f.id}/annotations" in resp.headers["location"]
|
|
|
|
def test_process_page_no_comments_section(self, client: TestClient, db_session, tmp_path):
|
|
"""The process page should NOT render the comments panel."""
|
|
f = _create_file(db_session, tmp_path)
|
|
resp = client.get(f"/files/{f.id}/process")
|
|
assert resp.status_code == 200
|
|
html = resp.text
|
|
assert 'id="comments-list"' not in html
|
|
assert 'id="comment-form"' not in html
|
|
|
|
def test_detail_page_no_comments_section(self, client: TestClient, db_session, tmp_path):
|
|
"""The detail page should NOT render the comments panel."""
|
|
f = _create_file(db_session, tmp_path)
|
|
resp = client.get(f"/files/{f.id}/detail")
|
|
assert resp.status_code == 200
|
|
html = resp.text
|
|
assert 'id="comments-list"' not in html
|
|
assert 'id="annotation-form"' not in html
|
|
|
|
def test_annotations_page_has_embedpdf_viewer_for_pdf(self, client: TestClient, db_session, tmp_path):
|
|
"""The annotations page should include the EmbedPDF viewer for PDF files."""
|
|
f = _create_file(db_session, tmp_path)
|
|
resp = client.get(f"/files/{f.id}/annotations")
|
|
assert resp.status_code == 200
|
|
html = resp.text
|
|
assert 'id="embedpdf-viewer"' in html
|
|
assert "@embedpdf/snippet" in html
|
|
|
|
def test_embedpdf_init_subscribes_to_page_change(self, client: TestClient, db_session, tmp_path):
|
|
"""The EmbedPDF init script should subscribe to page change events to sync the form."""
|
|
f = _create_file(db_session, tmp_path)
|
|
resp = client.get(f"/files/{f.id}/annotations")
|
|
assert resp.status_code == 200
|
|
html = resp.text
|
|
# Verifies the viewer registry is awaited and scroll plugin is used
|
|
assert "viewer.registry" in html
|
|
assert "onPageChange" in html
|
|
assert "annotation-page-input" in html
|
|
|
|
def test_embedpdf_init_exposes_scroll_function(self, client: TestClient, db_session, tmp_path):
|
|
"""The EmbedPDF init script must expose _embedpdfScrollToPage for the annotations panel."""
|
|
f = _create_file(db_session, tmp_path)
|
|
resp = client.get(f"/files/{f.id}/annotations")
|
|
assert resp.status_code == 200
|
|
assert "_embedpdfScrollToPage" in resp.text
|
|
assert "scrollToPage" in resp.text
|
|
|
|
def test_embedpdf_init_saves_viewer_annotations(self, client: TestClient, db_session, tmp_path):
|
|
"""The EmbedPDF init script should capture annotation events and POST to the API."""
|
|
f = _create_file(db_session, tmp_path)
|
|
resp = client.get(f"/files/{f.id}/annotations")
|
|
assert resp.status_code == 200
|
|
html = resp.text
|
|
assert "onAnnotationEvent" in html
|
|
# Verifies the POST target is the annotations API for this file
|
|
assert "/api/files/" in html and "/annotations" in html
|
|
|
|
def test_embedpdf_init_reloads_annotation_list(self, client: TestClient, db_session, tmp_path):
|
|
"""After auto-saving a viewer annotation, the panel list should be refreshed."""
|
|
f = _create_file(db_session, tmp_path)
|
|
resp = client.get(f"/files/{f.id}/annotations")
|
|
assert resp.status_code == 200
|
|
assert "_reloadAnnotations" in resp.text
|
|
|
|
def test_annotations_page_has_go_to_page_i18n(self, client: TestClient, db_session, tmp_path):
|
|
"""The annotations i18n bundle should include the go_to_page key."""
|
|
f = _create_file(db_session, tmp_path)
|
|
resp = client.get(f"/files/{f.id}/annotations")
|
|
assert resp.status_code == 200
|
|
assert "go_to_page" in resp.text
|
|
|
|
def test_summary_page_renders(self, client: TestClient, db_session, tmp_path):
|
|
"""The summary page at /files/{id} should render correctly."""
|
|
f = _create_file(db_session, tmp_path)
|
|
resp = client.get(f"/files/{f.id}")
|
|
assert resp.status_code == 200
|
|
html = resp.text
|
|
assert "Document Detail" in html
|
|
assert "Processing" in html
|
|
assert "Comments" in html or "Annotations" in html
|