generated tests (yes i'm feeling ashamed)
This commit is contained in:
458
src-tauri/tests/simplified_repository_tests.rs
Normal file
458
src-tauri/tests/simplified_repository_tests.rs
Normal file
@@ -0,0 +1,458 @@
|
||||
mod common;
|
||||
|
||||
use chrono::Utc;
|
||||
use common::TestDb;
|
||||
use flalingo_lib::models::{
|
||||
exercise::Exercise,
|
||||
node::Node,
|
||||
path::{Metadata, Path},
|
||||
};
|
||||
use flalingo_lib::repositories::{
|
||||
metadata_repository::MetadataRepository, path_repository::PathRepository,
|
||||
repository_manager::RepositoryManager,
|
||||
};
|
||||
|
||||
// Helper function to create test data
|
||||
fn create_test_metadata(path_id: &str, version: &str) -> Metadata {
|
||||
let now = Utc::now();
|
||||
Metadata {
|
||||
path_id: path_id.to_string(),
|
||||
version: version.to_string(),
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
}
|
||||
}
|
||||
|
||||
fn create_test_exercise(id: u32, node_id: u32) -> Exercise {
|
||||
Exercise {
|
||||
id,
|
||||
ex_type: "vocabulary".to_string(),
|
||||
content: format!(
|
||||
r#"{{"word": "TestWord{}", "translation": "TestTranslation{}", "example": "This is test {}."}}"#,
|
||||
id, id, id
|
||||
),
|
||||
node_id,
|
||||
}
|
||||
}
|
||||
|
||||
fn create_test_node(id: u32, path_id: &str) -> Node {
|
||||
Node {
|
||||
id,
|
||||
title: format!("Test Node {}", id),
|
||||
description: format!("Description for test node {}", id),
|
||||
path_id: path_id.to_string(),
|
||||
exercises: vec![create_test_exercise(id, id)],
|
||||
}
|
||||
}
|
||||
|
||||
fn create_test_path() -> Path {
|
||||
let now = Utc::now();
|
||||
|
||||
let metadata = vec![Metadata {
|
||||
path_id: "test_path_001".to_string(),
|
||||
version: "1.0.0".to_string(),
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
}];
|
||||
|
||||
let exercises = vec![
|
||||
Exercise {
|
||||
id: 1,
|
||||
ex_type: "vocabulary".to_string(),
|
||||
content: r#"{"word": "Hallo", "translation": "Hello", "audio": "/audio/hallo.mp3", "example": "Hallo, wie geht's?"}"#.to_string(),
|
||||
node_id: 1,
|
||||
},
|
||||
Exercise {
|
||||
id: 2,
|
||||
ex_type: "multiple_choice".to_string(),
|
||||
content: r#"{"question": "How do you say 'goodbye' in German?", "options": ["Tschüss", "Hallo", "Bitte", "Danke"], "correct": 0, "explanation": "Tschüss is the informal way to say goodbye."}"#.to_string(),
|
||||
node_id: 1,
|
||||
}
|
||||
];
|
||||
|
||||
let nodes = vec![Node {
|
||||
id: 1,
|
||||
title: "Basic Greetings".to_string(),
|
||||
description: "Learn essential German greetings".to_string(),
|
||||
path_id: "test_path_001".to_string(),
|
||||
exercises,
|
||||
}];
|
||||
|
||||
Path {
|
||||
id: "test_path_001".to_string(),
|
||||
title: "German Basics Test".to_string(),
|
||||
description: "A test path for demonstrating repository functionality".to_string(),
|
||||
metadata,
|
||||
nodes,
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_metadata_repository() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let test_db = TestDb::new().await?;
|
||||
let repo = MetadataRepository::new(&test_db.pool);
|
||||
|
||||
// Create test metadata
|
||||
let metadata = create_test_metadata("metadata_test_path", "1.0.0");
|
||||
|
||||
// Save metadata
|
||||
repo.save_metadata(&metadata).await?;
|
||||
|
||||
// Retrieve metadata
|
||||
let retrieved = repo.get_metadata_by_path_id("metadata_test_path").await?;
|
||||
|
||||
assert_eq!(retrieved.len(), 1);
|
||||
assert_eq!(retrieved[0].path_id, "metadata_test_path");
|
||||
assert_eq!(retrieved[0].version, "1.0.0");
|
||||
|
||||
// Update metadata
|
||||
let mut updated_metadata = metadata.clone();
|
||||
updated_metadata.version = "1.1.0".to_string();
|
||||
updated_metadata.updated_at = Utc::now();
|
||||
|
||||
repo.update_metadata(&updated_metadata).await?;
|
||||
|
||||
let updated_retrieved = repo.get_metadata_by_path_id("metadata_test_path").await?;
|
||||
assert_eq!(updated_retrieved[0].version, "1.1.0");
|
||||
|
||||
// Delete metadata
|
||||
repo.delete_metadata_by_path_id("metadata_test_path")
|
||||
.await?;
|
||||
|
||||
let delete_result = repo.get_metadata_by_path_id("metadata_test_path").await;
|
||||
assert!(delete_result.is_err());
|
||||
|
||||
test_db.cleanup().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_path_repository_crud() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let test_db = TestDb::new().await?;
|
||||
let repo = PathRepository::new(&test_db.pool);
|
||||
|
||||
// Create test path
|
||||
let test_path = create_test_path();
|
||||
|
||||
// Save path
|
||||
let saved_path_id = repo.save_path(test_path.clone()).await?;
|
||||
assert_eq!(saved_path_id, test_path.id);
|
||||
|
||||
// Retrieve path
|
||||
let path_id_int = saved_path_id.parse::<i32>().unwrap();
|
||||
let retrieved_path = repo.get_path_by_id(path_id_int).await?;
|
||||
|
||||
assert_eq!(retrieved_path.id, test_path.id);
|
||||
assert_eq!(retrieved_path.title, test_path.title);
|
||||
assert_eq!(retrieved_path.description, test_path.description);
|
||||
assert_eq!(retrieved_path.metadata.len(), test_path.metadata.len());
|
||||
assert_eq!(retrieved_path.nodes.len(), test_path.nodes.len());
|
||||
|
||||
// Test path exists
|
||||
let exists = repo.path_exists(path_id_int).await?;
|
||||
assert!(exists);
|
||||
|
||||
// Get all paths
|
||||
let all_paths = repo.get_all_paths().await?;
|
||||
assert_eq!(all_paths.len(), 1);
|
||||
|
||||
// Search by title
|
||||
let search_results = repo.get_paths_by_title("German").await?;
|
||||
assert_eq!(search_results.len(), 1);
|
||||
|
||||
// Clone path
|
||||
let cloned_path_id = repo
|
||||
.clone_path(path_id_int, "cloned_test_path", "Cloned Test Path")
|
||||
.await?;
|
||||
let cloned_id_int = cloned_path_id.parse::<i32>().unwrap();
|
||||
let cloned_path = repo.get_path_by_id(cloned_id_int).await?;
|
||||
|
||||
assert_eq!(cloned_path.id, "cloned_test_path");
|
||||
assert_eq!(cloned_path.title, "Cloned Test Path");
|
||||
|
||||
// Update path
|
||||
let mut updated_path = test_path.clone();
|
||||
updated_path.title = "Updated Test Path".to_string();
|
||||
repo.update_path(updated_path).await?;
|
||||
|
||||
let updated_retrieved = repo.get_path_by_id(path_id_int).await?;
|
||||
assert_eq!(updated_retrieved.title, "Updated Test Path");
|
||||
|
||||
// Delete path
|
||||
repo.delete_path(path_id_int).await?;
|
||||
|
||||
let exists_after_delete = repo.path_exists(path_id_int).await?;
|
||||
assert!(!exists_after_delete);
|
||||
|
||||
test_db.cleanup().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_repository_manager() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let test_db = TestDb::new().await?;
|
||||
let repo_manager = RepositoryManager::new(&test_db.pool);
|
||||
|
||||
// Test health check
|
||||
let is_healthy = repo_manager.health_check().await?;
|
||||
assert!(is_healthy);
|
||||
|
||||
// Test initial stats
|
||||
let initial_stats = repo_manager.get_stats().await?;
|
||||
assert_eq!(initial_stats.path_count, 0);
|
||||
assert!(initial_stats.is_empty());
|
||||
|
||||
// Create and save test path
|
||||
let test_path = create_test_path();
|
||||
let path_id = repo_manager.paths().save_path(test_path.clone()).await?;
|
||||
let path_id_int = path_id.parse::<i32>().unwrap();
|
||||
|
||||
// Test updated stats
|
||||
let updated_stats = repo_manager.get_stats().await?;
|
||||
assert_eq!(updated_stats.path_count, 1);
|
||||
assert_eq!(updated_stats.node_count, 1);
|
||||
assert_eq!(updated_stats.exercise_count, 2);
|
||||
assert!(!updated_stats.is_empty());
|
||||
|
||||
// Test path statistics
|
||||
let path_stats = repo_manager.get_path_statistics(path_id_int).await?;
|
||||
assert_eq!(path_stats.node_count, 1);
|
||||
assert_eq!(path_stats.total_exercises, 2);
|
||||
assert_eq!(path_stats.exercise_types.len(), 2);
|
||||
|
||||
// Test search functionality
|
||||
let search_results = repo_manager.search_paths("German").await?;
|
||||
assert_eq!(search_results.len(), 1);
|
||||
assert!(search_results[0].relevance_score > 0);
|
||||
|
||||
// Test validation
|
||||
let validation_issues = repo_manager.validate_path_integrity(path_id_int).await?;
|
||||
assert!(
|
||||
validation_issues.is_empty(),
|
||||
"Valid path should have no issues"
|
||||
);
|
||||
|
||||
// Test JSON export/import
|
||||
let exported_json = repo_manager.export_path_to_json(path_id_int).await?;
|
||||
assert!(exported_json.contains(&test_path.id));
|
||||
assert!(exported_json.contains(&test_path.title));
|
||||
|
||||
// Import as new path
|
||||
let modified_json = exported_json.replace("test_path_001", "imported_path_001");
|
||||
let imported_path_id = repo_manager.import_path_from_json(&modified_json).await?;
|
||||
|
||||
let imported_id_int = imported_path_id.parse::<i32>().unwrap();
|
||||
let imported_path = repo_manager.paths().get_path_by_id(imported_id_int).await?;
|
||||
assert_eq!(imported_path.id, "imported_path_001");
|
||||
|
||||
// Test cloning
|
||||
let cloned_path_id = repo_manager
|
||||
.clone_path_complete(path_id_int, "cloned_manager_test", "Cloned Manager Test")
|
||||
.await?;
|
||||
let cloned_id_int = cloned_path_id.parse::<i32>().unwrap();
|
||||
let cloned_path = repo_manager.paths().get_path_by_id(cloned_id_int).await?;
|
||||
assert_eq!(cloned_path.id, "cloned_manager_test");
|
||||
|
||||
test_db.cleanup().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_transaction_handling() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let test_db = TestDb::new().await?;
|
||||
let repo_manager = RepositoryManager::new(&test_db.pool);
|
||||
|
||||
// Test successful transaction
|
||||
{
|
||||
let mut tx = repo_manager.begin_transaction().await?;
|
||||
|
||||
sqlx::query("INSERT INTO path (id, title, description) VALUES (?, ?, ?)")
|
||||
.bind("tx_test_path")
|
||||
.bind("Transaction Test")
|
||||
.bind("Testing transactions")
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
tx.commit().await.map_err(|e| e.to_string())?;
|
||||
}
|
||||
|
||||
// Verify data was committed
|
||||
let path_count: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM path WHERE id = ?")
|
||||
.bind("tx_test_path")
|
||||
.fetch_one(&test_db.pool)
|
||||
.await?;
|
||||
assert_eq!(path_count.0, 1);
|
||||
|
||||
// Test transaction rollback
|
||||
{
|
||||
let mut tx2 = repo_manager.begin_transaction().await?;
|
||||
|
||||
sqlx::query("INSERT INTO path (id, title, description) VALUES (?, ?, ?)")
|
||||
.bind("rollback_test_path")
|
||||
.bind("Rollback Test")
|
||||
.bind("Testing rollback")
|
||||
.execute(&mut *tx2)
|
||||
.await?;
|
||||
|
||||
// Drop transaction without committing (rollback)
|
||||
drop(tx2);
|
||||
}
|
||||
|
||||
// Verify data was not committed
|
||||
let rollback_count: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM path WHERE id = ?")
|
||||
.bind("rollback_test_path")
|
||||
.fetch_one(&test_db.pool)
|
||||
.await?;
|
||||
assert_eq!(rollback_count.0, 0);
|
||||
|
||||
test_db.cleanup().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_error_handling() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let test_db = TestDb::new().await?;
|
||||
let repo_manager = RepositoryManager::new(&test_db.pool);
|
||||
|
||||
// Test non-existent path retrieval
|
||||
let result = repo_manager.paths().get_path_by_id(999).await;
|
||||
assert!(result.is_err());
|
||||
|
||||
// Test non-existent path deletion
|
||||
let delete_result = repo_manager.paths().delete_path(999).await;
|
||||
assert!(delete_result.is_err());
|
||||
|
||||
// Test invalid JSON import
|
||||
let invalid_json = r#"{"invalid": "structure", "missing": "fields"}"#;
|
||||
let import_result = repo_manager.import_path_from_json(invalid_json).await;
|
||||
assert!(import_result.is_err());
|
||||
|
||||
// Test non-existent path export
|
||||
let export_result = repo_manager.export_path_to_json(999).await;
|
||||
assert!(export_result.is_err());
|
||||
|
||||
// Test non-existent path statistics
|
||||
let stats_result = repo_manager.get_path_statistics(999).await;
|
||||
assert!(stats_result.is_err());
|
||||
|
||||
// Test non-existent path validation
|
||||
let validation_result = repo_manager.validate_path_integrity(999).await;
|
||||
assert!(validation_result.is_err());
|
||||
|
||||
test_db.cleanup().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_concurrent_operations() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let test_db = TestDb::new().await?;
|
||||
|
||||
// Create multiple paths concurrently
|
||||
let mut handles = vec![];
|
||||
|
||||
for i in 0..5 {
|
||||
let pool_clone = test_db.pool.clone();
|
||||
let mut test_path = create_test_path();
|
||||
test_path.id = format!("concurrent_path_{}", i);
|
||||
test_path.title = format!("Concurrent Path {}", i);
|
||||
|
||||
let handle = tokio::spawn(async move {
|
||||
let repo_manager = RepositoryManager::new(&pool_clone);
|
||||
repo_manager.paths().save_path(test_path).await
|
||||
});
|
||||
handles.push(handle);
|
||||
}
|
||||
|
||||
// Wait for all paths to be saved
|
||||
let mut successful_saves = 0;
|
||||
for handle in handles {
|
||||
match handle.await? {
|
||||
Ok(_) => successful_saves += 1,
|
||||
Err(e) => println!("Concurrent save failed: {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(successful_saves, 5);
|
||||
|
||||
// Verify all paths were saved
|
||||
let repo_manager = RepositoryManager::new(&test_db.pool);
|
||||
let all_paths = repo_manager.paths().get_all_paths().await?;
|
||||
assert_eq!(all_paths.len(), 5);
|
||||
|
||||
test_db.cleanup().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_complex_path_operations() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let test_db = TestDb::new().await?;
|
||||
let repo_manager = RepositoryManager::new(&test_db.pool);
|
||||
|
||||
// Create a complex path with multiple nodes and exercises
|
||||
let mut complex_path = Path {
|
||||
id: "complex_test_path".to_string(),
|
||||
title: "Complex Test Path".to_string(),
|
||||
description: "A path with multiple nodes and exercises".to_string(),
|
||||
metadata: vec![create_test_metadata("complex_test_path", "1.0.0")],
|
||||
nodes: vec![],
|
||||
};
|
||||
|
||||
// Add multiple nodes with different exercise types
|
||||
for i in 1..=3 {
|
||||
let mut node = Node {
|
||||
id: i,
|
||||
title: format!("Node {}", i),
|
||||
description: format!("Description for node {}", i),
|
||||
path_id: "complex_test_path".to_string(),
|
||||
exercises: vec![],
|
||||
};
|
||||
|
||||
// Add different types of exercises to each node
|
||||
for j in 1..=2 {
|
||||
let exercise_id = (i - 1) * 2 + j;
|
||||
let exercise_type = match j {
|
||||
1 => "vocabulary",
|
||||
2 => "multiple_choice",
|
||||
_ => "fill_blank",
|
||||
};
|
||||
|
||||
let exercise = Exercise {
|
||||
id: exercise_id,
|
||||
ex_type: exercise_type.to_string(),
|
||||
content: format!(
|
||||
r#"{{"type": "{}", "content": "Exercise {} for node {}"}}"#,
|
||||
exercise_type, exercise_id, i
|
||||
),
|
||||
node_id: i,
|
||||
};
|
||||
|
||||
node.exercises.push(exercise);
|
||||
}
|
||||
|
||||
complex_path.nodes.push(node);
|
||||
}
|
||||
|
||||
// Save complex path
|
||||
let path_id = repo_manager.paths().save_path(complex_path.clone()).await?;
|
||||
let path_id_int = path_id.parse::<i32>().unwrap();
|
||||
|
||||
// Retrieve and verify complex structure
|
||||
let retrieved_path = repo_manager.paths().get_path_by_id(path_id_int).await?;
|
||||
assert_eq!(retrieved_path.nodes.len(), 3);
|
||||
|
||||
let total_exercises: usize = retrieved_path.nodes.iter().map(|n| n.exercises.len()).sum();
|
||||
assert_eq!(total_exercises, 6);
|
||||
|
||||
// Test statistics on complex path
|
||||
let stats = repo_manager.get_path_statistics(path_id_int).await?;
|
||||
assert_eq!(stats.node_count, 3);
|
||||
assert_eq!(stats.total_exercises, 6);
|
||||
assert_eq!(stats.avg_exercises_per_node, 2.0);
|
||||
|
||||
// Test search across complex content
|
||||
let search_results = repo_manager.search_paths("Complex").await?;
|
||||
assert_eq!(search_results.len(), 1);
|
||||
|
||||
test_db.cleanup().await?;
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user