livestream-archiver/tests/integration_tests.rs
Benjamin Slingo 8c84b69028 Major refactoring: Event-driven livestream archiver
- Complete removal of polling loops for better performance
- Event-driven file processing with stability tracking
- Proper structured logging with tracing
- Graceful shutdown handling (SIGTERM/SIGINT)
- Disk space monitoring (5GB minimum threshold)
- Production-ready paths for RTSDA deployment
- Shared video_processing crate integration
- Filename compatibility improvements (| → () separator)
- Environment-based configuration
- Comprehensive error handling and logging

Performance improvements:
- No more wasteful 1-2 second polling
- Only processes files when filesystem events occur
- Proper resource management and cleanup

Production features:
- Default paths: /home/rockvilleav/.rtsda/livestreams → /media/archive/jellyfin/livestreams
- Configurable via INPUT_DIR and OUTPUT_DIR environment variables
- Structured logging configurable via RUST_LOG
- Hardware-accelerated video conversion (Intel QSV)
- NFO file generation for Jellyfin compatibility
2025-09-06 18:27:59 -04:00

172 lines
6.3 KiB
Rust

use std::env;
use std::fs;
use std::path::PathBuf;
use tempfile::TempDir;
use chrono::{NaiveDateTime, Datelike, Timelike};
use livestream_archiver::LivestreamArchiver;
#[tokio::test]
async fn test_livestream_archiver_creation() {
let temp_output = TempDir::new().unwrap();
// Test that we can create a new archiver
let archiver = LivestreamArchiver::new(temp_output.path().to_path_buf());
assert_eq!(archiver.get_output_path(), temp_output.path());
}
#[tokio::test]
async fn test_date_extraction_from_filename() {
let temp_output = TempDir::new().unwrap();
let archiver = LivestreamArchiver::new(temp_output.path().to_path_buf());
// Test valid filename
let result = archiver.extract_date_from_filename("2024-12-25_14-30-45.mp4").await;
assert!(result.is_ok());
let date = result.unwrap();
assert_eq!(date.year(), 2024);
assert_eq!(date.month(), 12);
assert_eq!(date.day(), 25);
assert_eq!(date.hour(), 14);
assert_eq!(date.minute(), 30);
assert_eq!(date.second(), 45);
// Test invalid filename
let result = archiver.extract_date_from_filename("invalid-filename.mp4").await;
assert!(result.is_err());
// Test non-mp4 file
let result = archiver.extract_date_from_filename("2024-12-25_14-30-45.avi").await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_file_already_processed_detection() {
let temp_output = TempDir::new().unwrap();
// Set up environment variables for program names
env::set_var("DIVINE_WORSHIP_NAME", "Test Divine Worship");
env::set_var("AFTERNOON_PROGRAM_NAME", "Test Afternoon Program");
let archiver = LivestreamArchiver::new(temp_output.path().to_path_buf());
let test_date = NaiveDateTime::parse_from_str("2024-12-25_14-30-45", "%Y-%m-%d_%H-%M-%S").unwrap();
// Initially no files should exist
assert!(!archiver.check_if_file_already_processed("2024-12-25_14-30-45.mp4", &test_date));
// Create the expected output directory structure
let year_dir = temp_output.path().join("2024");
let month_dir = year_dir.join("12-December");
fs::create_dir_all(&month_dir).unwrap();
// Create a divine worship file
let divine_worship_file = month_dir.join("Test Divine Worship (December 25 2024).mp4");
fs::write(&divine_worship_file, "test content").unwrap();
// Now it should detect the file as processed
assert!(archiver.check_if_file_already_processed("2024-12-25_14-30-45.mp4", &test_date));
// Clean up environment variables
env::remove_var("DIVINE_WORSHIP_NAME");
env::remove_var("AFTERNOON_PROGRAM_NAME");
}
#[tokio::test]
async fn test_environment_variable_configuration() {
// Test with custom environment variables
env::set_var("DIVINE_WORSHIP_NAME", "Custom Divine Service");
env::set_var("AFTERNOON_PROGRAM_NAME", "Custom Afternoon Show");
env::set_var("SHOW_TITLE", "Custom Livestreams");
let temp_output = TempDir::new().unwrap();
let archiver = LivestreamArchiver::new(temp_output.path().to_path_buf());
// These are internal fields, so we test them indirectly through the file processing logic
let test_date = NaiveDateTime::parse_from_str("2024-12-25_14-30-45", "%Y-%m-%d_%H-%M-%S").unwrap();
// The archiver should use the custom names when checking for existing files
assert!(!archiver.check_if_file_already_processed("test.mp4", &test_date));
// Create expected directory structure
let year_dir = temp_output.path().join("2024");
let month_dir = year_dir.join("12-December");
fs::create_dir_all(&month_dir).unwrap();
// Create file with custom name
let custom_file = month_dir.join("Custom Divine Service (December 25 2024).mp4");
fs::write(&custom_file, "test content").unwrap();
// Should now detect as processed
assert!(archiver.check_if_file_already_processed("test.mp4", &test_date));
// Clean up
env::remove_var("DIVINE_WORSHIP_NAME");
env::remove_var("AFTERNOON_PROGRAM_NAME");
env::remove_var("SHOW_TITLE");
}
#[tokio::test]
async fn test_stability_tracking_integration() {
let temp_output = TempDir::new().unwrap();
let archiver = LivestreamArchiver::new(temp_output.path().to_path_buf());
let temp_input = TempDir::new().unwrap();
let test_file = temp_input.path().join("2024-12-25_14-30-45.mp4");
// Create initial file
fs::write(&test_file, "initial content").unwrap();
// Test that stability tracking starts
let result = archiver.on_file_event(&test_file);
assert!(result.is_ok());
assert!(!result.unwrap(), "New file should not be immediately stable");
// File should be tracked
let tracked_files = archiver.get_tracked_files();
assert_eq!(tracked_files.len(), 1);
assert_eq!(tracked_files[0], test_file);
// Remove tracking
archiver.remove_file_tracker(&test_file);
assert_eq!(archiver.get_tracked_files().len(), 0);
}
#[tokio::test]
async fn test_directory_structure_creation() {
let temp_output = TempDir::new().unwrap();
// Test date parsing and directory structure
let test_cases = vec![
("2024-01-15_10-30-00.mp4", "2024", "01-January"),
("2024-12-31_23-59-59.mp4", "2024", "12-December"),
("2023-07-04_12-00-00.mp4", "2023", "07-July"),
];
for (filename, expected_year, expected_month) in test_cases {
let archiver = LivestreamArchiver::new(temp_output.path().to_path_buf());
let date = archiver.extract_date_from_filename(filename).await.unwrap();
// The directory structure should match what the archiver expects
let year_dir = temp_output.path().join(expected_year);
let month_dir = year_dir.join(expected_month);
// Create the directory structure as the archiver would
fs::create_dir_all(&month_dir).unwrap();
// Verify structure exists
assert!(year_dir.exists());
assert!(month_dir.exists());
// Test file detection logic
assert!(!archiver.check_if_file_already_processed(filename, &date));
}
}
// Helper function to create mock video files for testing
fn create_mock_mp4_file(path: &PathBuf, size_mb: usize) -> std::io::Result<()> {
let content = vec![0u8; size_mb * 1024 * 1024]; // Create file of specified size in MB
fs::write(path, content)
}