diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..bc644a9f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "vendor/embed-pdf-viewer"] + path = vendor/embed-pdf-viewer + url = https://github.com/embedpdf/embed-pdf-viewer.git diff --git a/app/views/files.py b/app/views/files.py index 2f9bc3c4..af9e2e5d 100644 --- a/app/views/files.py +++ b/app/views/files.py @@ -206,10 +206,91 @@ def files_page( @router.get("/files/{file_id}") @require_login +def file_summary_page(request: Request, file_id: int, db: Session = Depends(get_db)): + """ + Return the file summary page — a concise overview with links to detail, processing, and annotations views. + """ + try: + import json + import os + + from app.models import FileRecord + + file_record = db.query(FileRecord).filter(FileRecord.id == file_id).first() + + if not file_record: + return templates.TemplateResponse( + "file_summary.html", + {"request": request, "file": None, "error": f"File with ID {file_id} not found"}, + ) + + from app.config import settings + + workdir = os.path.realpath(settings.workdir) + + def _safe_exists(path: str | None) -> bool: + """Return True only when *path* exists and resides within workdir.""" + if not path: + return False + resolved = os.path.realpath(path) + try: + common = os.path.commonpath([resolved, workdir]) + except ValueError: + return False + return common == workdir and os.path.exists(resolved) + + original_file_exists = _safe_exists(file_record.original_file_path) + processed_file_exists = _safe_exists(file_record.processed_file_path) + + # Load AI metadata — JSON sidecar file first, then DB column + gpt_metadata = None + if file_record.processed_file_path: + metadata_path = os.path.splitext(os.path.realpath(file_record.processed_file_path))[0] + ".json" + if _safe_exists(metadata_path): + try: + with open(metadata_path, "r", encoding="utf-8") as f: + gpt_metadata = json.load(f) + except Exception as e: + logger.warning(f"Failed to load metadata sidecar for file {file_id}: {e}") + + if gpt_metadata is None and file_record.ai_metadata: + try: + gpt_metadata = json.loads(file_record.ai_metadata) + except Exception as e: + logger.warning(f"Failed to parse ai_metadata for file {file_id}: {e}") + + # Quick processing status + try: + from app.utils.step_manager import get_step_summary as _get_step_summary + + step_summary = _get_step_summary(db, file_id) + except Exception: + step_summary = None + + pipeline_info = _resolve_pipeline(db, file_record) + + return templates.TemplateResponse( + "file_summary.html", + { + "request": request, + "file": file_record, + "gpt_metadata": gpt_metadata, + "original_file_exists": original_file_exists, + "processed_file_exists": processed_file_exists, + "step_summary": step_summary, + "pipeline_info": pipeline_info, + }, + ) + except Exception as e: + logger.error(f"Error retrieving file summary {file_id}: {str(e)}") + return templates.TemplateResponse("file_summary.html", {"request": request, "file": None, "error": str(e)}) + + +@router.get("/files/{file_id}/detail") +@require_login def file_view_page(request: Request, file_id: int, db: Session = Depends(get_db)): """ - Return the document view page — document-centric view with metadata, preview, and extracted text. - Process-oriented details are available via /files/{file_id}/detail. + Return the document detail page — document-centric view with metadata, preview, and extracted text. """ try: import json @@ -290,11 +371,11 @@ def file_view_page(request: Request, file_id: int, db: Session = Depends(get_db) return templates.TemplateResponse("file_view.html", {"request": request, "file": None, "error": str(e)}) -@router.get("/files/{file_id}/detail") +@router.get("/files/{file_id}/process") @require_login def file_detail_page(request: Request, file_id: int, db: Session = Depends(get_db)): """ - Return the file detail page showing processing history and file information + Return the file processing page showing processing history and pipeline information. """ try: import json @@ -375,6 +456,73 @@ def file_detail_page(request: Request, file_id: int, db: Session = Depends(get_d return templates.TemplateResponse("file_detail.html", {"request": request, "file": None, "error": str(e)}) +@router.get("/files/{file_id}/annotations") +@require_login +def file_annotations_page(request: Request, file_id: int, db: Session = Depends(get_db)): + """ + Return the comments & annotations page for a file. + """ + try: + import os + + from app.models import FileRecord + + file_record = db.query(FileRecord).filter(FileRecord.id == file_id).first() + + if not file_record: + return templates.TemplateResponse( + "file_annotations.html", + {"request": request, "file": None, "error": f"File with ID {file_id} not found"}, + ) + + from app.config import settings + + workdir = os.path.realpath(settings.workdir) + + def _safe_exists(path: str | None) -> bool: + """Return True only when *path* exists and resides within workdir.""" + if not path: + return False + resolved = os.path.realpath(path) + try: + common = os.path.commonpath([resolved, workdir]) + except ValueError: + return False + return common == workdir and os.path.exists(resolved) + + original_file_exists = _safe_exists(file_record.original_file_path) + processed_file_exists = _safe_exists(file_record.processed_file_path) + + # Determine whether the file is a PDF (for EmbedPDF viewer) + mime = file_record.mime_type or "" + is_pdf = mime == "application/pdf" or (file_record.original_filename or "").lower().endswith(".pdf") + + return templates.TemplateResponse( + "file_annotations.html", + { + "request": request, + "file": file_record, + "original_file_exists": original_file_exists, + "processed_file_exists": processed_file_exists, + "is_pdf": is_pdf, + }, + ) + except Exception as e: + logger.error(f"Error retrieving annotations for file {file_id}: {str(e)}") + return templates.TemplateResponse("file_annotations.html", {"request": request, "file": None, "error": str(e)}) + + +@router.get("/files/{file_id}/comments") +@require_login +def file_comments_redirect(request: Request, file_id: int): + """ + Redirect /files/{file_id}/comments to /files/{file_id}/annotations. + """ + from starlette.responses import RedirectResponse + + return RedirectResponse(url=f"/files/{file_id}/annotations", status_code=302) + + # --------------------------------------------------------------------------- # Pipeline ↔ Celery-log stage mapping # --------------------------------------------------------------------------- diff --git a/frontend/templates/file_annotations.html b/frontend/templates/file_annotations.html new file mode 100644 index 00000000..299ba942 --- /dev/null +++ b/frontend/templates/file_annotations.html @@ -0,0 +1,746 @@ +{% extends "base.html" %} +{% block title %}Comments & Annotations - {{ file.original_filename or 'Document' }} - DocuElevate{% endblock %} + +{% block head_extra %} + + +{% endblock %} + +{% block content %} +
+ + {% if error %} +
Error: {{ error }}
+ {% elif file %} + + +
+ + Back to File + +
+ +
+
+
+ + Comments & Annotations +
+
{{ file.original_filename }}
+
+
+ + + {% if is_pdf and (processed_file_exists or original_file_exists) %} +
+
+ + Document Viewer +
+
+
+ {% endif %} + + +
+
+ +
+
+

{{ _("comments.heading") }}

+
+
+ + +
+
+
+ + +
+ +
+
+
+ + +
+
+

{{ _("annotations.heading") }}

+
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+
+
+
+ + {% else %} + +
Document not found.
+ {% endif %} + +
+ + + + + + +{% if file and is_pdf and (processed_file_exists or original_file_exists) %} + +{% endif %} +{% endblock %} diff --git a/frontend/templates/file_detail.html b/frontend/templates/file_detail.html index 1642313b..7c00f2b0 100644 --- a/frontend/templates/file_detail.html +++ b/frontend/templates/file_detail.html @@ -1056,7 +1056,7 @@ html += `
- +
{% endif %} - -
-
- -
-
-

{{ _("comments.heading") }}

-
-
- - -
-
-
- - -
- -
-
-
- - -
-
-

{{ _("annotations.heading") }}

-
-
- - -
-
- -
-
- - -
-
- - -
-
- - -
-
- -
-
-
-
-
- {% else %}
Document not found.
@@ -1138,9 +557,6 @@
- - - {% endblock %} diff --git a/frontend/templates/files.html b/frontend/templates/files.html index 1d142a03..10892093 100644 --- a/frontend/templates/files.html +++ b/frontend/templates/files.html @@ -904,7 +904,7 @@ function viewFileDetail(fileId, event) { if (event) event.stopPropagation(); - window.location.href = `/files/${fileId}/detail`; + window.location.href = `/files/${fileId}`; } // ── Preview modal ── diff --git a/tests/test_comments_ui.py b/tests/test_comments_ui.py index 7a44a7b0..059610a5 100644 --- a/tests/test_comments_ui.py +++ b/tests/test_comments_ui.py @@ -1,4 +1,4 @@ -"""Tests for the comments and annotations UI on the file view page.""" +"""Tests for the comments and annotations UI on the file annotations page.""" import pytest from fastapi.testclient import TestClient @@ -7,7 +7,7 @@ from app.models import FileRecord def _create_file(db_session, tmp_path) -> FileRecord: - """Create a minimal FileRecord with a real file path for the view page.""" + """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( @@ -26,77 +26,77 @@ def _create_file(db_session, tmp_path) -> FileRecord: @pytest.mark.unit class TestCommentsUIRendering: - """Verify the file view page includes the comments panel HTML.""" + """Verify the file annotations page includes the comments panel HTML.""" - def test_view_page_contains_comments_section(self, client: TestClient, db_session, tmp_path): - """The view page should render the comments panel container.""" + 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}") + 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_view_page_contains_annotations_section(self, client: TestClient, db_session, tmp_path): - """The view page should render the annotations panel container.""" + 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}") + 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_view_page_loads_comments_js(self, client: TestClient, db_session, tmp_path): - """The view page should include the comments JavaScript file.""" + 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}") + resp = client.get(f"/files/{f.id}/annotations") assert resp.status_code == 200 assert "js/comments.js" in resp.text - def test_view_page_loads_annotations_js(self, client: TestClient, db_session, tmp_path): - """The view page should include the annotations JavaScript file.""" + 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}") + resp = client.get(f"/files/{f.id}/annotations") assert resp.status_code == 200 assert "js/annotations.js" in resp.text - def test_view_page_has_mention_dropdown(self, client: TestClient, db_session, tmp_path): + 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}") + resp = client.get(f"/files/{f.id}/annotations") assert resp.status_code == 200 assert 'id="mention-dropdown"' in resp.text - def test_view_page_has_annotation_form_fields(self, client: TestClient, db_session, tmp_path): + 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}") + 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_view_page_has_collab_grid(self, client: TestClient, db_session, tmp_path): + 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}") + resp = client.get(f"/files/{f.id}/annotations") assert resp.status_code == 200 assert "collab-grid" in resp.text - def test_view_page_no_comments_for_missing_file(self, client: TestClient): + 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") + 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_view_page_annotation_type_options(self, client: TestClient, db_session, tmp_path): + 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}") + resp = client.get(f"/files/{f.id}/annotations") assert resp.status_code == 200 html = resp.text assert 'value="note"' in html @@ -104,38 +104,64 @@ class TestCommentsUIRendering: assert 'value="underline"' in html assert 'value="strikethrough"' in html - def test_view_page_comments_panel_accessibility(self, client: TestClient, db_session, tmp_path): + 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}") + 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_view_page_init_script(self, client: TestClient, db_session, tmp_path): + 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}") + 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_detail_page_no_comments_section(self, client: TestClient, db_session, tmp_path): - """The detail page should NOT render the comments panel (moved to view page).""" + 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}/detail") + 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_annotations_section(self, client: TestClient, db_session, tmp_path): - """The detail page should NOT render the annotations panel (moved to view page).""" + 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="annotations-list"' not in html + 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_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 diff --git a/tests/test_document_preview.py b/tests/test_document_preview.py index 45e46c3c..1a9dfa52 100644 --- a/tests/test_document_preview.py +++ b/tests/test_document_preview.py @@ -57,7 +57,7 @@ class TestFileViewPdfJs: pdf.write_bytes(b"%PDF-1.4 test") rec = _create_file_record(db_session, file_path=str(pdf), mime_type="application/pdf") - response = client.get(f"/files/{rec.id}") + response = client.get(f"/files/{rec.id}/detail") assert response.status_code == 200 html = response.text @@ -80,7 +80,7 @@ class TestFileViewPdfJs: pdf.write_bytes(b"%PDF-1.4 test") rec = _create_file_record(db_session, file_path=str(pdf), mime_type="application/pdf") - response = client.get(f"/files/{rec.id}") + response = client.get(f"/files/{rec.id}/detail") html = response.text # The preview section should use pdf-viewer, not iframe @@ -93,7 +93,7 @@ class TestFileViewPdfJs: pdf.write_bytes(b"%PDF-1.4 test") rec = _create_file_record(db_session, file_path=str(pdf), mime_type="application/pdf") - response = client.get(f"/files/{rec.id}") + response = client.get(f"/files/{rec.id}/detail") html = response.text assert 'id="pdf-prev-btn"' in html @@ -116,7 +116,7 @@ class TestFileViewImagePreview: img.write_bytes(b"\xff\xd8\xff\xe0" + b"\x00" * 100) # minimal JPEG header rec = _create_file_record(db_session, filename="photo.jpg", mime_type="image/jpeg", file_path=str(img)) - response = client.get(f"/files/{rec.id}") + response = client.get(f"/files/{rec.id}/detail") assert response.status_code == 200 html = response.text @@ -132,7 +132,7 @@ class TestFileViewImagePreview: img.write_bytes(b"\x89PNG\r\n\x1a\n" + b"\x00" * 50) rec = _create_file_record(db_session, filename="photo.png", mime_type="image/png", file_path=str(img)) - response = client.get(f"/files/{rec.id}") + response = client.get(f"/files/{rec.id}/detail") html = response.text assert 'aria-label="Zoom in"' in html @@ -146,7 +146,7 @@ class TestFileViewImagePreview: img.write_bytes(b"RIFF" + b"\x00" * 50) rec = _create_file_record(db_session, filename="wide.webp", mime_type="image/webp", file_path=str(img)) - response = client.get(f"/files/{rec.id}") + response = client.get(f"/files/{rec.id}/detail") html = response.text # Pan support is implemented via JavaScript on img-wrap @@ -170,7 +170,7 @@ class TestFileViewTextPreview: txt.write_text("Hello world\nSecond line\n") rec = _create_file_record(db_session, filename="readme.txt", mime_type="text/plain", file_path=str(txt)) - response = client.get(f"/files/{rec.id}") + response = client.get(f"/files/{rec.id}/detail") assert response.status_code == 200 html = response.text @@ -184,7 +184,7 @@ class TestFileViewTextPreview: txt.write_text("print('hello')\n") rec = _create_file_record(db_session, filename="code.py", mime_type="text/x-python", file_path=str(txt)) - response = client.get(f"/files/{rec.id}") + response = client.get(f"/files/{rec.id}/detail") html = response.text assert "copyTextPreview" in html @@ -196,7 +196,7 @@ class TestFileViewTextPreview: txt.write_text("a,b,c\n1,2,3\n") rec = _create_file_record(db_session, filename="data.csv", mime_type="text/csv", file_path=str(txt)) - response = client.get(f"/files/{rec.id}") + response = client.get(f"/files/{rec.id}/detail") html = response.text # JS builds line-number spans @@ -218,7 +218,7 @@ class TestFileViewPreviewIcon: pdf.write_bytes(b"%PDF-1.4") rec = _create_file_record(db_session, file_path=str(pdf), mime_type="application/pdf") - html = client.get(f"/files/{rec.id}").text + html = client.get(f"/files/{rec.id}/detail").text assert "fa-file-pdf" in html def test_image_icon(self, client: TestClient, db_session, tmp_path): @@ -227,7 +227,7 @@ class TestFileViewPreviewIcon: img.write_bytes(b"\xff\xd8\xff\xe0" + b"\x00" * 10) rec = _create_file_record(db_session, filename="p.jpg", mime_type="image/jpeg", file_path=str(img)) - html = client.get(f"/files/{rec.id}").text + html = client.get(f"/files/{rec.id}/detail").text assert "fa-image" in html def test_text_icon(self, client: TestClient, db_session, tmp_path): @@ -236,7 +236,7 @@ class TestFileViewPreviewIcon: txt.write_text("hello") rec = _create_file_record(db_session, filename="t.txt", mime_type="text/plain", file_path=str(txt)) - html = client.get(f"/files/{rec.id}").text + html = client.get(f"/files/{rec.id}/detail").text assert "fa-file-code" in html @@ -321,7 +321,7 @@ class TestFileDetailBottomPreview: pdf.write_bytes(b"%PDF-1.4") rec = _create_file_record(db_session, file_path=str(pdf), processed_path=str(pdf)) - response = client.get(f"/files/{rec.id}/detail") + response = client.get(f"/files/{rec.id}/process") assert response.status_code == 200 html = response.text @@ -335,7 +335,7 @@ class TestFileDetailBottomPreview: pdf.write_bytes(b"%PDF-1.4") rec = _create_file_record(db_session, file_path=str(pdf), processed_path=str(pdf)) - response = client.get(f"/files/{rec.id}/detail") + response = client.get(f"/files/{rec.id}/process") html = response.text assert f"/api/files/{rec.id}/download" in html @@ -350,7 +350,7 @@ class TestFileDetailBottomPreview: file_path=str(img), ) - response = client.get(f"/files/{rec.id}/detail") + response = client.get(f"/files/{rec.id}/process") html = response.text assert f"/api/files/{rec.id}/preview?version=original" in html @@ -372,7 +372,7 @@ class TestFileViewOcrText: rec.ocr_text = "Sample extracted OCR text content" db_session.commit() - html = client.get(f"/files/{rec.id}").text + html = client.get(f"/files/{rec.id}/detail").text assert "toggleOcrText" in html assert "ocr-text-block" in html assert "Sample extracted OCR text content" in html @@ -383,7 +383,7 @@ class TestFileViewOcrText: pdf.write_bytes(b"%PDF-1.4") rec = _create_file_record(db_session, file_path=str(pdf)) - html = client.get(f"/files/{rec.id}").text + html = client.get(f"/files/{rec.id}/detail").text assert "loadText" in html or "Extract" in html @@ -409,5 +409,5 @@ class TestFileViewNoFile: db_session.commit() db_session.refresh(rec) - html = client.get(f"/files/{rec.id}").text + html = client.get(f"/files/{rec.id}/detail").text assert "No file available for preview" in html diff --git a/tests/test_file_detail_endpoints.py b/tests/test_file_detail_endpoints.py index f5536116..a519dc8c 100644 --- a/tests/test_file_detail_endpoints.py +++ b/tests/test_file_detail_endpoints.py @@ -447,7 +447,7 @@ class TestFileDetailView: db_session.commit() # Test detail view - response = client.get(f"/files/{file_record.id}/detail") + response = client.get(f"/files/{file_record.id}/process") assert response.status_code == 200 # Check that response contains HTML with file information assert b"File Information" in response.content @@ -504,7 +504,7 @@ class TestFileDetailView: db_session.commit() # Test detail view - response = client.get(f"/files/{file_record.id}/detail") + response = client.get(f"/files/{file_record.id}/process") assert response.status_code == 200 # Check that response contains branching visualization elements assert b"Process Flow Visualization" in response.content @@ -514,7 +514,7 @@ class TestFileDetailView: def test_file_detail_view_nonexistent(self, client: TestClient): """Test file detail view for nonexistent file.""" - response = client.get("/files/99999/detail") + response = client.get("/files/99999/process") assert response.status_code == 200 # Returns page with error message assert b"not found" in response.content.lower() diff --git a/tests/test_file_detail_enhancements.py b/tests/test_file_detail_enhancements.py index c6645628..e0a4312f 100644 --- a/tests/test_file_detail_enhancements.py +++ b/tests/test_file_detail_enhancements.py @@ -50,7 +50,7 @@ def test_file_detail_page_with_metadata(client: TestClient, db_session, sample_p db_session.refresh(file_record) # Get detail page - response = client.get(f"/files/{file_record.id}/detail") + response = client.get(f"/files/{file_record.id}/process") assert response.status_code == 200 html = response.text @@ -85,7 +85,7 @@ def test_file_detail_with_gpt_metadata(client: TestClient, db_session, sample_pd db_session.refresh(file_record) # Get detail page - response = client.get(f"/files/{file_record.id}/detail") + response = client.get(f"/files/{file_record.id}/process") assert response.status_code == 200 html = response.text @@ -190,7 +190,7 @@ def test_file_detail_shows_file_status_indicators(client: TestClient, db_session db_session.commit() db_session.refresh(file_record) - response = client.get(f"/files/{file_record.id}/detail") + response = client.get(f"/files/{file_record.id}/process") assert response.status_code == 200 html = response.text diff --git a/tests/test_files_view_extended.py b/tests/test_files_view_extended.py index d968b641..bd66c447 100644 --- a/tests/test_files_view_extended.py +++ b/tests/test_files_view_extended.py @@ -142,7 +142,7 @@ class TestFileDetailPage: db_session.commit() # Test file detail page - response = client.get(f"/files/{file_record.id}/detail") + response = client.get(f"/files/{file_record.id}/process") assert response.status_code == 200 content = response.text assert "test.pdf" in content @@ -150,7 +150,7 @@ class TestFileDetailPage: def test_file_detail_page_with_missing_file(self, client: TestClient, db_session): """Test file detail page with non-existent file""" # Try to access non-existent file - response = client.get("/files/99999/detail") + response = client.get("/files/99999/process") assert response.status_code == 200 content = response.text assert "not found" in content.lower() @@ -193,7 +193,7 @@ class TestFileDetailPage: db_session.commit() # Test file detail page - response = client.get(f"/files/{file_record.id}/detail") + response = client.get(f"/files/{file_record.id}/process") assert response.status_code == 200 content = response.text assert "create_file_record" in content @@ -232,7 +232,7 @@ class TestFileDetailPage: db_session.commit() # Test file detail page - response = client.get(f"/files/{file_record.id}/detail") + response = client.get(f"/files/{file_record.id}/process") assert response.status_code == 200 content = response.text # Should show metadata diff --git a/tests/test_views_files_comprehensive.py b/tests/test_views_files_comprehensive.py index d522a637..c36c62ee 100644 --- a/tests/test_views_files_comprehensive.py +++ b/tests/test_views_files_comprehensive.py @@ -206,12 +206,12 @@ class TestFileDetailPage: db_session.add(file) db_session.commit() - response = client.get(f"/files/{file.id}/detail") + response = client.get(f"/files/{file.id}/process") assert response.status_code == 200 def test_file_detail_page_not_found(self, client: TestClient, db_session): """Test file detail page for non-existent file.""" - response = client.get("/files/99999/detail") + response = client.get("/files/99999/process") assert response.status_code == 200 # Still renders template with error def test_file_detail_page_with_processing_logs(self, client: TestClient, db_session, tmp_path): @@ -244,7 +244,7 @@ class TestFileDetailPage: db_session.add(log2) db_session.commit() - response = client.get(f"/files/{file.id}/detail") + response = client.get(f"/files/{file.id}/process") assert response.status_code == 200 def test_file_detail_page_with_metadata_json(self, client: TestClient, db_session, tmp_path): @@ -272,7 +272,7 @@ class TestFileDetailPage: db_session.add(file) db_session.commit() - response = client.get(f"/files/{file.id}/detail") + response = client.get(f"/files/{file.id}/process") assert response.status_code == 200 def test_file_detail_checks_original_file_exists(self, client: TestClient, db_session, tmp_path): @@ -289,7 +289,7 @@ class TestFileDetailPage: db_session.add(file) db_session.commit() - response = client.get(f"/files/{file.id}/detail") + response = client.get(f"/files/{file.id}/process") assert response.status_code == 200 def test_file_detail_error_handling(self, client: TestClient, db_session): @@ -1113,7 +1113,7 @@ class TestFileDetailPageAdditional: db_session.add(file) db_session.commit() - response = client.get(f"/files/{file.id}/detail") + response = client.get(f"/files/{file.id}/process") assert response.status_code == 200 def test_file_detail_step_summary_fallback(self, client: TestClient, db_session, tmp_path): @@ -1144,7 +1144,7 @@ class TestFileDetailPageAdditional: db_session.commit() with patch("app.utils.step_manager.get_step_summary", side_effect=Exception("Table not found")): - response = client.get(f"/files/{file.id}/detail") + response = client.get(f"/files/{file.id}/process") assert response.status_code == 200 def test_file_detail_error_handling(self, client: TestClient, db_session): @@ -1155,7 +1155,7 @@ class TestFileDetailPageAdditional: Mock(status_code=200), ] try: - response = client.get("/files/1/detail") + response = client.get("/files/1/process") assert response.status_code in (200, 500) except Exception: pass @@ -1638,7 +1638,7 @@ class TestFileDetailNoJsonSidecar: db_session.add(file) db_session.commit() - response = client.get(f"/files/{file.id}/detail") + response = client.get(f"/files/{file.id}/process") assert response.status_code == 200 @@ -1936,7 +1936,7 @@ class TestPipelineInfoInViews: pipeline = self._make_system_pipeline(db_session) file_rec = self._make_file(db_session, pipeline_id=None) - response = client.get(f"/files/{file_rec.id}/detail") + response = client.get(f"/files/{file_rec.id}/process") assert response.status_code == 200 assert b"Standard Processing Pipeline" in response.content @@ -1946,7 +1946,7 @@ class TestPipelineInfoInViews: self._make_system_pipeline(db_session) file_rec = self._make_file(db_session, pipeline_id=None) - response = client.get(f"/files/{file_rec.id}/detail") + response = client.get(f"/files/{file_rec.id}/process") assert response.status_code == 200 assert b"System Default" in response.content @@ -1956,28 +1956,28 @@ class TestPipelineInfoInViews: pipeline = self._make_custom_pipeline(db_session) file_rec = self._make_file(db_session, pipeline_id=pipeline.id) - response = client.get(f"/files/{file_rec.id}/detail") + response = client.get(f"/files/{file_rec.id}/process") assert response.status_code == 200 assert b"My Custom Pipeline" in response.content assert b"Custom" in response.content def test_file_view_page_includes_pipeline_name(self, client, db_session): - """GET /files/{id} response body contains the pipeline name in the sidebar.""" + """GET /files/{id}/detail response body contains the pipeline name in the sidebar.""" pipeline = self._make_system_pipeline(db_session) file_rec = self._make_file(db_session, pipeline_id=None) - response = client.get(f"/files/{file_rec.id}") + response = client.get(f"/files/{file_rec.id}/detail") assert response.status_code == 200 assert b"Standard Processing Pipeline" in response.content def test_file_view_page_no_pipeline_shows_standard(self, client, db_session): - """When no pipeline exists, file view shows 'Standard' fallback text.""" + """When no pipeline exists, file detail view shows 'Standard' fallback text.""" # No pipeline in DB file_rec = self._make_file(db_session, pipeline_id=None) - response = client.get(f"/files/{file_rec.id}") + response = client.get(f"/files/{file_rec.id}/detail") assert response.status_code == 200 assert b"Standard" in response.content diff --git a/vendor/embed-pdf-viewer b/vendor/embed-pdf-viewer new file mode 160000 index 00000000..aa45d6ef --- /dev/null +++ b/vendor/embed-pdf-viewer @@ -0,0 +1 @@ +Subproject commit aa45d6ef07aa2c6a77e15387d74661e835239408