feat: show-closed toggle, closed badge, reopen button, bulk purge
GET /web/cases now honours ?show_closed=1: the toggle link flips the listing between open-only (default) and open+closed (muted styling, grey "geschlossen" badge, countdown "wird in N Tagen entfernt" when auto_delete_days > 0). Each closed row shows a Lucide rotate-ccw reopen button in the same slot where the close button sits on open cases; selection checkboxes are suppressed so bulk-close cannot touch closed cases. GET /web/cases/:id honours ?show_closed=1 too: without the query a closed case still 404s (keeps the default listing clean), with it the detail page renders the reopen form in the header. The case_page template swaps the icon based on is_closed. The purge-closed bulk button from the earlier commit finally gets a UI: it appears below the listing when show_closed=1 is active AND at least one closed case is visible, with a JS confirm() + the server-side confirm=yes guard. Two integration tests (listing + detail) cover the happy path.
This commit is contained in:
@@ -732,6 +732,125 @@ async fn reopen_removes_marker_and_restores_case() {
|
||||
assert_eq!(detail.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn show_closed_query_includes_closed_cases() {
|
||||
let config = config_with_llm(unique_tmp("show-closed"), "http://unused".into());
|
||||
let case_id = "11111111-1111-1111-1111-111111111111";
|
||||
let case_dir = seed_case(&config.data_path, "dr_a", case_id);
|
||||
seed_recording(&case_dir, "10-00-00", Some("a closed one"));
|
||||
|
||||
let app = doctate_server::create_router(config);
|
||||
let cookie = login(app.clone(), "dr_a").await;
|
||||
|
||||
// Close first.
|
||||
let _ = app
|
||||
.clone()
|
||||
.oneshot(close_request(case_id, &cookie))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Default listing must NOT show it.
|
||||
let resp = app
|
||||
.clone()
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.uri("/web/cases")
|
||||
.header(header::COOKIE, &cookie)
|
||||
.body(Body::empty())
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let body = String::from_utf8(
|
||||
axum::body::to_bytes(resp.into_body(), usize::MAX)
|
||||
.await
|
||||
.unwrap()
|
||||
.to_vec(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(
|
||||
!body.contains(case_id),
|
||||
"default /web/cases must hide closed cases"
|
||||
);
|
||||
|
||||
// With show_closed=1 it must appear, with the closed badge.
|
||||
let resp = app
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.uri("/web/cases?show_closed=1")
|
||||
.header(header::COOKIE, &cookie)
|
||||
.body(Body::empty())
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let body = String::from_utf8(
|
||||
axum::body::to_bytes(resp.into_body(), usize::MAX)
|
||||
.await
|
||||
.unwrap()
|
||||
.to_vec(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(
|
||||
body.contains(case_id),
|
||||
"show_closed=1 must list closed cases"
|
||||
);
|
||||
assert!(
|
||||
body.contains("geschlossen"),
|
||||
"closed badge must render in show_closed mode"
|
||||
);
|
||||
assert!(
|
||||
body.contains("reopen"),
|
||||
"reopen button must be rendered for closed cases"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn closed_case_detail_with_show_closed_returns_200() {
|
||||
let config = config_with_llm(unique_tmp("show-detail"), "http://unused".into());
|
||||
let case_id = "11111111-1111-1111-1111-111111111111";
|
||||
let case_dir = seed_case(&config.data_path, "dr_a", case_id);
|
||||
seed_recording(&case_dir, "10-00-00", Some("x"));
|
||||
|
||||
let app = doctate_server::create_router(config);
|
||||
let cookie = login(app.clone(), "dr_a").await;
|
||||
|
||||
let _ = app
|
||||
.clone()
|
||||
.oneshot(close_request(case_id, &cookie))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Without query → 404 (covered by closed_case_returns_404_on_detail).
|
||||
// With ?show_closed=1 → 200 with the reopen form.
|
||||
let resp = app
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.uri(format!("/web/cases/{case_id}?show_closed=1"))
|
||||
.header(header::COOKIE, &cookie)
|
||||
.body(Body::empty())
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
let body = String::from_utf8(
|
||||
axum::body::to_bytes(resp.into_body(), usize::MAX)
|
||||
.await
|
||||
.unwrap()
|
||||
.to_vec(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(
|
||||
body.contains(&format!("/web/cases/{case_id}/reopen")),
|
||||
"detail page of a closed case must offer a reopen form"
|
||||
);
|
||||
assert!(
|
||||
!body.contains(&format!("/web/cases/{case_id}/close\"")),
|
||||
"detail page of a closed case must NOT offer a close form"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn reopen_is_noop_on_open_case() {
|
||||
let config = config_with_llm(unique_tmp("reopen-2"), "http://unused".into());
|
||||
|
||||
Reference in New Issue
Block a user