312 lines
9.6 KiB
Rust
312 lines
9.6 KiB
Rust
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::BTreeMap;
|
|
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: BTreeMap::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);
|
|
}
|
|
}
|