From 8158fdcab3d436d2722de303f3be64d40104bc56 Mon Sep 17 00:00:00 2001 From: Marc Plano-Lesay Date: Fri, 2 May 2025 09:33:25 +1000 Subject: [PATCH] Decouple report generation from logging --- src/main.rs | 5 +- src/report.rs | 222 +++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 196 insertions(+), 31 deletions(-) diff --git a/src/main.rs b/src/main.rs index cdb306b..624f841 100644 --- a/src/main.rs +++ b/src/main.rs @@ -194,9 +194,8 @@ async fn main() -> Result<()> { } } - // Generate report - if let Err(e) = report::generate_report(&action_results, total_new_links) { - warn!("Failed to generate report: {}", e); + for line in report::generate_report(&action_results, total_new_links).lines() { + info!("{}", line); } Ok(()) diff --git a/src/report.rs b/src/report.rs index 4b520a0..327fe50 100644 --- a/src/report.rs +++ b/src/report.rs @@ -1,7 +1,6 @@ use crate::actions::action::ProcessedMagnets; use color_eyre::eyre::Result; use console::{style, Emoji}; -use log::info; use std::collections::HashMap; static SPARKLES: Emoji = Emoji("✨", ":sparkles:"); @@ -13,10 +12,12 @@ static WARNING: Emoji = Emoji("⚠️", ":warning:"); pub fn generate_report( action_results: &HashMap, total_new_links: usize, -) -> Result<()> { - info!(""); - info!( - "{} {} {}", +) -> String { + let mut report = String::new(); + + report.push_str("\n"); + report.push_str(&format!( + "{} {} {}\n", SPARKLES, style("Report Summary").bold().underlined(), style(format!( @@ -25,8 +26,8 @@ pub fn generate_report( )) .bold() .green(), - ); - info!(""); + )); + report.push_str("\n"); for (action_name, processed_magnets) in action_results { let success_count = processed_magnets.success.len(); @@ -34,59 +35,224 @@ pub fn generate_report( let total_count = success_count + failed_count; // Section header for each action - info!(""); - info!( - "{} {} {}", + report.push_str("\n"); + report.push_str(&format!( + "{} {} {}\n", ROCKET, style(format!("{}", action_name)).bold().underlined().cyan(), style(format!("({} new links)", total_count)).bold() - ); + )); if failed_count == 0 && success_count == 0 { - info!(""); + report.push_str("\n"); continue; } // Success/failure summary if failed_count > 0 { - info!( - " {} Success: {}, {} Failure: {}", + report.push_str(&format!( + " {} Success: {}, {} Failure: {}\n", style("✅").green(), style(success_count).bold().green(), style("❌").red(), style(failed_count).bold().red() - ); + )); } else { - info!( - " {} Success: {}", + report.push_str(&format!( + " {} Success: {}\n", style("✅").green(), style(success_count).bold().green() - ); + )); } - info!(""); + report.push_str("\n"); // List successful magnets if success_count > 0 { - info!( - " {} {}", + report.push_str(&format!( + " {} {}\n", style("✅").green(), style("Added:").bold().green() - ); + )); for magnet in &processed_magnets.success { - info!(" {} {}", LINK, style(&magnet.title).italic()); + report.push_str(&format!( + " {} {}\n", + LINK, + style(&magnet.title).italic() + )); } - info!(""); + report.push_str("\n"); } // List failed magnets if failed_count > 0 { - info!(" {} {}", style("❌").red(), style("Failed:").bold().red()); + report.push_str(&format!( + " {} {}\n", + style("❌").red(), + style("Failed:").bold().red() + )); for magnet in &processed_magnets.failed { - info!(" {} {}", WARNING, style(&magnet.title).italic()); + report.push_str(&format!( + " {} {}\n", + WARNING, + style(&magnet.title).italic() + )); } - info!(""); + report.push_str("\n"); } } - Ok(()) + report +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::models::Magnet; + use chrono::{DateTime, NaiveDateTime}; + + // Helper function to strip ANSI color codes from a string for testing + fn strip_ansi_codes(s: &str) -> String { + // This regex matches ANSI escape codes + let re = regex::Regex::new(r"\x1B\[[0-9;]*[a-zA-Z]").unwrap(); + re.replace_all(s, "").to_string() + } + + fn create_test_magnet(title: &str) -> Magnet { + Magnet { + id: Some(1), + title: title.to_string(), + submitter: "test_user".to_string(), + subreddit: "test_subreddit".to_string(), + link: "magnet:?xt=urn:btih:test".to_string(), + published_at: DateTime::from_timestamp(0, 0).unwrap().naive_utc(), + imdb_id: None, + } + } + + #[test] + fn test_generate_report_empty() { + let action_results = HashMap::new(); + let total_new_links = 0; + + let report = generate_report(&action_results, total_new_links); + let clean_report = strip_ansi_codes(&report); + + assert!(clean_report.contains("Report Summary")); + assert!(clean_report.contains("0 new links added to the database")); + } + + #[test] + fn test_generate_report_only_success() { + let mut action_results = HashMap::new(); + let processed_magnets = ProcessedMagnets { + success: vec![ + create_test_magnet("Test Success 1"), + create_test_magnet("Test Success 2"), + ], + failed: vec![], + }; + + action_results.insert("TestAction".to_string(), processed_magnets); + let total_new_links = 2; + + let report = strip_ansi_codes(&generate_report(&action_results, total_new_links)); + + assert!(report.contains("Report Summary")); + assert!(report.contains("2 new links added to the database")); + assert!(report.contains("TestAction")); + assert!(report.contains("Success: 2")); + assert!(report.contains("Test Success 1")); + assert!(report.contains("Test Success 2")); + assert!(!report.contains("Failure:")); + } + + #[test] + fn test_generate_report_only_failed() { + let mut action_results = HashMap::new(); + let processed_magnets = ProcessedMagnets { + success: vec![], + failed: vec![ + create_test_magnet("Test Failed 1"), + create_test_magnet("Test Failed 2"), + ], + }; + + action_results.insert("TestAction".to_string(), processed_magnets); + let total_new_links = 0; + + let report = strip_ansi_codes(&generate_report(&action_results, total_new_links)); + + assert!(report.contains("Report Summary")); + assert!(report.contains("0 new links added to the database")); + assert!(report.contains("TestAction")); + assert!(report.contains("Success: 0")); + assert!(report.contains("Failure: 2")); + assert!(report.contains("Test Failed 1")); + assert!(report.contains("Test Failed 2")); + } + + #[test] + fn test_generate_report_success_and_failed() { + let mut action_results = HashMap::new(); + let processed_magnets = ProcessedMagnets { + success: vec![create_test_magnet("Test Success 1")], + failed: vec![create_test_magnet("Test Failed 1")], + }; + + action_results.insert("TestAction".to_string(), processed_magnets); + let total_new_links = 1; + + let report = strip_ansi_codes(&generate_report(&action_results, total_new_links)); + + assert!(report.contains("Report Summary")); + assert!(report.contains("1 new links added to the database")); + assert!(report.contains("TestAction")); + assert!(report.contains("Success: 1")); + assert!(report.contains("Failure: 1")); + assert!(report.contains("Test Success 1")); + assert!(report.contains("Test Failed 1")); + } + + #[test] + fn test_generate_report_multiple_actions() { + let mut action_results = HashMap::new(); + + let processed_magnets1 = ProcessedMagnets { + success: vec![ + create_test_magnet("Action1 Success 1"), + create_test_magnet("Action1 Success 2"), + ], + failed: vec![create_test_magnet("Action1 Failed 1")], + }; + + let processed_magnets2 = ProcessedMagnets { + success: vec![create_test_magnet("Action2 Success 1")], + failed: vec![], + }; + + let processed_magnets3 = ProcessedMagnets { + success: vec![], + failed: vec![], + }; + + action_results.insert("Action1".to_string(), processed_magnets1); + action_results.insert("Action2".to_string(), processed_magnets2); + action_results.insert("EmptyAction".to_string(), processed_magnets3); + let total_new_links = 3; + + let report = strip_ansi_codes(&generate_report(&action_results, total_new_links)); + + assert!(report.contains("Report Summary")); + assert!(report.contains("3 new links added to the database")); + + assert!(report.contains("Action1")); + assert!(report.contains("Action1 Success 1")); + assert!(report.contains("Action1 Success 2")); + assert!(report.contains("Action1 Failed 1")); + + assert!(report.contains("Action2")); + assert!(report.contains("Action2 Success 1")); + + assert!(report.contains("EmptyAction")); + } }