fix(sync): S-01 — excluded paths no longer deleted by --delete

This commit is contained in:
2026-09-19 00:12:14 +02:00
parent fba7f305e3
commit 374150e5b3
3 changed files with 266 additions and 1 deletions
+193
View File
@@ -269,3 +269,196 @@ fn test_sync_delete_and_exclude_flags() {
let _ = fs::remove_dir_all(&temp_root);
}
#[test]
fn test_s01_delete_preserves_excluded_files_and_directories() {
let temp_root =
std::env::temp_dir().join(format!("sanctum_s01_test_{}", rand::random::<u64>()));
let container_path = temp_root.join("test_s01.sanctum");
let source_dir = temp_root.join("source");
fs::create_dir_all(&source_dir).unwrap();
fs::create_dir_all(source_dir.join("excluded_dir")).unwrap();
fs::write(source_dir.join("normal.txt"), b"Normal file").unwrap();
fs::write(source_dir.join("document.pdf"), b"Important PDF").unwrap();
fs::write(
source_dir.join("excluded_dir").join("subfile.txt"),
b"Subfile in excluded dir",
)
.unwrap();
let (db, dek) = create_test_container(&container_path);
// 1. Initialer Push ALLER Dateien (ohne Exclude)
let mut opts = SyncOptions::default();
opts.direction = SyncDirection::Push;
opts.quiet = true;
run_sync(
&db,
0,
&dek,
FORMAT_VERSION,
source_dir.to_str().unwrap(),
"/Data",
&opts,
)
.unwrap();
assert!(db
.resolve_path_in_vault("/Data/document.pdf", 0, &dek)
.unwrap()
.is_some());
assert!(db
.resolve_path_in_vault("/Data/excluded_dir/subfile.txt", 0, &dek)
.unwrap()
.is_some());
// 2. Lokale Kopie von document.pdf und excluded_dir entfernen
fs::remove_file(source_dir.join("document.pdf")).unwrap();
fs::remove_dir_all(source_dir.join("excluded_dir")).unwrap();
// 3. Sync Push mit --delete aber MIT --exclude "*.pdf" und --exclude "excluded_dir*"
opts.delete = true;
opts.delete_excluded = false;
opts.exclude_patterns = vec!["*.pdf".to_string(), "excluded_dir*".to_string()];
let stats = run_sync(
&db,
0,
&dek,
FORMAT_VERSION,
source_dir.to_str().unwrap(),
"/Data",
&opts,
)
.unwrap();
assert_eq!(
stats.files_deleted, 0,
"Ausgeschlossene Dateien/Ordner dürfen NICHT gelöscht werden!"
);
// PDF und excluded_dir Teilbaum müssen im Tresor überlebt haben!
assert!(
db.resolve_path_in_vault("/Data/document.pdf", 0, &dek)
.unwrap()
.is_some(),
"PDF im Tresor muss überleben"
);
assert!(
db.resolve_path_in_vault("/Data/excluded_dir/subfile.txt", 0, &dek)
.unwrap()
.is_some(),
"Teilbaum in excluded_dir muss überleben"
);
// 4. Jetzt Push mit --delete UND --delete-excluded
opts.delete_excluded = true;
let stats_del = run_sync(
&db,
0,
&dek,
FORMAT_VERSION,
source_dir.to_str().unwrap(),
"/Data",
&opts,
)
.unwrap();
assert!(
stats_del.files_deleted >= 2,
"Mit --delete-excluded müssen die verwaisten ausgeschlossenen Dateien gelöscht werden"
);
assert!(
db.resolve_path_in_vault("/Data/document.pdf", 0, &dek)
.unwrap()
.is_none(),
"PDF muss mit --delete-excluded gelöscht sein"
);
assert!(
db.resolve_path_in_vault("/Data/excluded_dir/subfile.txt", 0, &dek)
.unwrap()
.is_none(),
"excluded_dir Inhalt muss gelöscht sein"
);
let _ = fs::remove_dir_all(&temp_root);
}
#[test]
fn test_s01_pull_delete_preserves_local_excluded_and_leak_files() {
let temp_root =
std::env::temp_dir().join(format!("sanctum_s01_pull_{}", rand::random::<u64>()));
let container_path = temp_root.join("test_s01_pull.sanctum");
let restore_dir = temp_root.join("restored");
fs::create_dir_all(&restore_dir).unwrap();
// Lokale Dateien anlegen, die im Vault NICHT existieren: folder.jpg (Leak file) und local_notes.pdf (Ausschluss)
fs::write(
restore_dir.join("folder.jpg"),
b"Album Art or Explorer Cache",
)
.unwrap();
fs::write(
restore_dir.join("local_notes.pdf"),
b"Private Local Notes",
)
.unwrap();
let (db, dek) = create_test_container(&container_path);
// Eine Datei im Vault anlegen
let root_node = db.get_node_by_id_in_vault(1, 0, &dek).unwrap().unwrap();
let _ = db
.create_node_in_vault(0, root_node.id, "vault_file.txt", false, &dek)
.unwrap();
// Pull mit --delete und --exclude "*.pdf"
let mut opts = SyncOptions::default();
opts.direction = SyncDirection::Pull;
opts.delete = true;
opts.delete_excluded = false;
opts.exclude_patterns = vec!["*.pdf".to_string()];
opts.quiet = true;
let stats = run_sync(
&db,
0,
&dek,
FORMAT_VERSION,
"/",
restore_dir.to_str().unwrap(),
&opts,
)
.unwrap();
assert_eq!(
stats.files_deleted, 0,
"folder.jpg und local_notes.pdf dürfen bei Pull mit --delete NICHT gelöscht werden!"
);
assert!(
restore_dir.join("folder.jpg").exists(),
"folder.jpg muss auf dem Host erhalten bleiben"
);
assert!(
restore_dir.join("local_notes.pdf").exists(),
"local_notes.pdf muss auf dem Host erhalten bleiben"
);
// Pull mit --delete UND --delete-excluded
opts.delete_excluded = true;
run_sync(
&db,
0,
&dek,
FORMAT_VERSION,
"/",
restore_dir.to_str().unwrap(),
&opts,
)
.unwrap();
assert!(
!restore_dir.join("local_notes.pdf").exists(),
"local_notes.pdf muss mit --delete-excluded gelöscht werden"
);
let _ = fs::remove_dir_all(&temp_root);
}