obsidian-export/tests/export_test.rs
Davis Davalos-DeLosh 2a7a489c23
Optionally preserve modified time of exported files
Add a new argument --preserve-mtime to keep the original modified time
attribute of notes being exported, instead of setting them to the
current time.
2024-08-04 13:57:27 +02:00

462 lines
14 KiB
Rust

#![allow(clippy::shadow_unrelated)]
use std::fs::{create_dir, read_to_string, set_permissions, File, Permissions};
use std::io::prelude::*;
#[cfg(not(target_os = "windows"))]
use std::os::unix::fs::PermissionsExt;
use std::path::PathBuf;
use obsidian_export::{ExportError, Exporter, FrontmatterStrategy};
use pretty_assertions::assert_eq;
use tempfile::TempDir;
use walkdir::WalkDir;
#[test]
fn test_main_variants_with_default_options() {
let tmp_dir = TempDir::new().expect("failed to make tempdir");
Exporter::new(
PathBuf::from("tests/testdata/input/main-samples/"),
tmp_dir.path().to_path_buf(),
)
.run()
.expect("exporter returned error");
let walker = WalkDir::new("tests/testdata/expected/main-samples/")
// Without sorting here, different test runs may trigger the first assertion failure in
// unpredictable order.
.sort_by(|a, b| a.file_name().cmp(b.file_name()))
.into_iter();
for entry in walker {
let entry = entry.unwrap();
if entry.metadata().unwrap().is_dir() {
continue;
};
let filename = entry.file_name().to_string_lossy().into_owned();
let expected = read_to_string(entry.path()).unwrap_or_else(|_| {
panic!(
"failed to read {} from testdata/expected/main-samples/",
entry.path().display()
)
});
let actual = read_to_string(tmp_dir.path().join(PathBuf::from(&filename)))
.unwrap_or_else(|_| panic!("failed to read {} from temporary exportdir", filename));
assert_eq!(
expected, actual,
"{} does not have expected content",
filename
);
}
}
#[test]
fn test_frontmatter_never() {
let tmp_dir = TempDir::new().expect("failed to make tempdir");
let mut exporter = Exporter::new(
PathBuf::from("tests/testdata/input/main-samples/"),
tmp_dir.path().to_path_buf(),
);
exporter.frontmatter_strategy(FrontmatterStrategy::Never);
exporter.run().expect("exporter returned error");
let expected = "Note with frontmatter.\n";
let actual = read_to_string(
tmp_dir
.path()
.join(PathBuf::from("note-with-frontmatter.md")),
)
.unwrap();
assert_eq!(expected, actual);
}
#[test]
fn test_frontmatter_always() {
let tmp_dir = TempDir::new().expect("failed to make tempdir");
let mut exporter = Exporter::new(
PathBuf::from("tests/testdata/input/main-samples/"),
tmp_dir.path().to_path_buf(),
);
exporter.frontmatter_strategy(FrontmatterStrategy::Always);
exporter.run().expect("exporter returned error");
// Note without frontmatter should have empty frontmatter added.
let expected = "---\n---\n\nNote without frontmatter.\n";
let actual = read_to_string(
tmp_dir
.path()
.join(PathBuf::from("note-without-frontmatter.md")),
)
.unwrap();
assert_eq!(expected, actual);
// Note with frontmatter should remain untouched.
let expected = "---\nFoo: bar\n---\n\nNote with frontmatter.\n";
let actual = read_to_string(
tmp_dir
.path()
.join(PathBuf::from("note-with-frontmatter.md")),
)
.unwrap();
assert_eq!(expected, actual);
}
#[test]
fn test_exclude() {
let tmp_dir = TempDir::new().expect("failed to make tempdir");
Exporter::new(
PathBuf::from("tests/testdata/input/main-samples/"),
tmp_dir.path().to_path_buf(),
)
.run()
.expect("exporter returned error");
let excluded_note = tmp_dir.path().join(PathBuf::from("excluded-note.md"));
assert!(
!excluded_note.exists(),
"exluded-note.md was found in tmpdir, but should be absent due to .export-ignore rules"
);
}
#[test]
fn test_single_file_to_dir() {
let tmp_dir = TempDir::new().expect("failed to make tempdir");
Exporter::new(
PathBuf::from("tests/testdata/input/single-file/note.md"),
tmp_dir.path().to_path_buf(),
)
.run()
.unwrap();
assert_eq!(
read_to_string("tests/testdata/expected/single-file/note.md").unwrap(),
read_to_string(tmp_dir.path().join(PathBuf::from("note.md"))).unwrap(),
);
}
#[test]
fn test_single_file_to_file() {
let tmp_dir = TempDir::new().expect("failed to make tempdir");
let dest = tmp_dir.path().join(PathBuf::from("export.md"));
Exporter::new(
PathBuf::from("tests/testdata/input/single-file/note.md"),
dest.clone(),
)
.run()
.unwrap();
assert_eq!(
read_to_string("tests/testdata/expected/single-file/note.md").unwrap(),
read_to_string(&dest).unwrap(),
);
}
#[test]
fn test_start_at_subdir() {
let tmp_dir = TempDir::new().expect("failed to make tempdir");
let mut exporter = Exporter::new(
PathBuf::from("tests/testdata/input/start-at/"),
tmp_dir.path().to_path_buf(),
);
exporter.start_at(PathBuf::from("tests/testdata/input/start-at/subdir"));
exporter.run().unwrap();
let expected = if cfg!(windows) {
read_to_string("tests/testdata/expected/start-at/subdir/Note B.md")
.unwrap()
.replace('/', "\\")
} else {
read_to_string("tests/testdata/expected/start-at/subdir/Note B.md").unwrap()
};
assert_eq!(
expected,
read_to_string(tmp_dir.path().join(PathBuf::from("Note B.md"))).unwrap(),
);
}
#[test]
fn test_start_at_file_within_subdir_destination_is_dir() {
let tmp_dir = TempDir::new().expect("failed to make tempdir");
let mut exporter = Exporter::new(
PathBuf::from("tests/testdata/input/start-at/"),
tmp_dir.path().to_path_buf(),
);
exporter.start_at(PathBuf::from(
"tests/testdata/input/start-at/subdir/Note B.md",
));
exporter.run().unwrap();
let expected = if cfg!(windows) {
read_to_string("tests/testdata/expected/start-at/single-file/Note B.md")
.unwrap()
.replace('/', "\\")
} else {
read_to_string("tests/testdata/expected/start-at/single-file/Note B.md").unwrap()
};
assert_eq!(
expected,
read_to_string(tmp_dir.path().join(PathBuf::from("Note B.md"))).unwrap(),
);
}
#[test]
fn test_start_at_file_within_subdir_destination_is_file() {
let tmp_dir = TempDir::new().expect("failed to make tempdir");
let dest = tmp_dir.path().join(PathBuf::from("note.md"));
let mut exporter = Exporter::new(
PathBuf::from("tests/testdata/input/start-at/"),
dest.clone(),
);
exporter.start_at(PathBuf::from(
"tests/testdata/input/start-at/subdir/Note B.md",
));
exporter.run().unwrap();
let expected = if cfg!(windows) {
read_to_string("tests/testdata/expected/start-at/single-file/Note B.md")
.unwrap()
.replace('/', "\\")
} else {
read_to_string("tests/testdata/expected/start-at/single-file/Note B.md").unwrap()
};
assert_eq!(expected, read_to_string(dest).unwrap(),);
}
#[test]
fn test_not_existing_source() {
let tmp_dir = TempDir::new().expect("failed to make tempdir");
let err = Exporter::new(
PathBuf::from("tests/testdata/no-such-file.md"),
tmp_dir.path().to_path_buf(),
)
.run()
.unwrap_err();
match err {
ExportError::PathDoesNotExist { .. } => {}
_ => panic!("Wrong error variant: {:?}", err),
}
}
#[test]
fn test_not_existing_destination_with_source_dir() {
let tmp_dir = TempDir::new().expect("failed to make tempdir");
let err = Exporter::new(
PathBuf::from("tests/testdata/input/main-samples/"),
tmp_dir.path().to_path_buf().join("does-not-exist"),
)
.run()
.unwrap_err();
match err {
ExportError::PathDoesNotExist { .. } => {}
_ => panic!("Wrong error variant: {:?}", err),
}
}
#[test]
// This test ensures that when source is a file, but destination points to a
// regular file inside of a non-existent directory, an error is raised instead
// of that directory path being created (like `mkdir -p`)
fn test_not_existing_destination_with_source_file() {
let tmp_dir = TempDir::new().expect("failed to make tempdir");
let err = Exporter::new(
PathBuf::from("tests/testdata/input/main-samples/obsidian-wikilinks.md"),
tmp_dir.path().to_path_buf().join("subdir/does-not-exist"),
)
.run()
.unwrap_err();
match err {
ExportError::PathDoesNotExist { .. } => {}
_ => panic!("Wrong error variant: {:?}", err),
}
}
#[cfg(not(target_os = "windows"))]
#[test]
fn test_source_no_permissions() {
let tmp_dir = TempDir::new().expect("failed to make tempdir");
let src = tmp_dir.path().to_path_buf().join("source.md");
let dest = tmp_dir.path().to_path_buf().join("dest.md");
let mut file = File::create(&src).unwrap();
file.write_all(b"Foo").unwrap();
set_permissions(&src, Permissions::from_mode(0o000)).unwrap();
match Exporter::new(src, dest).run().unwrap_err() {
ExportError::FileExportError { source, .. } => match *source {
ExportError::ReadError { .. } => {}
_ => panic!("Wrong error variant for source, got: {:?}", source),
},
err => panic!("Wrong error variant: {:?}", err),
}
}
#[cfg(not(target_os = "windows"))]
#[test]
fn test_dest_no_permissions() {
let tmp_dir = TempDir::new().expect("failed to make tempdir");
let src = tmp_dir.path().to_path_buf().join("source.md");
let dest = tmp_dir.path().to_path_buf().join("dest");
let mut file = File::create(&src).unwrap();
file.write_all(b"Foo").unwrap();
create_dir(&dest).unwrap();
set_permissions(&dest, Permissions::from_mode(0o555)).unwrap();
match Exporter::new(src, dest).run().unwrap_err() {
ExportError::FileExportError { source, .. } => match *source {
ExportError::WriteError { .. } => {}
_ => panic!("Wrong error variant for source, got: {:?}", source),
},
err => panic!("Wrong error variant: {:?}", err),
}
}
#[test]
fn test_infinite_recursion() {
let tmp_dir = TempDir::new().expect("failed to make tempdir");
let err = Exporter::new(
PathBuf::from("tests/testdata/input/infinite-recursion/"),
tmp_dir.path().to_path_buf(),
)
.run()
.unwrap_err();
match err {
ExportError::FileExportError { source, .. } => match *source {
ExportError::RecursionLimitExceeded { .. } => {}
_ => panic!("Wrong error variant for source, got: {:?}", source),
},
err => panic!("Wrong error variant: {:?}", err),
}
}
#[test]
fn test_no_recursive_embeds() {
let tmp_dir = TempDir::new().expect("failed to make tempdir");
let mut exporter = Exporter::new(
PathBuf::from("tests/testdata/input/infinite-recursion/"),
tmp_dir.path().to_path_buf(),
);
exporter.process_embeds_recursively(false);
exporter.run().expect("exporter returned error");
assert_eq!(
read_to_string("tests/testdata/expected/infinite-recursion/Note A.md").unwrap(),
read_to_string(tmp_dir.path().join(PathBuf::from("Note A.md"))).unwrap(),
);
}
#[test]
fn test_preserve_mtime() {
let tmp_dir = TempDir::new().expect("failed to make tempdir");
let mut exporter = Exporter::new(
PathBuf::from("tests/testdata/input/main-samples/"),
tmp_dir.path().to_path_buf(),
);
exporter.preserve_mtime(true);
exporter.run().expect("exporter returned error");
let src = "tests/testdata/input/main-samples/obsidian-wikilinks.md";
let dest = tmp_dir.path().join(PathBuf::from("obsidian-wikilinks.md"));
let src_meta = std::fs::metadata(src).unwrap();
let dest_meta = std::fs::metadata(dest).unwrap();
assert_eq!(src_meta.modified().unwrap(), dest_meta.modified().unwrap());
}
#[test]
fn test_no_preserve_mtime() {
let tmp_dir = TempDir::new().expect("failed to make tempdir");
let mut exporter = Exporter::new(
PathBuf::from("tests/testdata/input/main-samples/"),
tmp_dir.path().to_path_buf(),
);
exporter.preserve_mtime(false);
exporter.run().expect("exporter returned error");
let src = "tests/testdata/input/main-samples/obsidian-wikilinks.md";
let dest = tmp_dir.path().join(PathBuf::from("obsidian-wikilinks.md"));
let src_meta = std::fs::metadata(src).unwrap();
let dest_meta = std::fs::metadata(dest).unwrap();
assert_ne!(src_meta.modified().unwrap(), dest_meta.modified().unwrap());
}
#[test]
fn test_non_ascii_filenames() {
let tmp_dir = TempDir::new().expect("failed to make tempdir");
Exporter::new(
PathBuf::from("tests/testdata/input/non-ascii/"),
tmp_dir.path().to_path_buf(),
)
.run()
.expect("exporter returned error");
let walker = WalkDir::new("tests/testdata/expected/non-ascii/")
// Without sorting here, different test runs may trigger the first assertion failure in
// unpredictable order.
.sort_by(|a, b| a.file_name().cmp(b.file_name()))
.into_iter();
for entry in walker {
let entry = entry.unwrap();
if entry.metadata().unwrap().is_dir() {
continue;
};
let filename = entry.file_name().to_string_lossy().into_owned();
let expected = read_to_string(entry.path()).unwrap_or_else(|_| {
panic!(
"failed to read {} from testdata/expected/non-ascii/",
entry.path().display()
)
});
let actual = read_to_string(tmp_dir.path().join(PathBuf::from(&filename)))
.unwrap_or_else(|_| panic!("failed to read {} from temporary exportdir", filename));
assert_eq!(
expected, actual,
"{} does not have expected content",
filename
);
}
}
#[test]
fn test_same_filename_different_directories() {
let tmp_dir = TempDir::new().expect("failed to make tempdir");
Exporter::new(
PathBuf::from("tests/testdata/input/same-filename-different-directories"),
tmp_dir.path().to_path_buf(),
)
.run()
.unwrap();
let expected = if cfg!(windows) {
read_to_string("tests/testdata/expected/same-filename-different-directories/Note.md")
.unwrap()
.replace('/', "\\")
} else {
read_to_string("tests/testdata/expected/same-filename-different-directories/Note.md")
.unwrap()
};
let actual = read_to_string(tmp_dir.path().join(PathBuf::from("Note.md"))).unwrap();
assert_eq!(expected, actual);
}