use reqwest::{Client, Response}; use serde_json::{json, Value}; use std::collections::HashMap; use std::time::{SystemTime, UNIX_EPOCH}; use tokio::time::{sleep, Duration}; #[derive(Debug)] struct TestClient { client: Client, base_url: String, auth_token: Option, } impl TestClient { fn new() -> Self { Self { client: Client::new(), base_url: "http://localhost:8000".to_string(), auth_token: None, } } async fn check_server_health(&self) -> Result<(), Box> { let response = self .client .get(&format!("{}/api/health", self.base_url)) .send() .await?; if response.status().is_success() { Ok(()) } else { Err(format!("Server health check failed: {}", response.status()).into()) } } async fn register_user(&mut self, username: &str, email: &str, password: &str) -> Result<(), Box> { let response = self .client .post(&format!("{}/api/auth/register", self.base_url)) .json(&json!({ "username": username, "email": email, "password": password })) .send() .await?; if response.status().is_success() { Ok(()) } else { let error_text = response.text().await?; Err(format!("Registration failed: {}", error_text).into()) } } async fn login(&mut self, username: &str, password: &str) -> Result<(), Box> { let response = self .client .post(&format!("{}/api/auth/login", self.base_url)) .json(&json!({ "username": username, "password": password })) .send() .await?; if response.status().is_success() { let login_response: Value = response.json().await?; if let Some(token) = login_response["token"].as_str() { self.auth_token = Some(token.to_string()); Ok(()) } else { Err("No token in login response".into()) } } else { let error_text = response.text().await?; Err(format!("Login failed: {}", error_text).into()) } } fn get_auth_headers(&self) -> HashMap { let mut headers = HashMap::new(); if let Some(token) = &self.auth_token { headers.insert("Authorization".to_string(), format!("Bearer {}", token)); } headers } async fn create_label(&self, name: &str, description: Option<&str>, color: &str, icon: Option<&str>) -> Result> { let mut payload = json!({ "name": name, "color": color }); if let Some(desc) = description { payload["description"] = json!(desc); } if let Some(icon_name) = icon { payload["icon"] = json!(icon_name); } let mut request = self .client .post(&format!("{}/api/labels", self.base_url)) .json(&payload); for (key, value) in self.get_auth_headers() { request = request.header(&key, &value); } let response = request.send().await?; if response.status().is_success() { let label: Value = response.json().await?; Ok(label) } else { let error_text = response.text().await?; Err(format!("Failed to create label: {}", error_text).into()) } } async fn get_labels(&self, include_counts: bool) -> Result, Box> { let url = if include_counts { format!("{}/api/labels?include_counts=true", self.base_url) } else { format!("{}/api/labels", self.base_url) }; let mut request = self.client.get(&url); for (key, value) in self.get_auth_headers() { request = request.header(&key, &value); } let response = request.send().await?; if response.status().is_success() { let labels: Vec = response.json().await?; Ok(labels) } else { let error_text = response.text().await?; Err(format!("Failed to get labels: {}", error_text).into()) } } async fn update_label(&self, label_id: &str, updates: Value) -> Result> { let mut request = self .client .put(&format!("{}/api/labels/{}", self.base_url, label_id)) .json(&updates); for (key, value) in self.get_auth_headers() { request = request.header(&key, &value); } let response = request.send().await?; if response.status().is_success() { let label: Value = response.json().await?; Ok(label) } else { let error_text = response.text().await?; Err(format!("Failed to update label: {}", error_text).into()) } } async fn delete_label(&self, label_id: &str) -> Result<(), Box> { let mut request = self .client .delete(&format!("{}/api/labels/{}", self.base_url, label_id)); for (key, value) in self.get_auth_headers() { request = request.header(&key, &value); } let response = request.send().await?; if response.status().is_success() || response.status().as_u16() == 204 { Ok(()) } else { let error_text = response.text().await?; Err(format!("Failed to delete label: {}", error_text).into()) } } async fn upload_document(&self, content: &[u8], filename: &str) -> Result> { let form = reqwest::multipart::Form::new() .part("file", reqwest::multipart::Part::bytes(content.to_vec()) .file_name(filename.to_string()) .mime_str("text/plain")?); let mut request = self .client .post(&format!("{}/api/documents", self.base_url)) .multipart(form); for (key, value) in self.get_auth_headers() { request = request.header(&key, &value); } let response = request.send().await?; if response.status().is_success() { let document: Value = response.json().await?; Ok(document) } else { let error_text = response.text().await?; Err(format!("Failed to upload document: {}", error_text).into()) } } async fn assign_label_to_document(&self, document_id: &str, label_id: &str) -> Result<(), Box> { let mut request = self .client .post(&format!("{}/api/labels/documents/{}/labels/{}", self.base_url, document_id, label_id)); for (key, value) in self.get_auth_headers() { request = request.header(&key, &value); } let response = request.send().await?; if response.status().is_success() { Ok(()) } else { let error_text = response.text().await?; Err(format!("Failed to assign label to document: {}", error_text).into()) } } async fn get_document_labels(&self, document_id: &str) -> Result, Box> { let mut request = self .client .get(&format!("{}/api/labels/documents/{}", self.base_url, document_id)); for (key, value) in self.get_auth_headers() { request = request.header(&key, &value); } let response = request.send().await?; if response.status().is_success() { let labels: Vec = response.json().await?; Ok(labels) } else { let error_text = response.text().await?; Err(format!("Failed to get document labels: {}", error_text).into()) } } async fn update_document_labels(&self, document_id: &str, label_ids: Vec<&str>) -> Result, Box> { let payload = json!({ "label_ids": label_ids }); let mut request = self .client .put(&format!("{}/api/labels/documents/{}", self.base_url, document_id)) .json(&payload); for (key, value) in self.get_auth_headers() { request = request.header(&key, &value); } let response = request.send().await?; if response.status().is_success() { let labels: Vec = response.json().await?; Ok(labels) } else { let error_text = response.text().await?; Err(format!("Failed to update document labels: {}", error_text).into()) } } async fn remove_label_from_document(&self, document_id: &str, label_id: &str) -> Result<(), Box> { let mut request = self .client .delete(&format!("{}/api/labels/documents/{}/labels/{}", self.base_url, document_id, label_id)); for (key, value) in self.get_auth_headers() { request = request.header(&key, &value); } let response = request.send().await?; if response.status().is_success() || response.status().as_u16() == 204 { Ok(()) } else { let error_text = response.text().await?; Err(format!("Failed to remove label from document: {}", error_text).into()) } } } #[tokio::test] async fn test_label_crud_operations() -> Result<(), Box> { let mut client = TestClient::new(); // Check server health client.check_server_health().await?; // Create unique user let timestamp = SystemTime::now().duration_since(UNIX_EPOCH)?.as_millis(); let username = format!("label_test_user_{}", timestamp); let email = format!("label_test_{}@example.com", timestamp); client.register_user(&username, &email, "test_password").await?; client.login(&username, "test_password").await?; // Test: Create label println!("Testing label creation..."); let created_label = client.create_label( "Test Label", Some("A test label for integration testing"), "#ff0000", Some("star") ).await?; assert_eq!(created_label["name"], "Test Label"); assert_eq!(created_label["color"], "#ff0000"); assert_eq!(created_label["icon"], "star"); assert_eq!(created_label["is_system"], false); let label_id = created_label["id"].as_str().unwrap(); // Test: Get all labels println!("Testing label retrieval..."); let labels = client.get_labels(false).await?; assert!(labels.len() >= 1); let found_label = labels.iter().find(|l| l["id"] == label_id); assert!(found_label.is_some()); // Test: Get labels with counts let labels_with_counts = client.get_labels(true).await?; assert!(labels_with_counts.len() >= 1); let found_label_with_count = labels_with_counts.iter().find(|l| l["id"] == label_id); assert!(found_label_with_count.is_some()); assert_eq!(found_label_with_count.unwrap()["document_count"], 0); // Test: Update label println!("Testing label update..."); let updates = json!({ "name": "Updated Test Label", "color": "#00ff00", "description": "Updated description" }); let updated_label = client.update_label(label_id, updates).await?; assert_eq!(updated_label["name"], "Updated Test Label"); assert_eq!(updated_label["color"], "#00ff00"); assert_eq!(updated_label["description"], "Updated description"); // Test: Delete label println!("Testing label deletion..."); client.delete_label(label_id).await?; // Verify deletion let labels_after_delete = client.get_labels(false).await?; let deleted_label = labels_after_delete.iter().find(|l| l["id"] == label_id); assert!(deleted_label.is_none()); println!("Label CRUD operations test completed successfully!"); Ok(()) } #[tokio::test] async fn test_document_label_assignment() -> Result<(), Box> { let mut client = TestClient::new(); // Check server health client.check_server_health().await?; // Create unique user let timestamp = SystemTime::now().duration_since(UNIX_EPOCH)?.as_millis(); let username = format!("doc_label_test_user_{}", timestamp); let email = format!("doc_label_test_{}@example.com", timestamp); client.register_user(&username, &email, "test_password").await?; client.login(&username, "test_password").await?; // Create labels println!("Creating test labels..."); let label1 = client.create_label("Important", Some("High priority items"), "#ff0000", Some("star")).await?; let label2 = client.create_label("Work", Some("Work-related documents"), "#0000ff", Some("work")).await?; let label3 = client.create_label("Personal", Some("Personal documents"), "#00ff00", Some("person")).await?; let label1_id = label1["id"].as_str().unwrap(); let label2_id = label2["id"].as_str().unwrap(); let label3_id = label3["id"].as_str().unwrap(); // Upload a test document println!("Uploading test document..."); let document_content = b"This is a test document for label assignment testing."; let document = client.upload_document(document_content, "test_document.txt").await?; let document_id = document["id"].as_str().unwrap(); // Test: Assign single label println!("Testing single label assignment..."); client.assign_label_to_document(document_id, label1_id).await?; let document_labels = client.get_document_labels(document_id).await?; assert_eq!(document_labels.len(), 1); assert_eq!(document_labels[0]["id"], label1_id); // Test: Assign additional label println!("Testing additional label assignment..."); client.assign_label_to_document(document_id, label2_id).await?; let document_labels = client.get_document_labels(document_id).await?; assert_eq!(document_labels.len(), 2); // Test: Update document labels (replace) println!("Testing label replacement..."); let new_labels = client.update_document_labels(document_id, vec![label2_id, label3_id]).await?; assert_eq!(new_labels.len(), 2); let label_ids: Vec<&str> = new_labels.iter() .map(|l| l["id"].as_str().unwrap()) .collect(); assert!(label_ids.contains(&label2_id)); assert!(label_ids.contains(&label3_id)); assert!(!label_ids.contains(&label1_id)); // Test: Remove single label println!("Testing single label removal..."); client.remove_label_from_document(document_id, label2_id).await?; let document_labels = client.get_document_labels(document_id).await?; assert_eq!(document_labels.len(), 1); assert_eq!(document_labels[0]["id"], label3_id); // Test: Remove all labels println!("Testing removal of all labels..."); client.update_document_labels(document_id, vec![]).await?; let document_labels = client.get_document_labels(document_id).await?; assert_eq!(document_labels.len(), 0); // Test: Verify label usage counts println!("Testing label usage counts..."); let labels_with_counts = client.get_labels(true).await?; for label in labels_with_counts { let label_id = label["id"].as_str().unwrap(); if label_id == label1_id || label_id == label2_id || label_id == label3_id { assert_eq!(label["document_count"], 0); } } println!("Document label assignment test completed successfully!"); Ok(()) } #[tokio::test] async fn test_system_labels_access() -> Result<(), Box> { let mut client = TestClient::new(); // Check server health client.check_server_health().await?; // Create unique user let timestamp = SystemTime::now().duration_since(UNIX_EPOCH)?.as_millis(); let username = format!("system_label_test_user_{}", timestamp); let email = format!("system_label_test_{}@example.com", timestamp); client.register_user(&username, &email, "test_password").await?; client.login(&username, "test_password").await?; // Test: Get labels (should include system labels) println!("Testing system labels access..."); let labels = client.get_labels(false).await?; // Should have system labels let system_labels: Vec<&Value> = labels.iter() .filter(|l| l["is_system"].as_bool().unwrap_or(false)) .collect(); assert!(system_labels.len() > 0, "Should have system labels"); // Check for some expected system labels let system_label_names: Vec<&str> = system_labels.iter() .map(|l| l["name"].as_str().unwrap()) .collect(); let expected_system_labels = vec!["Important", "Archive", "Personal", "Work"]; for expected in expected_system_labels { assert!( system_label_names.contains(&expected), "Expected system label '{}' not found. Available: {:?}", expected, system_label_names ); } // Test: Upload document and assign system label println!("Testing system label assignment..."); let document_content = b"This is a test document for system label assignment."; let document = client.upload_document(document_content, "system_label_test.txt").await?; let document_id = document["id"].as_str().unwrap(); // Find a system label to assign let important_label = system_labels.iter() .find(|l| l["name"] == "Important") .expect("Important system label not found"); let important_label_id = important_label["id"].as_str().unwrap(); // Assign system label to document client.assign_label_to_document(document_id, important_label_id).await?; let document_labels = client.get_document_labels(document_id).await?; assert_eq!(document_labels.len(), 1); assert_eq!(document_labels[0]["id"], important_label_id); assert_eq!(document_labels[0]["is_system"], true); println!("System labels access test completed successfully!"); Ok(()) } #[tokio::test] #[ignore = "Requires server restart to pick up validation changes"] async fn test_label_validation() -> Result<(), Box> { let mut client = TestClient::new(); // Check server health client.check_server_health().await?; // Create unique user let timestamp = SystemTime::now().duration_since(UNIX_EPOCH)?.as_millis(); let username = format!("validation_test_user_{}", timestamp); let email = format!("validation_test_{}@example.com", timestamp); client.register_user(&username, &email, "test_password").await?; client.login(&username, "test_password").await?; // Test: Invalid color format should fail println!("Testing invalid color validation..."); let response = client.client .post(&format!("{}/api/labels", client.base_url)) .header("Authorization", format!("Bearer {}", client.auth_token.as_ref().unwrap())) .json(&json!({ "name": "Invalid Color", "color": "invalid_color" })) .send() .await?; assert!(!response.status().is_success(), "Should reject invalid color format"); // Test: Empty name should fail println!("Testing empty name validation..."); let response = client.client .post(&format!("{}/api/labels", client.base_url)) .header("Authorization", format!("Bearer {}", client.auth_token.as_ref().unwrap())) .json(&json!({ "name": "", "color": "#ff0000" })) .send() .await?; assert!(!response.status().is_success(), "Should reject empty name"); // Test: Duplicate name should fail println!("Testing duplicate name validation..."); let label_name = "Duplicate Test"; // Create first label client.create_label(label_name, None, "#ff0000", None).await?; // Try to create duplicate let response = client.client .post(&format!("{}/api/labels", client.base_url)) .header("Authorization", format!("Bearer {}", client.auth_token.as_ref().unwrap())) .json(&json!({ "name": label_name, "color": "#00ff00" })) .send() .await?; assert!(!response.status().is_success(), "Should reject duplicate name"); println!("Label validation test completed successfully!"); Ok(()) } #[tokio::test] async fn test_label_permissions() -> Result<(), Box> { let mut client1 = TestClient::new(); let mut client2 = TestClient::new(); // Check server health client1.check_server_health().await?; // Create two unique users let timestamp = SystemTime::now().duration_since(UNIX_EPOCH)?.as_millis(); let username1 = format!("perm_test_user1_{}", timestamp); let email1 = format!("perm_test1_{}@example.com", timestamp); let username2 = format!("perm_test_user2_{}", timestamp); let email2 = format!("perm_test2_{}@example.com", timestamp); client1.register_user(&username1, &email1, "test_password").await?; client1.login(&username1, "test_password").await?; sleep(Duration::from_millis(100)).await; // Small delay to ensure unique timestamps client2.register_user(&username2, &email2, "test_password").await?; client2.login(&username2, "test_password").await?; // User 1 creates a label println!("Testing cross-user label access permissions..."); let user1_label = client1.create_label("User 1 Label", None, "#ff0000", None).await?; let user1_label_id = user1_label["id"].as_str().unwrap(); // User 2 should not be able to update User 1's label println!("Testing unauthorized label update..."); let updates = json!({ "name": "Hacked Label", "color": "#000000" }); let response = client2.client .put(&format!("{}/api/labels/{}", client2.base_url, user1_label_id)) .header("Authorization", format!("Bearer {}", client2.auth_token.as_ref().unwrap())) .json(&updates) .send() .await?; assert!(!response.status().is_success(), "User 2 should not be able to update User 1's label"); // User 2 should not be able to delete User 1's label println!("Testing unauthorized label deletion..."); let response = client2.client .delete(&format!("{}/api/labels/{}", client2.base_url, user1_label_id)) .header("Authorization", format!("Bearer {}", client2.auth_token.as_ref().unwrap())) .send() .await?; assert!(!response.status().is_success(), "User 2 should not be able to delete User 1's label"); // User 1's label should still exist and be unchanged println!("Verifying label integrity..."); let user1_labels = client1.get_labels(false).await?; let found_label = user1_labels.iter().find(|l| l["id"] == user1_label_id); assert!(found_label.is_some()); assert_eq!(found_label.unwrap()["name"], "User 1 Label"); println!("Label permissions test completed successfully!"); Ok(()) }