Refactor App struct
This commit is contained in:
parent
f9692b8ddf
commit
ee7d7971be
13 changed files with 1147 additions and 163 deletions
312
src/services/action.rs
Normal file
312
src/services/action.rs
Normal file
|
|
@ -0,0 +1,312 @@
|
|||
use crate::actions::action::{Action, ProcessedMagnets};
|
||||
use crate::actions::bitmagnet::BitmagnetAction;
|
||||
use crate::actions::factory::init_action;
|
||||
use crate::actions::transmission::TransmissionAction;
|
||||
use crate::config::Config;
|
||||
use crate::services::database::DatabaseService;
|
||||
use color_eyre::eyre::Result;
|
||||
use log::{debug, warn};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// Service for managing actions
|
||||
pub struct ActionService {
|
||||
actions: Vec<Box<dyn Action>>,
|
||||
db_service: Rc<RefCell<DatabaseService>>,
|
||||
}
|
||||
|
||||
impl ActionService {
|
||||
/// Create a new ActionService
|
||||
pub fn new(db_service: Rc<RefCell<DatabaseService>>) -> Self {
|
||||
Self {
|
||||
actions: Vec::new(),
|
||||
db_service,
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize actions based on configuration
|
||||
pub fn init_actions(&mut self, config: &Config) -> Result<()> {
|
||||
if let Some(action) = init_action(&config.bitmagnet, BitmagnetAction::new)? {
|
||||
self.actions.push(action);
|
||||
}
|
||||
if let Some(action) = init_action(&config.transmission, TransmissionAction::new)? {
|
||||
self.actions.push(action);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Process magnet links with actions
|
||||
#[allow(clippy::await_holding_refcell_ref)] // Not sure how to address this
|
||||
pub async fn process_actions(&mut self) -> Result<HashMap<String, ProcessedMagnets>> {
|
||||
let mut action_results = HashMap::new();
|
||||
|
||||
for action in &mut self.actions {
|
||||
debug!("Processing magnet links with {}", action.get_name());
|
||||
|
||||
match action
|
||||
.process_unprocessed_magnets(&mut self.db_service.borrow_mut())
|
||||
.await
|
||||
{
|
||||
Ok(processed_magnets) => {
|
||||
let count = processed_magnets.success.len();
|
||||
debug!(
|
||||
"Successfully processed {} magnet links with {}",
|
||||
count,
|
||||
action.get_name()
|
||||
);
|
||||
action_results.insert(action.get_name().to_string(), processed_magnets);
|
||||
}
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"Failed to process magnet links with {}: {}",
|
||||
action.get_name(),
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(action_results)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::actions::action::ProcessedMagnets;
|
||||
use crate::db::Database;
|
||||
use crate::models::Magnet;
|
||||
use async_trait::async_trait;
|
||||
use chrono::NaiveDateTime;
|
||||
use color_eyre::eyre::{eyre, Result};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use tempfile::tempdir;
|
||||
|
||||
struct MockAction {
|
||||
name: &'static str,
|
||||
process_called: AtomicBool,
|
||||
should_fail: bool,
|
||||
success_count: usize,
|
||||
failed_count: usize,
|
||||
}
|
||||
|
||||
impl MockAction {
|
||||
fn new(name: &'static str, should_fail: bool) -> Self {
|
||||
Self {
|
||||
name,
|
||||
process_called: AtomicBool::new(false),
|
||||
should_fail,
|
||||
success_count: 0,
|
||||
failed_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn with_success_count(mut self, count: usize) -> Self {
|
||||
self.success_count = count;
|
||||
self
|
||||
}
|
||||
|
||||
fn with_failed_count(mut self, count: usize) -> Self {
|
||||
self.failed_count = count;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Action for MockAction {
|
||||
fn name() -> &'static str
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
"MockAction"
|
||||
}
|
||||
|
||||
fn get_name(&self) -> &'static str {
|
||||
self.name
|
||||
}
|
||||
|
||||
async fn process_unprocessed_magnets(
|
||||
&mut self,
|
||||
_db_service: &mut DatabaseService,
|
||||
) -> Result<ProcessedMagnets> {
|
||||
self.process_called.store(true, Ordering::SeqCst);
|
||||
|
||||
if self.should_fail {
|
||||
return Err(eyre!("Mock action failed"));
|
||||
}
|
||||
|
||||
let mut success = Vec::new();
|
||||
for i in 0..self.success_count {
|
||||
success.push(create_test_magnet(
|
||||
i as i32,
|
||||
&format!("Success Magnet {}", i),
|
||||
));
|
||||
}
|
||||
|
||||
let mut failed = Vec::new();
|
||||
for i in 0..self.failed_count {
|
||||
failed.push(create_test_magnet(
|
||||
i as i32 + 100,
|
||||
&format!("Failed Magnet {}", i),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(ProcessedMagnets { success, failed })
|
||||
}
|
||||
}
|
||||
|
||||
fn create_test_magnet(id: i32, title: &str) -> Magnet {
|
||||
Magnet {
|
||||
id,
|
||||
title: title.to_string(),
|
||||
submitter: "test_user".to_string(),
|
||||
subreddit: "test_subreddit".to_string(),
|
||||
link: format!("magnet:?xt=urn:btih:{}", id),
|
||||
published_at: NaiveDateTime::default(),
|
||||
imdb_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_test_db() -> Rc<RefCell<DatabaseService>> {
|
||||
let temp_dir = tempdir().unwrap();
|
||||
let db_path = temp_dir.path().join("test.db");
|
||||
|
||||
let db = Database::new(&db_path).unwrap();
|
||||
let db_service = DatabaseService::new(db);
|
||||
Rc::new(RefCell::new(db_service))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_action_service() {
|
||||
let db_service = setup_test_db();
|
||||
|
||||
let action_service = ActionService::new(db_service);
|
||||
|
||||
assert_eq!(action_service.actions.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_init_actions_with_empty_config() {
|
||||
let db_service = setup_test_db();
|
||||
let mut action_service = ActionService::new(db_service);
|
||||
|
||||
// Create an empty config
|
||||
let config = Config {
|
||||
bitmagnet: None,
|
||||
transmission: None,
|
||||
ntfy: None,
|
||||
sources: HashMap::new(),
|
||||
};
|
||||
|
||||
let result = action_service.init_actions(&config);
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(action_service.actions.len(), 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_process_actions_with_no_actions() {
|
||||
let db_service = setup_test_db();
|
||||
let mut action_service = ActionService::new(db_service);
|
||||
|
||||
let result = action_service.process_actions().await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
let action_results = result.unwrap();
|
||||
assert_eq!(action_results.len(), 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_process_actions_with_successful_action() {
|
||||
let db_service = setup_test_db();
|
||||
let mut action_service = ActionService::new(db_service);
|
||||
|
||||
// Create a mock action that succeeds
|
||||
let mock_action = MockAction::new("SuccessAction", false).with_success_count(2);
|
||||
|
||||
action_service.actions.push(Box::new(mock_action));
|
||||
|
||||
let result = action_service.process_actions().await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
let action_results = result.unwrap();
|
||||
assert_eq!(action_results.len(), 1);
|
||||
assert!(action_results.contains_key("SuccessAction"));
|
||||
|
||||
let processed_magnets = &action_results["SuccessAction"];
|
||||
assert_eq!(processed_magnets.success.len(), 2);
|
||||
assert_eq!(processed_magnets.failed.len(), 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_process_actions_with_failing_action() {
|
||||
let db_service = setup_test_db();
|
||||
let mut action_service = ActionService::new(db_service);
|
||||
|
||||
// Create a mock action that fails
|
||||
let mock_action = MockAction::new("FailingAction", true);
|
||||
|
||||
action_service.actions.push(Box::new(mock_action));
|
||||
|
||||
let result = action_service.process_actions().await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
let action_results = result.unwrap();
|
||||
assert_eq!(action_results.len(), 0); // No results for failing action
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_process_actions_with_mixed_results() {
|
||||
let db_service = setup_test_db();
|
||||
let mut action_service = ActionService::new(db_service);
|
||||
|
||||
// Create a mock action with both successful and failed magnets
|
||||
let mock_action = MockAction::new("MixedAction", false)
|
||||
.with_success_count(1)
|
||||
.with_failed_count(1);
|
||||
|
||||
action_service.actions.push(Box::new(mock_action));
|
||||
|
||||
let result = action_service.process_actions().await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
let action_results = result.unwrap();
|
||||
assert_eq!(action_results.len(), 1);
|
||||
assert!(action_results.contains_key("MixedAction"));
|
||||
|
||||
let processed_magnets = &action_results["MixedAction"];
|
||||
assert_eq!(processed_magnets.success.len(), 1);
|
||||
assert_eq!(processed_magnets.failed.len(), 1);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_process_actions_with_multiple_actions() {
|
||||
let db_service = setup_test_db();
|
||||
let mut action_service = ActionService::new(db_service);
|
||||
|
||||
// Create two mock actions
|
||||
let action1 = MockAction::new("Action1", false).with_success_count(1);
|
||||
|
||||
let action2 = MockAction::new("Action2", false).with_success_count(1);
|
||||
|
||||
action_service.actions.push(Box::new(action1));
|
||||
action_service.actions.push(Box::new(action2));
|
||||
|
||||
let result = action_service.process_actions().await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
let action_results = result.unwrap();
|
||||
assert_eq!(action_results.len(), 2);
|
||||
assert!(action_results.contains_key("Action1"));
|
||||
assert!(action_results.contains_key("Action2"));
|
||||
|
||||
let processed_magnets1 = &action_results["Action1"];
|
||||
assert_eq!(processed_magnets1.success.len(), 1);
|
||||
|
||||
let processed_magnets2 = &action_results["Action2"];
|
||||
assert_eq!(processed_magnets2.success.len(), 1);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue