#1944: keyword.export refuses library originals and writes durably

Export Keywords wrote with std::fs::write to any path, replacing a library
original or its XMP sidecar. It now goes through check_write_target and
write_file_durable like preset.export and curve.exportPresets.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
GSibille
2026-10-16 13:35:53 +02:00
co-authored by Copilot App
parent f34c7a8a89
commit b9640ef1d5
2 changed files with 26 additions and 1 deletions
+2 -1
View File
@@ -445,7 +445,8 @@ pub fn specs() -> Vec<CommandSpec> {
let mut r = json!({"keywords": file.written.len(), "captureOneRefuses": refuses, "unwritable": file.unwritable});
match str_param(p, "path").map(str::trim).filter(|x| !x.is_empty()) {
Some(path) => {
std::fs::write(path, &file.text).map_err(|e| crate::EngineError::Other(format!("{path}: {e}")))?;
s.check_write_target(path).map_err(|e| bad("keyword.export", e))?;
crate::export::write_file_durable(path, file.text.as_bytes()).map_err(crate::EngineError::Other)?;
r["path"] = json!(path);
}
None => r["text"] = json!(file.text),
+24
View File
@@ -276,6 +276,30 @@ fn curve_preset_export_never_overwrites_originals_or_sidecars() {
preset_export_preserves_originals("curve.exportPresets", json!({"names": ["Linear"]}), "curve-export-guard");
}
/// #1944: Export Keywords went straight to `std::fs::write`, replacing a library original.
#[test]
fn keyword_export_never_overwrites_originals_or_sidecars() {
preset_export_preserves_originals("keyword.export", json!({}), "keyword-export-guard");
}
#[test]
fn keyword_export_preserves_the_previous_file_on_write_failure() {
let dir = temp_dir("keyword-export-failure");
let mut s = Session::with_demo();
let path = dir.join("keywords.txt");
let params = json!({"path": path.to_string_lossy()});
s.execute("keyword.export", &params).unwrap();
let previous = std::fs::read(&path).unwrap();
{
let _fault = lightcraft_catalog::safe_file::fail_writes_after(4);
assert!(s.execute("keyword.export", &params).is_err(), "keyword.export must report the failed write");
}
assert_eq!(std::fs::read(&path).unwrap(), previous, "the prior export is kept");
s.execute("keyword.export", &params).unwrap();
assert_eq!(std::fs::read_dir(&dir).unwrap().count(), 1, "no temporary files remain");
let _ = std::fs::remove_dir_all(dir);
}
#[test]
fn preset_exports_preserve_previous_files_on_write_failure() {
let dir = temp_dir("preset-export-failure");