Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions efile_app/efile/migrations/0039_remove_preview_step.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 5.2.17 on 2026-10-09 21:20

import efile.workflow
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('efile', '0038_existing_case_identity'),
]

operations = [
migrations.AlterField(
model_name='filingdraft',
name='current_step',
field=models.CharField(choices=[('options', 'Options'), ('filing_path', 'Start'), ('upload_documents', 'Upload documents'), ('extraction_review', 'Confirm filing'), ('case_lookup', 'Find your case'), ('case_confirmation', 'Confirm your case'), ('document_checklist', 'Check documents'), ('organize_documents', 'Organize documents'), ('your_information', 'Your information'), ('parties', 'People in this filing'), ('party_details', 'Person details'), ('case_questions', 'Case questions'), ('payment', 'Fees'), ('review', 'Review'), ('confirmation', 'Confirmation')], default=efile.workflow.WorkflowStepKey['OPTIONS'], max_length=64),
),
]
2 changes: 1 addition & 1 deletion efile_app/efile/services/document_previews.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
class DocumentReviewError(ValueError):
"""The displayed documents cannot be approved."""

def __init__(self, message, *, status=200):
def __init__(self, message, *, status=422):
super().__init__(message)
self.status = status

Expand Down
15 changes: 11 additions & 4 deletions efile_app/efile/templates/efile/extraction_review.html
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,16 @@ <h1>{% translate "Tell us about your case" %}</h1>
<a href="{% url 'upload_documents' jurisdiction %}">{% translate "go back and try a different document" %}</a>.
</div>
{% endif %}
{% if lead_document %}
{% include "efile/components/document_preview.html" with document=lead_document %}
{% endif %}
{% if preview_error %}<p class="alert alert-danger" role="alert">{{ preview_error }}</p>{% endif %}
<a href="{% url 'upload_documents' jurisdiction %}{% if return_to %}?return_to={{ return_to }}{% endif %}">{% translate "Change files" %}</a>
{% for document in documents %}
{% include "efile/components/document_preview.html" %}
{% endfor %}
<form method="post" id="extraction-review-form">
{% csrf_token %}
<input type="hidden"
name="preview_fingerprint"
value="{{ preview_fingerprint }}" />
{% include "efile/components/filing_availability_notice.html" %}
<input type="hidden" name="return_to" value="{{ return_to }}" />
{% comment %}
Expand Down Expand Up @@ -518,7 +523,9 @@ <h1>{% translate "Tell us about your case" %}</h1>
<a class="btn btn-outline-secondary"
href="{% url 'upload_documents' jurisdiction %}"><i class="fa-solid fa-arrow-left" aria-hidden="true"></i> {% translate "Back" %}</a>
{% endif %}
<button class="btn btn-primary" type="submit">
<button class="btn btn-primary"
type="submit"
{% if preparation_pending %}disabled{% endif %}>
{% translate "Confirm and continue" %} <i class="fa-solid fa-arrow-right" aria-hidden="true"></i>
</button>
</div>
Expand Down
4 changes: 2 additions & 2 deletions efile_app/efile/templates/efile/upload_documents.html
Original file line number Diff line number Diff line change
Expand Up @@ -218,11 +218,11 @@ <h2 id="uploaded-heading">{% translate "Your documents" %}</h2>
preview it came from, not the start of the filing.
{% endcomment %}
<a class="btn btn-outline-secondary"
href="{% url 'preview_documents' jurisdiction %}?return_to={{ return_to }}"><i class="fa-solid fa-arrow-left" aria-hidden="true"></i> {% translate "Back" %}</a>
href="{% url 'extraction_review' jurisdiction %}?return_to={{ return_to }}"><i class="fa-solid fa-arrow-left" aria-hidden="true"></i> {% translate "Back" %}</a>
{% else %}
<a class="btn btn-outline-secondary" href="{{ workflow_previous_url }}"><i class="fa-solid fa-arrow-left" aria-hidden="true"></i> {% translate "Back" %}</a>
{% endif %}
<a class="btn btn-primary {% if not has_lead_document or extraction_wait_seconds %}disabled{% endif %}" id="continue-to-analysis" data-continue-url="{% url 'preview_documents' jurisdiction %}{% if return_to %}?return_to={{ return_to }}{% endif %}" {% if not has_lead_document or extraction_wait_seconds %}aria-disabled="true" tabindex="-1"{% else %}href="{% url 'preview_documents' jurisdiction %}{% if return_to %}?return_to={{ return_to }}{% endif %}"{% endif %}>{% translate "Preview your PDFs" %} <i class="fa-solid fa-arrow-right" aria-hidden="true"></i></a>
<a class="btn btn-primary {% if not has_lead_document or extraction_wait_seconds %}disabled{% endif %}" id="continue-to-analysis" data-continue-url="{% url 'extraction_review' jurisdiction %}{% if return_to %}?return_to={{ return_to }}{% endif %}" {% if not has_lead_document or extraction_wait_seconds %}aria-disabled="true" tabindex="-1"{% else %}href="{% url 'extraction_review' jurisdiction %}{% if return_to %}?return_to={{ return_to }}{% endif %}"{% endif %}>{% translate "Confirm information" %} <i class="fa-solid fa-arrow-right" aria-hidden="true"></i></a>
</div>
</section>
{% endblock workflow_content %}
Expand Down
6 changes: 4 additions & 2 deletions efile_app/efile/tests/test_detours.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from django.urls import reverse

from efile.models import FilingDocument, FilingParty
from efile.services.document_previews import preview_fingerprint
from efile.services.drafts import read_upload_data, write_upload_data
from efile.services.filing_path import change_filing_path
from efile.tests.test_review_submit_flow import submission_draft as _submission_draft
Expand Down Expand Up @@ -150,8 +151,8 @@ def test_finding_a_case_on_a_handoff_detour_still_asks_to_confirm_it(client, dra
def test_unchecked_files_on_a_detour_return_through_the_preview(client, draft):
draft.documents.update(preparation_reviewed_at=None)
response = client.get(step("extraction_review", draft, "handoff"))
assert path(response.url) == reverse("preview_documents", kwargs=J)
assert "return_to=handoff" in response.url
assert response.status_code == 200
assert response.context["return_to"] == "handoff"


def test_switching_to_an_existing_case_from_review_finds_it_then_returns(client, draft):
Expand All @@ -168,6 +169,7 @@ def test_switching_to_an_existing_case_from_review_finds_it_then_returns(client,
"case_type_code": "contract",
"case_type_name": "Contract",
"reviewed_extraction": "yes",
"preview_fingerprint": preview_fingerprint(list(draft.documents.all())),
"return_to": "review",
},
)
Expand Down
14 changes: 11 additions & 3 deletions efile_app/efile/tests/test_document_checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ def test_legacy_main_documents_recover_through_preparation(client, draft, page,
with patch("efile.views.document_checklist.draft_unavailable_message", return_value=""):
response = getattr(client, method)(url)
assert response.status_code == 302
assert response.url.partition("?")[0] == reverse("preview_documents", kwargs={"jurisdiction": draft.jurisdiction})
assert response.url.partition("?")[0] == reverse("extraction_review", kwargs={"jurisdiction": draft.jurisdiction})
assert f"draft={draft.pk}" in response.url
if return_to:
assert f"return_to={return_to}" in response.url
Expand All @@ -143,15 +143,23 @@ def test_legacy_main_documents_recover_through_preparation(client, draft, page,
storage.s3_client.get_object.side_effect = lambda **_kwargs: {"Body": io.BytesIO(pdf_bytes())}
storage.upload_file.return_value = {"success": True, "key": "prepared/filing.pdf"}
storage.get_public_url.return_value = "https://synthetic.invalid/prepared.pdf"
with patch("efile.views.document_previews.S3UploadHandler", return_value=storage):
with patch("efile.views.extraction_review.S3UploadHandler", return_value=storage):
prepared = client.get(response.url)
assert prepared.status_code == 200
lead.refresh_from_db()
assert lead.preparation == "unchanged"
assert lead.preparation_reviewed_at is None
approved = client.post(
response.url,
{"preview_fingerprint": preview_fingerprint(list(draft.documents.all())), "return_to": return_to},
{
"preview_fingerprint": preview_fingerprint(list(draft.documents.all())),
"return_to": return_to,
"existing_case": draft.existing_case,
"court_code": draft.court_code,
"case_category_code": draft.case_category_code,
"case_type_code": draft.case_type_code,
"reviewed_extraction": "yes",
},
)
assert approved.status_code == 302
lead.refresh_from_db()
Expand Down
2 changes: 1 addition & 1 deletion efile_app/efile/tests/test_document_extractions.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ def test_status_endpoint_reports_when_review_is_ready(client, extraction_draft):
"wait_seconds": 0,
"pages_analyzed": 20,
"total_pages": 30,
"review_url": reverse("preview_documents", kwargs={"jurisdiction": "illinois"}),
"review_url": reverse("extraction_review", kwargs={"jurisdiction": "illinois"}),
}


Expand Down
70 changes: 48 additions & 22 deletions efile_app/efile/tests/test_document_previews.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,25 +40,24 @@ def url(name, draft, **kwargs):
def approve(client, draft, **overrides):
documents = list(draft.documents.all())
return client.post(
url("preview_documents", draft),
url("extraction_review", draft),
{
"preview_fingerprint": preview_fingerprint(documents),
"existing_case": "existing",
**overrides,
},
)


def test_continue_records_preview_without_checkboxes_for_current_documents(client, preview_draft):
doc = preview_draft.documents.get()
redirect = client.get(url("extraction_review", preview_draft))
assert redirect.status_code == 302 and "preview-documents" in redirect.url
with patch("efile.views.document_previews.prepare_document_review"):
page = client.get(url("preview_documents", preview_draft))
with patch("efile.views.extraction_review.prepare_document_review"):
page = client.get(url("extraction_review", preview_draft))
assert b'type="checkbox"' not in page.content
assert b"check every page before you continue" in page.content
assert b"preview_fingerprint" in page.content
doc.refresh_from_db()
assert doc.preparation_reviewed_at is None
assert approve(client, preview_draft, preview_fingerprint="stale").status_code == 200
assert approve(client, preview_draft, preview_fingerprint="stale").status_code == 422
doc.refresh_from_db()
assert doc.preparation_reviewed_at is None
assert approve(client, preview_draft).status_code == 302
Expand Down Expand Up @@ -157,14 +156,17 @@ def test_original_and_approval_survive_supporting_row_rebuilds(preview_draft):


def organized(draft):
draft.existing_case = "existing"
draft.previous_case_id = "verified-case"
draft.save()
draft.documents.update(filing_type_code="complaint", document_type_code="public")


def test_preview_return_destinations_are_restricted(client, preview_draft):
organized(preview_draft)
for unknown in ("https://attacker.example", "payment", "document_checklist"):
response = approve(client, preview_draft, return_to=unknown)
assert response.url.partition("?")[0].endswith("/extraction-review/")
assert response.url.partition("?")[0].endswith("/case-lookup/")
assert "return_to" not in response.url


Expand Down Expand Up @@ -195,7 +197,7 @@ def test_legacy_word_support_is_prepared_before_it_can_be_approved(client, previ

write_upload_data(preview_draft, {"files": {"supporting": [{"s3_key": "legacy.docx", "name": "legacy.docx"}]}})
supporting = preview_draft.documents.get(role="supporting")
assert approve(client, preview_draft).status_code == 200
assert approve(client, preview_draft).status_code == 422
supporting.refresh_from_db()
assert supporting.preparation_reviewed_at is None
handler = MagicMock()
Expand All @@ -207,11 +209,11 @@ def test_legacy_word_support_is_prepared_before_it_can_be_approved(client, previ
{"success": True, "key": "prepared.pdf"},
]
with (
patch("efile.views.document_previews.S3UploadHandler", return_value=handler),
patch("efile.views.extraction_review.S3UploadHandler", return_value=handler),
patch("efile.services.document_preparation.requests.post", return_value=service_response(pdf_bytes())),
patch("efile.services.document_preparation.settings.GOTENBERG_URL", "https://synthetic.invalid"),
):
assert client.get(url("preview_documents", preview_draft)).status_code == 200
assert client.get(url("extraction_review", preview_draft)).status_code == 200
supporting.refresh_from_db()
assert supporting.preparation == "converted"
assert supporting.s3_key == "prepared.pdf"
Expand All @@ -228,7 +230,7 @@ def test_legacy_rows_and_changed_preparation_cannot_bypass_submission(client, pr
doc.original_s3_key = "new-original"
doc.save()
assert preview_fingerprint([doc]) != fingerprint
assert approve(client, preview_draft, preview_fingerprint=fingerprint).status_code == 200
assert approve(client, preview_draft, preview_fingerprint=fingerprint).status_code == 422
doc.preparation = ""
doc.save()
with patch("efile.views.submission.forward_final_filing") as forward:
Expand Down Expand Up @@ -274,14 +276,14 @@ def test_legacy_pdf_is_flattened_and_cannot_be_acknowledged_after_preparation_fa
doc.original_filename = "source.pdf"
doc.save()
with (
patch("efile.views.document_previews.S3UploadHandler", return_value=handler),
patch("efile.views.extraction_review.S3UploadHandler", return_value=handler),
patch("efile.services.document_preparation.settings.GOTENBERG_URL", "https://synthetic.invalid"),
patch(
"efile.services.document_preparation.requests.post",
return_value=service_response(pdf_bytes("Stored answer")),
),
):
assert client.get(url("preview_documents", preview_draft)).status_code == 200
assert client.get(url("extraction_review", preview_draft)).status_code == 200
doc.refresh_from_db()
assert doc.preparation == "flattened"
assert doc.s3_key == "flat.pdf"
Expand All @@ -292,17 +294,17 @@ def test_legacy_pdf_is_flattened_and_cannot_be_acknowledged_after_preparation_fa
doc.preparation_reviewed_at = None
doc.save()
with (
patch("efile.views.document_previews.S3UploadHandler", return_value=handler),
patch("efile.views.extraction_review.S3UploadHandler", return_value=handler),
patch("efile.services.document_preparation.settings.GOTENBERG_URL", "https://synthetic.invalid"),
patch(
"efile.services.document_preparation.requests.post",
return_value=service_response(pdf_bytes("Dropped answer")),
),
):
response = client.get(url("preview_documents", preview_draft))
response = client.get(url("extraction_review", preview_draft))
assert response.status_code == 422
assert b"Some answers are missing" in response.content
assert approve(client, preview_draft).status_code == 200
assert approve(client, preview_draft).status_code == 422
doc.refresh_from_db()
assert doc.preparation_reviewed_at is None

Expand All @@ -319,12 +321,12 @@ def test_accessibility_seed_starts_with_a_prepared_acknowledged_document(tmp_pat


def test_change_files_keeps_where_the_filer_came_from(client, preview_draft):
with patch("efile.views.document_previews.prepare_document_review"):
preview = client.get(url("preview_documents", preview_draft) + "&return_to=review").content.decode()
with patch("efile.views.extraction_review.prepare_document_review"):
preview = client.get(url("extraction_review", preview_draft) + "&return_to=review").content.decode()
assert 'upload-documents/?return_to=review"' in preview
upload = client.get(url("upload_documents", preview_draft) + "&return_to=review").content.decode()
# Both ways off the upload page lead back toward Review, not the start.
assert upload.count('href="/jurisdiction/vermont/preview-documents/?return_to=review"') == 2
assert upload.count('href="/jurisdiction/vermont/extraction-review/?return_to=review"') == 2
unknown = client.get(url("upload_documents", preview_draft) + "&return_to=elsewhere").content.decode()
assert "return_to" not in unknown

Expand All @@ -335,7 +337,7 @@ def test_upload_from_a_detour_continues_to_its_preview(client, preview_draft):
url("upload_documents", preview_draft) + "&return_to=review",
{"documents": SimpleUploadedFile("new.pdf", pdf_bytes())},
)
assert "preview-documents/?return_to=review" in response.json()["redirect_url"]
assert "extraction-review/?return_to=review" in response.json()["redirect_url"]


def test_unchanged_files_return_straight_to_review(client, preview_draft):
Expand All @@ -350,7 +352,7 @@ def test_a_new_file_from_review_stops_only_at_organize(client, preview_draft):
response = approve(client, preview_draft, return_to="review")
assert "/organize-documents/?return_to=review" in response.url
# Without a return target, new files still follow the full flow.
assert "extraction-review" in approve(client, preview_draft).url
assert "case-lookup" in approve(client, preview_draft).url


def test_handoff_detour_returns_to_its_list(client, preview_draft):
Expand All @@ -376,3 +378,27 @@ def test_shared_review_rejects_unsafe_approval(preview_draft, state):
with pytest.raises(DocumentReviewError):
approve_document_review(preview_draft, fingerprint)
assert not preview_draft.documents.filter(preparation_reviewed_at__isnull=False).exists()


def test_legacy_preview_redirects_without_approving(client, preview_draft):
response = client.post(url("preview_documents", preview_draft) + "&return_to=review")
assert response.status_code == 302
assert "extraction-review/" in response.url
assert "return_to=review" in response.url
assert preview_draft.documents.get().preparation_reviewed_at is None


def test_all_documents_are_available_on_confirm(client, preview_draft):
supporting = FilingDocument.objects.create(
draft=preview_draft, role="supporting", name="second.pdf", s3_key="second.pdf", preparation="original"
)
page = client.get(url("extraction_review", preview_draft))
assert page.status_code == 200
assert set(doc.pk for doc in page.context["documents"]) == set(preview_draft.documents.values_list("pk", flat=True))
assert str(supporting.pk).encode() in page.content


def test_a_rejected_form_leaves_the_files_unchecked(client, preview_draft):
response = approve(client, preview_draft, existing_case="")
assert response.status_code == 200
assert preview_draft.documents.get().preparation_reviewed_at is None
14 changes: 1 addition & 13 deletions efile_app/efile/tests/test_end_to_end_new_flow_states.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,19 +230,6 @@ def fake_download(_key, destination):
assert status_resp2.json()["ready"] is True
assert status_resp2.json()["status"] == "complete"

# The uploaded filing copies must be checked before extracted case details.
preview_url = reverse("preview_documents", kwargs={"jurisdiction": jurisdiction})
preview = client.get(preview_url)
assert preview.status_code == 200
approved = client.post(
preview_url,
{
"preview_fingerprint": preview.context["preview_fingerprint"],
"reviewed_document": [str(doc.pk) for doc in draft.documents.all()],
},
)
assert approved.status_code == 302

# 3. Step: extraction-review
review_page = client.get(reverse("extraction_review", kwargs={"jurisdiction": jurisdiction}))
assert review_page.status_code == 200
Expand All @@ -257,6 +244,7 @@ def fake_download(_key, destination):
reverse("extraction_review", kwargs={"jurisdiction": jurisdiction}),
{
"reviewed_extraction": "yes",
"preview_fingerprint": review_page.context["preview_fingerprint"],
"existing_case": ExistingCase.NEW,
"court_code": court_code,
"court_name": court_name,
Expand Down
Loading
Loading