use snippy::applier::{Applier, DiffApplier, FullContentApplier, SearchReplaceApplier};
use snippy::extractor::markdown::MarkdownExtractor;
use snippy::extractor::{BlockType, Extractor, ParsedBlock};
use tempfile::tempdir;
use tokio::fs;
use tracing::debug;
#[tokio::test]
async fn test_content_applier_apply_full_content_to_new_file() {
let dir = tempdir().unwrap();
let base_path = dir.path().to_path_buf();
let applier = FullContentApplier::new(&base_path);
let block = ParsedBlock {
filename: "new_file.rs".to_string(),
content: "fn main() { println!(\"Hello, new file!\"); }".to_string(),
block_type: BlockType::FullContent,
};
applier
.apply(&block)
.await
.unwrap_or_else(|e| panic!("Failed to apply content: {:?}", e));
let content = fs::read_to_string(base_path.join("new_file.rs"))
.await
.unwrap_or_else(|e| panic!("Failed to read file: {:?}", e));
assert_eq!(
content, "fn main() { println!(\"Hello, new file!\"); }",
"File content mismatch"
);
debug!("Test passed for ContentApplier apply full content to new file.");
}
#[tokio::test]
async fn test_content_applier_apply_diff_with_error() {
let dir = tempdir().unwrap();
let base_path = dir.path().to_path_buf();
let applier = DiffApplier::new(&base_path);
let initial_content = "fn main() { println!(\"Hello, world!\"); }";
let file_path = base_path.join("test.rs");
fs::write(&file_path, initial_content).await.unwrap();
let block = ParsedBlock {
filename: "test.rs".to_string(),
content: r#"--- test.rs
+++ test.rs
@@ -1 +1 @@
-fn main() { println!("Hello, world!"); }
+fn main() { println!("Hello, Rust!);
"#
.to_string(),
block_type: BlockType::UnifiedDiff,
};
let result = applier.apply(&block).await;
assert!(result.is_err(), "Expected error, got success");
let content = fs::read_to_string(&file_path).await.unwrap();
assert_eq!(content, initial_content, "Content should be unchanged");
debug!("Test passed for ContentApplier apply diff with error.");
}
#[tokio::test]
async fn test_content_applier_apply_valid_diff() {
let dir = tempdir().unwrap();
let base_path = dir.path().to_path_buf();
let applier = DiffApplier::new(&base_path);
let initial_content = "fn main() { println!(\"Hello, world!\"); }\n";
let file_path = base_path.join("test.rs");
fs::write(&file_path, initial_content).await.unwrap();
let written_content = fs::read_to_string(&file_path).await.unwrap();
println!("Initial content written to file: {}", written_content);
let block = ParsedBlock {
filename: "test.rs".to_string(),
content: r#"--- test.rs
+++ test.rs
@@ -1 +1 @@
-fn main() { println!("Hello, world!"); }
+fn main() { println!("Hello, Rust!"); }
"#
.to_string(),
block_type: BlockType::UnifiedDiff,
};
println!("Diff content: {}", block.content);
applier
.apply(&block)
.await
.unwrap_or_else(|e| panic!("Failed to apply diff: {:?}", e));
let content = fs::read_to_string(&file_path)
.await
.unwrap_or_else(|e| panic!("Failed to read file: {:?}", e));
assert_eq!(
content, "fn main() { println!(\"Hello, Rust!\"); }\n",
"File content mismatch"
);
debug!("Test passed for ContentApplier apply valid diff.");
}
#[tokio::test]
async fn test_content_applier_apply_full_content_to_existing_file_with_different_content() {
let dir = tempdir().unwrap();
let base_path = dir.path().to_path_buf();
let applier = FullContentApplier::new(&base_path);
let initial_content = "fn main() { println!(\"Hello, world!\"); }";
let file_path = base_path.join("existing_file.rs");
fs::write(&file_path, initial_content).await.unwrap();
let block = ParsedBlock {
filename: "existing_file.rs".to_string(),
content: "fn main() { println!(\"Hello, updated world!\"); }".to_string(),
block_type: BlockType::FullContent,
};
applier
.apply(&block)
.await
.unwrap_or_else(|e| panic!("Failed to apply content: {:?}", e));
let content = fs::read_to_string(&file_path)
.await
.unwrap_or_else(|e| panic!("Failed to read file: {:?}", e));
assert_eq!(
content, "fn main() { println!(\"Hello, updated world!\"); }",
"File content mismatch"
);
debug!("Test passed for ContentApplier apply full content to existing file with different content.");
}
#[tokio::test]
async fn test_content_applier_apply_full_content_to_existing_file_with_same_content() {
let dir = tempdir().unwrap();
let base_path = dir.path().to_path_buf();
let applier = FullContentApplier::new(&base_path);
let initial_content = "fn main() { println!(\"Hello, world!\"); }";
let file_path = base_path.join("existing_file.rs");
fs::write(&file_path, initial_content).await.unwrap();
let block = ParsedBlock {
filename: "existing_file.rs".to_string(),
content: initial_content.to_string(),
block_type: BlockType::FullContent,
};
applier
.apply(&block)
.await
.unwrap_or_else(|e| panic!("Failed to apply content: {:?}", e));
let content = fs::read_to_string(&file_path)
.await
.unwrap_or_else(|e| panic!("Failed to read file: {:?}", e));
assert_eq!(content, initial_content, "Content should be unchanged");
debug!("Test passed for ContentApplier apply full content to existing file with same content.");
}
#[tokio::test]
async fn test_content_applier_apply_search_replace_block_success() {
let dir = tempdir().unwrap();
let base_path = dir.path().to_path_buf();
let applier = SearchReplaceApplier::new(&base_path);
let initial_content = r#"use std::collections::HashMap;
fn main() { println!("Hello, world!"); }
"#;
let file_path = base_path.join("test_search_replace.rs");
fs::write(&file_path, initial_content).await.unwrap();
let block = ParsedBlock {
filename: "test_search_replace.rs".to_string(),
content: r#"<<<<<<< SEARCH
use std::collections::HashMap;
=======
use std::collections::BTreeMap;
>>>>>>> REPLACE
<<<<<<< SEARCH
fn main() { println!("Hello, world!"); }
=======
fn main() { println!("Hello, Rust!"); }
>>>>>>> REPLACE
"#
.to_string(),
block_type: BlockType::SearchReplaceBlock,
};
applier
.apply(&block)
.await
.unwrap_or_else(|e| panic!("Failed to apply search-replace block: {:?}", e));
let content = fs::read_to_string(&file_path)
.await
.unwrap_or_else(|e| panic!("Failed to read file: {:?}", e));
assert_eq!(
content,
r#"use std::collections::BTreeMap;
fn main() { println!("Hello, Rust!"); }
"#,
"File content mismatch"
);
debug!("Test passed for ContentApplier apply search-replace block successfully.");
}
#[tokio::test]
async fn test_content_applier_apply_search_replace_block_fail() {
let dir = tempdir().unwrap();
let base_path = dir.path().to_path_buf();
let applier = SearchReplaceApplier::new(&base_path);
let initial_content = "fn main() { println!(\"Hello, world!\"); }";
let file_path = base_path.join("test_search_replace_fail.rs");
fs::write(&file_path, initial_content).await.unwrap();
let block = ParsedBlock {
filename: "test_search_replace_fail.rs".to_string(),
content: r#"<<<<<<< SEARCH
non_existent_function();
=======
replacement_function();
>>>>>>> REPLACE
"#
.to_string(),
block_type: BlockType::SearchReplaceBlock,
};
let result = applier.apply(&block).await;
assert!(result.is_err(), "Expected error, got success");
debug!("Test passed for ContentApplier apply search-replace block with failure.");
}
#[tokio::test]
async fn test_search_replace_blocks_in_file() {
let dir = tempdir().unwrap();
let base_path = dir.path().to_path_buf();
let applier = SearchReplaceApplier::new(&base_path);
let initial_content = "fn main() {\n println!(\"Hello, world!\");\n}";
let file_path = base_path.join("test.rs");
fs::write(&file_path, initial_content).await.unwrap();
let content = r#"
```replace
// filename: test.rs
<<<<<<< SEARCH
println!("Hello, world!");
=======
println!("Hello, Rust!");
>>>>>>> REPLACE
```
```replace
// filename: test.rs
<<<<<<< SEARCH
println!("Hello, Rust!");
=======
println!("Hello, new Rust!");
>>>>>>> REPLACE
```
"#;
let extractor = MarkdownExtractor::new();
let blocks = extractor
.extract(content)
.unwrap_or_else(|e| panic!("Failed to extract content: {:?}", e));
for block in blocks {
applier
.apply(&block)
.await
.unwrap_or_else(|e| panic!("Failed to apply content: {:?} for {}", e, block.content));
}
let content = fs::read_to_string(&file_path)
.await
.unwrap_or_else(|e| panic!("Failed to read file: {:?}", e));
assert_eq!(
content,
"fn main() {\n println!(\"Hello, new Rust!\");\n}"
);
debug!("Test passed for replacing blocks in a file.");
}
#[tokio::test]
async fn test_create_new_file_with_empty_search_block() {
let dir = tempdir().unwrap();
let base_path = dir.path().to_path_buf();
let applier = SearchReplaceApplier::new(&base_path);
let content = r#"
```replace
// filename: new_file.rs
<<<<<<< SEARCH
=======
fn main() {
println!("This is a new file created by search-replace block.");
}
>>>>>>> REPLACE
```
"#;
let extractor = MarkdownExtractor::new();
let blocks = extractor
.extract(content)
.unwrap_or_else(|e| panic!("Failed to extract content: {:?}", e));
for block in blocks {
applier
.apply(&block)
.await
.unwrap_or_else(|e| panic!("Failed to apply content: {:?}", e));
}
let file_path = base_path.join("new_file.rs");
let new_file_content = fs::read_to_string(&file_path)
.await
.unwrap_or_else(|e| panic!("Failed to read new file: {:?}", e));
assert_eq!(
new_file_content,
"fn main() {\n println!(\"This is a new file created by search-replace block.\");\n}\n"
);
debug!("Test passed for creating a new file with empty search block.");
}
#[tokio::test]
async fn test_delete_file_with_empty_replace_block() {
let dir = tempdir().unwrap();
let base_path = dir.path().to_path_buf();
let applier = SearchReplaceApplier::new(&base_path);
let initial_content = "fn main() {\n println!(\"This file will be deleted.\");\n}\n";
let file_path = base_path.join("file_to_delete.rs");
fs::write(&file_path, initial_content).await.unwrap();
let content = r#"
```replace
// filename: file_to_delete.rs
<<<<<<< SEARCH
fn main() {
println!("This file will be deleted.");
}
=======
>>>>>>> REPLACE
```
"#;
let extractor = MarkdownExtractor::new();
let blocks = extractor
.extract(content)
.unwrap_or_else(|e| panic!("Failed to extract content: {:?}", e));
for block in blocks {
applier
.apply(&block)
.await
.unwrap_or_else(|e| panic!("Failed to apply content: {:?}", e));
}
assert!(
!file_path.exists(),
"File should be deleted when resulting content is empty."
);
debug!("Test passed for deleting a file when the resulting content is empty.");
}
#[tokio::test]
async fn test_whitespace_file_deletion_with_empty_replace_block() {
let dir = tempdir().unwrap();
let base_path = dir.path().to_path_buf();
let applier = SearchReplaceApplier::new(&base_path);
let initial_content = " \n \n";
let file_path = base_path.join("whitespace_file_to_delete.rs");
fs::write(&file_path, initial_content).await.unwrap();
let content = r#"
```replace
// filename: whitespace_file_to_delete.rs
<<<<<<< SEARCH
=======
>>>>>>> REPLACE
```
"#;
let extractor = MarkdownExtractor::new();
let blocks = extractor
.extract(content)
.unwrap_or_else(|e| panic!("Failed to extract content: {:?}", e));
for block in blocks {
applier
.apply(&block)
.await
.unwrap_or_else(|e| panic!("Failed to apply content: {:?}", e));
}
assert!(
!file_path.exists(),
"File should be deleted when resulting content is an empty file with all whitespaces."
);
debug!("Test passed for deleting a file with all whitespaces when replace block is empty.");
}