"gema_texto",
"serde",
"serde_json",
+ "test_utilities",
"time",
]
"unicode-ident",
]
+[[package]]
+name = "test_utilities"
+version = "7.1.0"
+
[[package]]
name = "time"
version = "0.3.37"
serde = { version = "1.0.217", features = ["derive"] }
serde_json = "1.0.134"
+[dev-dependencies]
+test_utilities = { path = "test_utilities" }
+
[profile.release]
strip = true
lto = true
}
impl Metadata {
+ /// Reads `Metadata` from a file or creates a new one.
+ ///
+ /// This method will create new metadata if it can't read the file,
+ /// whether it's because of malformed JSON, UTF-8 or because the file
+ /// is not readable.
pub fn read_or_create(file_path: &PathBuf) -> Metadata {
if let Some(metadata) = Metadata::read_metadata_file(file_path) {
metadata
}
}
+ /// Returns the metadata `created_on` as an RFC-2822 string.
pub fn created_on_utc(&self) -> Option<String> {
let date =
OffsetDateTime::from_unix_timestamp_nanos((self.created_on * 1_000_000).into()).ok()?;
serde_json::from_str(&contents).ok()
}
}
+
+#[cfg(test)]
+mod tests {
+ use std::fs::write;
+
+ use super::*;
+
+ use test_utilities::*;
+
+ #[test]
+ fn test_reads_metadata_if_file_exists() {
+ let test_dir = setup_test_dir();
+ create_test_file(
+ &test_dir.join("metadata.json"),
+ "\
+{
+ \"id\": \"cool\",
+ \"created_on\": 1736105008957
+}",
+ );
+
+ let metadata = Metadata::read_or_create(&test_dir.join("metadata.json"));
+
+ assert_eq!(metadata.id, "cool");
+ assert_eq!(metadata.created_on, 1736105008957);
+ }
+
+ #[test]
+ fn test_creates_metadata_if_file_does_not_exist() {
+ let test_dir = setup_test_dir();
+
+ assert!(!test_dir.join("metadata.json").exists());
+ let metadata = Metadata::read_or_create(&test_dir.join("metadata.json"));
+ assert_eq!(metadata.created_on.to_string(), metadata.id);
+ }
+
+ #[test]
+ fn test_creates_metadata_if_file_is_malformed() {
+ let test_dir = setup_test_dir();
+ write(&test_dir.join("metadata.json"), vec![0xFF, 0xFF]).expect("Failed to write file");
+
+ let metadata = Metadata::read_or_create(&test_dir.join("metadata.json"));
+ assert_eq!(metadata.created_on.to_string(), metadata.id);
+ }
+
+ #[test]
+ fn test_it_returns_created_on_as_rfc_2822_utc() {
+ let metadata = Metadata {
+ id: "cool".to_string(),
+ created_on: 1736035200000,
+ };
+
+ if let Some(created_on_utc) = metadata.created_on_utc() {
+ assert_eq!(created_on_utc, "Sun, 05 Jan 2025 00:00:00 +0000");
+ } else {
+ panic!("Could not generate RFC-2822 string");
+ }
+ }
+}
use std::io::Result;
use std::path::Path;
+/// Recursively copies the contents of a directory.
pub fn recursively_copy(source: &Path, target: &Path) -> Result<()> {
let entries = read_dir(source)?;
for entry in entries {
Ok(())
}
+
+#[cfg(test)]
+mod tests {
+ use std::fs::create_dir_all;
+
+ use super::*;
+
+ use test_utilities::*;
+
+ #[test]
+ fn test_recursively_copies_directories() {
+ let test_dir = setup_test_dir();
+ let source_dir = test_dir.join("source");
+ let target_dir = test_dir.join("target");
+ let nested_dir = source_dir.join("nested");
+ let hidden_dir = source_dir.join(".hidden");
+ create_dir_all(&target_dir).expect("Could not create target test directory");
+ create_dir_all(&nested_dir).expect("Could not create nested test directory");
+ create_dir_all(&hidden_dir).expect("Could not create hidden test directory");
+ create_test_file(&source_dir.join("image.png"), "A beautiful pencil");
+ create_test_file(&nested_dir.join("hello.txt"), "I am here");
+ create_test_file(&nested_dir.join(".hidden"), "I am not here?");
+ create_test_file(&hidden_dir.join("in_plain_sight.mkv"), "Almost");
+ create_test_file(&hidden_dir.join(".hidden"), "I'm double hidden");
+
+ recursively_copy(&source_dir, &target_dir).expect("Copy failed on test directory");
+
+ assert_file_contents(&target_dir.join("image.png"), "A beautiful pencil");
+ assert_file_contents(&target_dir.join("nested/hello.txt"), "I am here");
+ assert_file_contents(&target_dir.join("nested/.hidden"), "I am not here?");
+ assert_file_contents(&target_dir.join(".hidden/in_plain_sight.mkv"), "Almost");
+ assert_file_contents(&target_dir.join(".hidden/.hidden"), "I'm double hidden");
+ }
+
+ #[test]
+ fn test_empty_directory() {
+ let test_dir = setup_test_dir();
+ let source_dir = test_dir.join("source");
+ let target_dir = test_dir.join("target");
+ create_dir_all(&source_dir).expect("Could not create source test directory");
+ create_dir_all(&target_dir).expect("Could not create target test directory");
+
+ recursively_copy(&source_dir, &target_dir).expect("Copy failed on empty test directory");
+
+ assert_eq!(target_dir.read_dir().unwrap().count(), 0);
+ }
+}
--- /dev/null
+[package]
+name = "test_utilities"
+version = "7.1.0"
+edition = "2021"
+license = "AGPL-3.0-or-later"
+description = "Shared test utilities for blog"
+homepage = "https://r.bdr.sh/page.html"
+authors = ["Rubén Beltrán del Río <page@r.bdr.sh>"]
+
+[dependencies]
--- /dev/null
+use std::env::temp_dir;
+use std::fs::{create_dir_all, read_to_string, remove_dir_all, File};
+use std::io::Write;
+use std::path::PathBuf;
+use std::time::{SystemTime, UNIX_EPOCH};
+use std::process::id;
+
+pub fn setup_test_dir() -> PathBuf {
+ let timestamp = SystemTime::now()
+ .duration_since(UNIX_EPOCH)
+ .unwrap()
+ .as_nanos();
+ let process_id = id();
+ let test_dir = temp_dir().join(format!("blog_test_{}_{}", timestamp, process_id));
+ create_dir_all(&test_dir)
+ .expect("Could not create test directory");
+ test_dir
+}
+
+pub fn cleanup_test_dir(test_dir: &PathBuf) {
+ if test_dir.exists() {
+ remove_dir_all(test_dir).unwrap();
+ }
+}
+
+pub fn create_test_file(path: &PathBuf, content: &str) {
+ let mut file = File::create(path).unwrap();
+ file.write_all(content.as_bytes()).unwrap();
+}
+
+pub fn assert_file_contents(path: &PathBuf, expected: &str) {
+ let content = read_to_string(path).unwrap();
+ assert_eq!(content.trim(), expected.trim());
+}