refactor: rename soft-delete marker from .deleted to .closed

Internal rename: DELETE_MARKER -> CLOSE_MARKER, DeleteMarker ->
CloseMarker, is_deleted -> is_closed. The batch UUID is dropped;
undo now groups cases by their exact closed_at timestamp, which
bulk-close writes identically across its selection while per-case
close writes a unique stamp.

Behavior unchanged. No migration (project is pre-production);
legacy .deleted files in tmpdata were removed manually.
This commit is contained in:
2026-04-21 10:36:55 +02:00
parent a8389a89db
commit 9410d6daaa
13 changed files with 131 additions and 145 deletions
+16 -17
View File
@@ -6,7 +6,7 @@ use axum::http::{Request, StatusCode};
use tower::util::ServiceExt;
use doctate_server::config::{Config, User};
use doctate_server::paths::{DELETE_MARKER, DeleteMarker, write_delete_marker};
use doctate_server::paths::{CLOSE_MARKER, CloseMarker, write_close_marker};
fn test_config() -> Arc<Config> {
let data_path = std::env::temp_dir().join(format!(
@@ -247,11 +247,11 @@ async fn upload_second_recording_same_case() {
let _ = std::fs::remove_dir_all(&data_path);
}
/// Scenario A: a new upload to a *soft-deleted* case must silently
/// remove the `.deleted` marker and store the new audio. Reopens the
/// case so it reappears in /api/oneliners without any manual "undo".
/// Scenario A: a new upload to a *closed* case must silently remove
/// the `.closed` marker and store the new audio. Reopens the case so
/// it reappears in /api/oneliners without any manual "undo".
#[tokio::test]
async fn upload_reopens_soft_deleted_case() {
async fn upload_reopens_closed_case() {
let config = test_config();
let data_path = config.data_path.clone();
let app = doctate_server::create_router(config);
@@ -278,18 +278,17 @@ async fn upload_reopens_soft_deleted_case() {
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
// Soft-delete the case by writing the marker directly — simulates
// the user hitting the delete button in the web UI without having
// to drive the /web/cases/{id}/delete handler here. Direct write
// also means the watermark is NOT bumped here; that way the next
// GET /api/oneliners pins down the pre-reopen ETag cleanly.
// Close the case by writing the marker directly — simulates the
// user hitting the close button in the web UI without having to
// drive the /web/cases/{id}/delete handler here. Direct write also
// means the watermark is NOT bumped here; that way the next GET
// /api/oneliners pins down the pre-reopen ETag cleanly.
let case_dir = data_path.join("dr_test").join(case_id);
let marker = DeleteMarker {
batch: uuid::Uuid::new_v4(),
deleted_at: "2026-04-13T11:00:00Z".into(),
let marker = CloseMarker {
closed_at: "2026-04-13T11:00:00Z".into(),
};
write_delete_marker(&case_dir, &marker).await.unwrap();
assert!(case_dir.join(DELETE_MARKER).exists());
write_close_marker(&case_dir, &marker).await.unwrap();
assert!(case_dir.join(CLOSE_MARKER).exists());
// Capture the ETag before the reopen.
let etag_before = {
@@ -335,8 +334,8 @@ async fn upload_reopens_soft_deleted_case() {
// Marker is gone → case is visible again.
assert!(
!case_dir.join(DELETE_MARKER).exists(),
".deleted marker must be removed after re-upload"
!case_dir.join(CLOSE_MARKER).exists(),
".closed marker must be removed after re-upload"
);
// Both audios are on disk — the old one was not touched.
assert!(case_dir.join("2026-04-13T10-30-00Z.m4a").exists());