use crate::error::MetadataError;
use crate::extract_and_prepare_metadata;
use crate::metatags::MetaTagGroups;
use std::collections::HashMap;
use tokio::fs::File;
use tokio::io::AsyncReadExt;
pub fn escape_html(value: &str) -> String {
value
.replace('&', "&")
.replace('<', "<")
.replace('>', ">")
.replace('"', """)
.replace('\'', "'")
}
pub fn unescape_html(value: &str) -> String {
value
.replace("&", "&")
.replace("<", "<")
.replace(">", ">")
.replace(""", "\"")
.replace("'", "'")
.replace("'", "'")
.replace("/", "/")
.replace("/", "/")
}
pub async fn async_extract_metadata_from_file(
file_path: &str,
) -> Result<
(HashMap<String, String>, Vec<String>, MetaTagGroups),
MetadataError,
> {
let mut file = File::open(file_path)
.await
.map_err(MetadataError::IoError)?;
let mut content = String::new();
file.read_to_string(&mut content)
.await
.map_err(MetadataError::IoError)?;
if content.trim().is_empty() {
return Ok((
HashMap::new(),
Vec::new(),
MetaTagGroups {
primary: String::new(),
apple: String::new(),
ms: String::new(),
og: String::new(),
twitter: String::new(),
},
));
}
extract_and_prepare_metadata(&content)
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
use tokio::fs::File;
use tokio::io::AsyncWriteExt;
#[test]
fn test_escape_html() {
let input = "Hello, <world> & \"friends\"!";
let expected =
"Hello, <world> & "friends"!";
assert_eq!(escape_html(input), expected);
}
#[test]
fn test_escape_html_special_characters() {
let input = "It's <b>bold</b> & it's <i>italic</i>";
let expected = "It's <b>bold</b> & it's <i>italic</i>";
assert_eq!(escape_html(input), expected);
}
#[test]
fn test_unescape_html() {
let input = "Hello, <world> & "friends"!";
let expected = "Hello, <world> & \"friends\"!";
assert_eq!(unescape_html(input), expected);
}
#[test]
fn test_unescape_html_edge_cases() {
let input = "<&>"''/";
let expected = "<&>\"''/";
assert_eq!(unescape_html(input), expected);
}
#[test]
fn test_escape_unescape_roundtrip() {
let original = "Test <script>alert('XSS');</script> & other \"special\" chars";
let escaped = escape_html(original);
let unescaped = unescape_html(&escaped);
assert_eq!(original, unescaped);
}
#[tokio::test]
async fn test_async_extract_metadata_from_file() {
let temp_dir = tempdir().unwrap();
let file_path = temp_dir.path().join("test.md");
let content = r#"---
title: Test Page
description: A test page for metadata extraction
keywords: test, metadata, extraction
---
# Test Content
This is a test file for metadata extraction."#;
let mut file = File::create(&file_path).await.unwrap();
file.write_all(content.as_bytes()).await.unwrap();
let result = async_extract_metadata_from_file(
file_path.to_str().unwrap(),
)
.await;
assert!(result.is_ok());
let (metadata, keywords, meta_tags) = result.unwrap();
assert_eq!(
metadata.get("title"),
Some(&"Test Page".to_string())
);
assert_eq!(
metadata.get("description"),
Some(&"A test page for metadata extraction".to_string())
);
assert_eq!(keywords, vec!["test", "metadata", "extraction"]);
assert!(!meta_tags.primary.is_empty());
}
#[tokio::test]
async fn test_async_extract_metadata_from_empty_file() {
let temp_dir = tempdir().unwrap();
let file_path = temp_dir.path().join("empty.md");
let mut file = File::create(&file_path).await.unwrap();
file.write_all(b"").await.unwrap();
let result = async_extract_metadata_from_file(
file_path.to_str().unwrap(),
)
.await;
assert!(result.is_ok());
let (metadata, keywords, meta_tags) = result.unwrap();
assert!(metadata.is_empty());
assert!(keywords.is_empty());
assert!(meta_tags.primary.is_empty());
}
#[tokio::test]
async fn test_async_extract_metadata_from_nonexistent_file() {
let result =
async_extract_metadata_from_file("nonexistent_file.md")
.await;
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
MetadataError::IoError(_)
));
}
}