sermon-converter/tests/integration_tests.rs
Benjamin Slingo 18bddf4e6f Major refactoring: Event-driven sermon converter
- 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)
- Shared video_processing crate integration
- Filename compatibility improvements (| → () separator)
- Environment-based configuration
- Comprehensive error handling and logging

Features:
- Intelligent sermon filename parsing (Title - Speaker (Date).mp4)
- Date extraction with full month name support
- Title and speaker metadata extraction
- Environment variable configuration (SERMON_WATCH_PATH, SERMON_OUTPUT_PATH)
- Hardware-accelerated video conversion (Intel QSV)
- NFO file generation for Jellyfin compatibility
- Syncthing temporary file filtering
- AV1 video encoding with configurable settings

Performance improvements:
- No more wasteful 1-2 second polling
- Only processes files when filesystem events occur
- Proper resource management and cleanup
2025-09-06 18:30:06 -04:00

223 lines
8.5 KiB
Rust

use std::env;
use std::fs;
use std::path::PathBuf;
use tempfile::TempDir;
use chrono::{NaiveDate, Datelike};
use sermon_converter::VideoConverter;
#[tokio::test]
async fn test_sermon_converter_creation() {
let temp_output = TempDir::new().unwrap();
// Test that we can create a new converter
let converter = VideoConverter::new(temp_output.path().to_path_buf());
// Test that it was created successfully (converter doesn't expose output_path directly)
// We'll test this indirectly through other methods
assert!(true); // Basic smoke test
}
#[tokio::test]
async fn test_date_extraction_from_sermon_filename() {
let temp_output = TempDir::new().unwrap();
let converter = VideoConverter::new(temp_output.path().to_path_buf());
// Test valid sermon filename format
let filename = "God's Love and Mercy - Pastor John Smith (December 25th 2024).mp4";
let result = converter.extract_date_from_filename(filename).await;
assert!(result.is_ok());
let date = result.unwrap();
assert_eq!(date.year(), 2024);
assert_eq!(date.month(), 12);
assert_eq!(date.day(), 25);
// Test different date formats
let test_cases = vec![
("Title - Speaker (January 1st 2024).mp4", (2024, 1, 1)),
("Title - Speaker (February 29th 2024).mp4", (2024, 2, 29)), // Leap year
("Title - Speaker (March 3rd 2023).mp4", (2023, 3, 3)),
("Title - Speaker (April 22nd 2025).mp4", (2025, 4, 22)),
("Title - Speaker (May 11th 2024).mp4", (2024, 5, 11)),
("Title - Speaker (June 30th 2024).mp4", (2024, 6, 30)),
("Title - Speaker (July 4th 2024).mp4", (2024, 7, 4)),
("Title - Speaker (August 15th 2024).mp4", (2024, 8, 15)),
("Title - Speaker (September 22nd 2024).mp4", (2024, 9, 22)),
("Title - Speaker (October 31st 2024).mp4", (2024, 10, 31)),
("Title - Speaker (November 11th 2024).mp4", (2024, 11, 11)),
];
for (filename, (expected_year, expected_month, expected_day)) in test_cases {
let result = converter.extract_date_from_filename(filename).await;
assert!(result.is_ok(), "Failed to parse filename: {}", filename);
let date = result.unwrap();
assert_eq!(date.year(), expected_year, "Wrong year for: {}", filename);
assert_eq!(date.month(), expected_month, "Wrong month for: {}", filename);
assert_eq!(date.day(), expected_day, "Wrong day for: {}", filename);
}
}
#[tokio::test]
async fn test_invalid_sermon_filenames() {
let temp_output = TempDir::new().unwrap();
let converter = VideoConverter::new(temp_output.path().to_path_buf());
let invalid_cases = vec![
"no-date-info.mp4",
"Title - Speaker.mp4",
"Title - Speaker (Invalid Date).mp4",
"Title - Speaker (February 30th 2024).mp4", // Invalid date
"Title - Speaker (13th Month 2024).mp4",
"No separator here 2024.mp4",
];
for filename in invalid_cases {
let result = converter.extract_date_from_filename(filename).await;
assert!(result.is_err(), "Should fail to parse invalid filename: {}", filename);
}
}
#[tokio::test]
async fn test_title_and_speaker_extraction() {
let temp_output = TempDir::new().unwrap();
let converter = VideoConverter::new(temp_output.path().to_path_buf());
let test_cases = vec![
(
"The Power of Prayer - Dr. Sarah Johnson (December 25th 2024).mp4",
("The Power of Prayer", "Dr. Sarah Johnson")
),
(
"Faith in Difficult Times - Pastor Michael Brown (January 1st 2024).mp4",
("Faith in Difficult Times", "Pastor Michael Brown")
),
(
"God's Amazing Grace - Elder Mary Wilson (March 15th 2024).mp4",
("God's Amazing Grace", "Elder Mary Wilson")
),
];
for (filename, (expected_title, expected_speaker)) in test_cases {
let result = converter.extract_title_and_speaker(filename).await;
assert!(result.is_ok(), "Failed to extract title and speaker from: {}", filename);
let (title, speaker) = result.unwrap();
assert_eq!(title, expected_title);
assert_eq!(speaker, expected_speaker);
}
}
#[tokio::test]
async fn test_invalid_title_speaker_formats() {
let temp_output = TempDir::new().unwrap();
let converter = VideoConverter::new(temp_output.path().to_path_buf());
let invalid_cases = vec![
"No dash separators here (December 25th 2024).mp4", // No dash separator
];
for filename in invalid_cases {
let result = converter.extract_title_and_speaker(filename).await;
assert!(result.is_err(), "Should fail to parse invalid format: {}", filename);
}
}
#[tokio::test]
async fn test_environment_variable_integration() {
// Test with custom environment variables
env::set_var("SHOW_TITLE", "Custom Sermon Series");
env::set_var("CREATE_NFO_FILES", "true");
env::set_var("PRESERVE_ORIGINAL_FILES", "false");
env::set_var("AUDIO_CODEC", "libopus");
env::set_var("AUDIO_BITRATE", "128k");
let temp_output = TempDir::new().unwrap();
let converter = VideoConverter::new(temp_output.path().to_path_buf());
// Test that the converter was created with custom configuration
// (We test this indirectly since the config is internal)
assert!(true); // Smoke test that creation succeeded
// Clean up
env::remove_var("SHOW_TITLE");
env::remove_var("CREATE_NFO_FILES");
env::remove_var("PRESERVE_ORIGINAL_FILES");
env::remove_var("AUDIO_CODEC");
env::remove_var("AUDIO_BITRATE");
}
#[tokio::test]
async fn test_directory_structure_creation() {
let temp_output = TempDir::new().unwrap();
let converter = VideoConverter::new(temp_output.path().to_path_buf());
let test_cases = vec![
("Title - Speaker (January 15th 2024).mp4", "2024", "01-January"),
("Title - Speaker (December 31st 2024).mp4", "2024", "12-December"),
("Title - Speaker (July 4th 2023).mp4", "2023", "07-July"),
];
for (filename, expected_year, expected_month) in test_cases {
let date = converter.extract_date_from_filename(filename).await.unwrap();
// The directory structure should match what the converter expects
let year_dir = temp_output.path().join(expected_year);
let month_dir = year_dir.join(expected_month);
// Create the directory structure as the converter would
fs::create_dir_all(&month_dir).unwrap();
// Verify structure exists
assert!(year_dir.exists());
assert!(month_dir.exists());
// Test that output file would go in the right place
let output_file = month_dir.join(filename);
assert!(!output_file.exists()); // Shouldn't exist initially
}
}
#[tokio::test]
async fn test_syncthing_temp_file_rejection() {
let temp_output = TempDir::new().unwrap();
let temp_input = TempDir::new().unwrap();
let converter = VideoConverter::new(temp_output.path().to_path_buf());
// Create a syncthing temp file
let syncthing_file = temp_input.path().join("test.syncthing.tmp.mp4");
fs::write(&syncthing_file, "temporary content").unwrap();
// Process file should reject syncthing files
let result = converter.process_file(syncthing_file).await;
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Syncthing"));
}
#[tokio::test]
async fn test_non_mp4_file_rejection() {
let temp_output = TempDir::new().unwrap();
let temp_input = TempDir::new().unwrap();
let converter = VideoConverter::new(temp_output.path().to_path_buf());
// Create a non-MP4 file
let avi_file = temp_input.path().join("Title - Speaker (December 25th 2024).avi");
fs::write(&avi_file, "avi content").unwrap();
// Process file should reject non-MP4 files
let result = converter.process_file(avi_file).await;
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("non-MP4"));
}
#[tokio::test]
async fn test_missing_file_handling() {
let temp_output = TempDir::new().unwrap();
let converter = VideoConverter::new(temp_output.path().to_path_buf());
// Try to process a file that doesn't exist
let missing_file = PathBuf::from("/nonexistent/path/Title - Speaker (December 25th 2024).mp4");
let result = converter.process_file(missing_file).await;
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("no longer exists"));
}