diff --git a/server/templates/case_page.html b/server/templates/case_page.html index d3025dd..b6dc63f 100644 --- a/server/templates/case_page.html +++ b/server/templates/case_page.html @@ -436,40 +436,6 @@ - {% else %} -
- {% call csrf::field(csrf_token) %} - -
{% endif %} {% match recorded_at_iso %} {% when Some with (iso) %} @@ -623,13 +589,27 @@
{{ html|safe }}
+ {% if !is_closed %} + + {% endif %} {% when None %} {% match analysis_failed %} {% when Some with (failure) %} @@ -743,7 +747,16 @@ {{ recordings_count }} Aufnahme{% if recordings_count != 1 %}n{% endif %}, davon {{ transcribed_count }} transkribiert. - {% endif %} {% endmatch %} {% endmatch %} + {% endif %} {% endmatch %} + {% if !is_closed %} + + {% endif %} + {% endmatch %}
Schließen"#), + "documentless open case must offer a bare Schließen button" + ); } #[tokio::test] @@ -843,3 +847,118 @@ async fn case_recordings_has_no_action_buttons() { "recordings page must not expose the close action" ); } + +/// Property: an open case WITH a document renders the combined +/// "Kopieren + Schließen" control below the document — a single form +/// that POSTs the close action and carries the CSRF token. +#[tokio::test] +async fn case_page_open_document_has_copy_close_button() { + let cfg = TestConfig::new() + .with_label("cp-copyclose") + .with_user(test_user("dr_a")) + .with_llm("http://unused") + .build(); + let case_id = "11111111-1111-1111-1111-111111111111"; + let case_dir = seed_case(&cfg.data_path, "dr_a", case_id); + write_document_for_test(&case_dir, "fertiger Brief", "1970-01-01T00:00:00Z"); + + let app = doctate_server::create_router(cfg); + let cookie = login_dr_a(&app).await; + + let resp = app + .oneshot(get_with_cookie(paths::case_detail(case_id), &cookie)) + .await + .unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + let body = body_string(resp).await; + + assert!( + body.contains(r#"id="copy-close-form""#), + "combined copy-close form missing" + ); + assert!( + body.contains("Kopieren + Schließen"), + "copy-close button label missing" + ); + // The combined form must POST the close action... + let form_start = body + .find(r#"id="copy-close-form""#) + .expect("copy-close form present"); + let rest = &body[form_start..]; + let form_end = rest.find("").expect("copy-close form closes"); + let form_html = &rest[..form_end]; + assert!( + form_html.contains(&format!(r#"action="{}""#, paths::case_close(case_id))), + "copy-close form must POST the close action" + ); + // ...and carry the CSRF token inside that same form. + assert!( + form_html.contains(r#"name="csrf_token""#), + "copy-close form must carry the CSRF token" + ); +} + +/// Guard: a CLOSED case (even with a document, rendered read-only) shows +/// no copy-close control — closing an already-closed case is impossible. +/// The reopen affordance is the only state-changing control. +#[tokio::test] +async fn case_page_closed_document_hides_copy_close() { + let cfg = TestConfig::new() + .with_label("cp-closed-copyclose") + .with_user(test_user("dr_a")) + .with_llm("http://unused") + .build(); + let case_id = "11111111-1111-1111-1111-111111111111"; + let case_dir = seed_case(&cfg.data_path, "dr_a", case_id); + write_document_for_test(&case_dir, "fertiger Brief", "1970-01-01T00:00:00Z"); + write_closed_marker(&case_dir, "2026-04-15T12:00:00Z"); + + let app = doctate_server::create_router(cfg); + let cookie = login_dr_a(&app).await; + + let resp = app + .oneshot(get_with_cookie(paths::case_detail(case_id), &cookie)) + .await + .unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + let body = body_string(resp).await; + + assert!( + !body.contains(r#"id="copy-close-form""#), + "closed case must not expose the copy-close control" + ); + assert!( + body.contains(&format!(r#"action="{}""#, paths::case_reopen(case_id))), + "closed case must still offer reopen" + ); +} + +/// Property: the icon-only trash-can close button in the H1 title row is +/// gone — closing now happens via the text control below the document. +/// (The closed-case reopen affordance in the same slot is untouched.) +#[tokio::test] +async fn case_page_open_case_has_no_trashcan_close() { + let cfg = TestConfig::new() + .with_label("cp-no-trash") + .with_user(test_user("dr_a")) + .with_llm("http://unused") + .build(); + let case_id = "11111111-1111-1111-1111-111111111111"; + let case_dir = seed_case(&cfg.data_path, "dr_a", case_id); + write_document_for_test(&case_dir, "fertiger Brief", "1970-01-01T00:00:00Z"); + + let app = doctate_server::create_router(cfg); + let cookie = login_dr_a(&app).await; + + let resp = app + .oneshot(get_with_cookie(paths::case_detail(case_id), &cookie)) + .await + .unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + let body = body_string(resp).await; + + assert!( + !body.contains(r#"title="Fall schließen""#), + "the H1 trash-can close button must be gone" + ); +}