Initial Commit

This commit is contained in:
Andy
2025-08-11 14:31:51 +08:00
commit 39e94c1b13
36 changed files with 12517 additions and 0 deletions
+457
View File
@@ -0,0 +1,457 @@
//! Test fixtures and helper data for the docx-mcp test suite
use anyhow::Result;
use docx_mcp::docx_handler::{DocxHandler, DocxStyle, TableData};
use serde_json::{json, Value};
use std::collections::HashMap;
use tempfile::TempDir;
pub mod sample_documents;
pub mod test_data;
/// Common test fixture for creating a handler with a temporary directory
pub fn create_test_handler() -> (DocxHandler, TempDir) {
let temp_dir = TempDir::new().unwrap();
let handler = DocxHandler::new_with_temp_dir(temp_dir.path()).unwrap();
(handler, temp_dir)
}
/// Create a handler with a document containing basic content
pub fn create_handler_with_document() -> (DocxHandler, String, TempDir) {
let (mut handler, temp_dir) = create_test_handler();
let doc_id = handler.create_document().unwrap();
(handler, doc_id, temp_dir)
}
/// Standard document styles for testing
pub struct TestStyles;
impl TestStyles {
pub fn basic() -> DocxStyle {
DocxStyle {
font_family: Some("Calibri".to_string()),
font_size: Some(11),
bold: Some(false),
italic: Some(false),
underline: Some(false),
color: Some("#000000".to_string()),
alignment: Some("left".to_string()),
line_spacing: Some(1.15),
}
}
pub fn heading() -> DocxStyle {
DocxStyle {
font_family: Some("Calibri".to_string()),
font_size: Some(16),
bold: Some(true),
italic: Some(false),
underline: Some(false),
color: Some("#1f4e79".to_string()),
alignment: Some("left".to_string()),
line_spacing: Some(1.15),
}
}
pub fn emphasis() -> DocxStyle {
DocxStyle {
font_family: Some("Calibri".to_string()),
font_size: Some(11),
bold: Some(true),
italic: Some(true),
underline: Some(false),
color: Some("#c55a11".to_string()),
alignment: Some("left".to_string()),
line_spacing: Some(1.15),
}
}
pub fn centered() -> DocxStyle {
DocxStyle {
font_family: Some("Calibri".to_string()),
font_size: Some(11),
bold: Some(false),
italic: Some(false),
underline: Some(false),
color: Some("#000000".to_string()),
alignment: Some("center".to_string()),
line_spacing: Some(1.15),
}
}
}
/// Standard table data for testing
pub struct TestTables;
impl TestTables {
pub fn simple_2x2() -> TableData {
TableData {
rows: vec![
vec!["Row 1 Col 1".to_string(), "Row 1 Col 2".to_string()],
vec!["Row 2 Col 1".to_string(), "Row 2 Col 2".to_string()],
],
headers: None,
border_style: Some("single".to_string()),
}
}
pub fn with_headers() -> TableData {
TableData {
rows: vec![
vec!["Name".to_string(), "Age".to_string(), "City".to_string()],
vec!["John".to_string(), "30".to_string(), "New York".to_string()],
vec!["Jane".to_string(), "25".to_string(), "Los Angeles".to_string()],
vec!["Bob".to_string(), "35".to_string(), "Chicago".to_string()],
],
headers: Some(vec!["Name".to_string(), "Age".to_string(), "City".to_string()]),
border_style: Some("single".to_string()),
}
}
pub fn financial_data() -> TableData {
TableData {
rows: vec![
vec!["Quarter".to_string(), "Revenue".to_string(), "Profit".to_string(), "Growth".to_string()],
vec!["Q1 2024".to_string(), "$1.2M".to_string(), "$240K".to_string(), "15%".to_string()],
vec!["Q2 2024".to_string(), "$1.4M".to_string(), "$290K".to_string(), "18%".to_string()],
vec!["Q3 2024".to_string(), "$1.6M".to_string(), "$340K".to_string(), "22%".to_string()],
vec!["Q4 2024".to_string(), "$1.8M".to_string(), "$380K".to_string(), "25%".to_string()],
],
headers: Some(vec!["Quarter".to_string(), "Revenue".to_string(), "Profit".to_string(), "Growth".to_string()]),
border_style: Some("single".to_string()),
}
}
pub fn large_table(rows: usize, cols: usize) -> TableData {
let mut table_rows = Vec::new();
// Header row
let header_row: Vec<String> = (0..cols)
.map(|i| format!("Column {}", i + 1))
.collect();
table_rows.push(header_row.clone());
// Data rows
for row in 0..rows {
let data_row: Vec<String> = (0..cols)
.map(|col| format!("R{}C{}", row + 1, col + 1))
.collect();
table_rows.push(data_row);
}
TableData {
rows: table_rows,
headers: Some(header_row),
border_style: Some("single".to_string()),
}
}
}
/// Standard list data for testing
pub struct TestLists;
impl TestLists {
pub fn simple_bullets() -> Vec<String> {
vec![
"First bullet point".to_string(),
"Second bullet point".to_string(),
"Third bullet point".to_string(),
]
}
pub fn numbered_steps() -> Vec<String> {
vec![
"Open the application".to_string(),
"Navigate to the settings menu".to_string(),
"Select the desired configuration".to_string(),
"Save your changes".to_string(),
"Restart the application".to_string(),
]
}
pub fn features_list() -> Vec<String> {
vec![
"Advanced document editing capabilities".to_string(),
"Real-time collaboration tools".to_string(),
"Cloud synchronization".to_string(),
"Version control and history tracking".to_string(),
"Export to multiple formats (PDF, HTML, Markdown)".to_string(),
"Template library with professional designs".to_string(),
"Advanced formatting and styling options".to_string(),
]
}
pub fn technical_requirements() -> Vec<String> {
vec![
"Rust 1.70 or higher".to_string(),
"Memory: 2GB RAM minimum, 4GB recommended".to_string(),
"Storage: 500MB available space".to_string(),
"Network: Internet connection for cloud features".to_string(),
"OS: Windows 10, macOS 10.15, or Linux (Ubuntu 20.04+)".to_string(),
]
}
pub fn large_list(item_count: usize) -> Vec<String> {
(1..=item_count)
.map(|i| format!("List item number {} with descriptive content", i))
.collect()
}
}
/// Sample text content for testing
pub struct TestContent;
impl TestContent {
pub fn lorem_ipsum() -> &'static str {
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
}
pub fn technical_paragraph() -> &'static str {
"This application leverages cutting-edge Rust technology to provide high-performance document processing capabilities. The architecture is built on modern asynchronous programming patterns, ensuring efficient resource utilization and scalability. Key features include memory-safe operations, zero-cost abstractions, and excellent concurrent processing performance."
}
pub fn business_paragraph() -> &'static str {
"Our comprehensive business solution addresses the evolving needs of modern enterprises through innovative technology and streamlined workflows. With a focus on productivity enhancement and cost reduction, this platform delivers measurable value across multiple departments and use cases. The solution integrates seamlessly with existing infrastructure while providing robust security and compliance features."
}
pub fn multilingual_content() -> Vec<(&'static str, &'static str)> {
vec![
("English", "The quick brown fox jumps over the lazy dog."),
("Spanish", "El zorro marrón rápido salta sobre el perro perezoso."),
("French", "Le renard brun rapide saute par-dessus le chien paresseux."),
("German", "Der schnelle braune Fuchs springt über den faulen Hund."),
("Italian", "La volpe marrone veloce salta sopra il cane pigro."),
("Portuguese", "A raposa marrom rápida pula sobre o cão preguiçoso."),
("Japanese", "素早い茶色のキツネは怠惰な犬を飛び越える。"),
("Chinese", "敏捷的棕色狐狸跳过懒狗。"),
("Korean", "빠른 갈색 여우가 게으른 개를 뛰어넘는다."),
("Russian", "Быстрая коричневая лиса прыгает через ленивую собаку."),
]
}
pub fn special_characters() -> &'static str {
"Special characters test: àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ ĀāĂ㥹ĆćĈĉĊċČčĎďĐđĒēĔĕĖėĘęĚě"
}
pub fn symbols_and_math() -> &'static str {
"Mathematical symbols: ∑ ∏ ∫ √ ≤ ≥ ≠ ± ∞ ∂ ∇ Ω α β γ δ ε θ λ μ π σ φ ψ ω"
}
pub fn long_paragraph(sentence_count: usize) -> String {
let sentences = vec![
"This is a comprehensive test of document processing capabilities.",
"The system handles various types of content efficiently and accurately.",
"Performance optimization ensures smooth operation even with large documents.",
"Advanced formatting features provide professional document appearance.",
"Error handling mechanisms maintain system stability under all conditions.",
"Security features protect sensitive information throughout the process.",
"Integration capabilities allow seamless workflow with existing systems.",
"User-friendly interfaces make complex operations simple and intuitive.",
"Scalable architecture supports growing business requirements.",
"Continuous improvements ensure the solution remains cutting-edge.",
];
let mut result = String::new();
for i in 0..sentence_count {
let sentence = sentences[i % sentences.len()];
result.push_str(sentence);
if i < sentence_count - 1 {
result.push(' ');
}
}
result
}
}
/// MCP tool call arguments for testing
pub struct TestMcpArgs;
impl TestMcpArgs {
pub fn create_document() -> Value {
json!({})
}
pub fn add_paragraph(doc_id: &str, text: &str, style: Option<DocxStyle>) -> Value {
let mut args = json!({
"document_id": doc_id,
"text": text
});
if let Some(s) = style {
args["style"] = json!({
"font_family": s.font_family,
"font_size": s.font_size,
"bold": s.bold,
"italic": s.italic,
"underline": s.underline,
"color": s.color,
"alignment": s.alignment,
"line_spacing": s.line_spacing
});
}
args
}
pub fn add_heading(doc_id: &str, text: &str, level: usize) -> Value {
json!({
"document_id": doc_id,
"text": text,
"level": level
})
}
pub fn add_table(doc_id: &str, table_data: &TableData) -> Value {
json!({
"document_id": doc_id,
"rows": table_data.rows
})
}
pub fn add_list(doc_id: &str, items: &[String], ordered: bool) -> Value {
json!({
"document_id": doc_id,
"items": items,
"ordered": ordered
})
}
pub fn extract_text(doc_id: &str) -> Value {
json!({
"document_id": doc_id
})
}
pub fn search_text(doc_id: &str, search_term: &str, case_sensitive: bool) -> Value {
json!({
"document_id": doc_id,
"search_term": search_term,
"case_sensitive": case_sensitive
})
}
pub fn get_metadata(doc_id: &str) -> Value {
json!({
"document_id": doc_id
})
}
pub fn convert_to_pdf(doc_id: &str, output_path: &str) -> Value {
json!({
"document_id": doc_id,
"output_path": output_path
})
}
pub fn save_document(doc_id: &str, output_path: &str) -> Value {
json!({
"document_id": doc_id,
"output_path": output_path
})
}
}
/// Performance test data generators
pub struct PerformanceData;
impl PerformanceData {
pub fn create_large_document(handler: &mut DocxHandler, paragraph_count: usize) -> Result<String> {
let doc_id = handler.create_document()?;
handler.add_heading(&doc_id, "Performance Test Document", 1)?;
for i in 0..paragraph_count {
if i % 50 == 0 && i > 0 {
handler.add_heading(&doc_id, &format!("Section {}", i / 50), 2)?;
}
let content = format!(
"This is paragraph {} in our performance test document. It contains substantial text content to simulate real-world usage patterns and test system performance under realistic load conditions. The paragraph includes various punctuation marks, numbers like {}, and other elements that affect processing performance.",
i + 1, (i + 1) * 7
);
handler.add_paragraph(&doc_id, &content, None)?;
// Add tables periodically
if i % 100 == 99 {
let table_data = TestTables::simple_2x2();
handler.add_table(&doc_id, table_data)?;
}
}
Ok(doc_id)
}
pub fn create_complex_document(handler: &mut DocxHandler) -> Result<String> {
let doc_id = handler.create_document()?;
// Add comprehensive content with all features
handler.add_heading(&doc_id, "Complex Document Test", 1)?;
handler.set_header(&doc_id, "Complex Document Header")?;
handler.set_footer(&doc_id, "Complex Document Footer")?;
handler.add_paragraph(&doc_id, TestContent::business_paragraph(), Some(TestStyles::basic()))?;
handler.add_heading(&doc_id, "Technical Details", 2)?;
handler.add_paragraph(&doc_id, TestContent::technical_paragraph(), None)?;
let features_list = TestLists::features_list();
handler.add_list(&doc_id, features_list, false)?;
handler.add_heading(&doc_id, "Financial Overview", 2)?;
let financial_table = TestTables::financial_data();
handler.add_table(&doc_id, financial_table)?;
handler.add_page_break(&doc_id)?;
handler.add_heading(&doc_id, "Multilingual Content", 2)?;
for (language, text) in TestContent::multilingual_content() {
handler.add_paragraph(&doc_id, &format!("{}: {}", language, text), None)?;
}
handler.add_heading(&doc_id, "Special Characters", 2)?;
handler.add_paragraph(&doc_id, TestContent::special_characters(), None)?;
handler.add_paragraph(&doc_id, TestContent::symbols_and_math(), None)?;
Ok(doc_id)
}
}
/// Error testing utilities
pub struct ErrorTestCases;
impl ErrorTestCases {
pub fn invalid_document_ids() -> Vec<&'static str> {
vec![
"nonexistent-123",
"fake-document-id",
"invalid-uuid",
"",
" ",
"null",
"undefined",
]
}
pub fn invalid_mcp_calls() -> Vec<(&'static str, Value)> {
vec![
("add_paragraph", json!({"text": "missing document_id"})),
("add_heading", json!({"document_id": "test", "level": 10})),
("add_table", json!({"document_id": "test", "rows": "not_an_array"})),
("add_list", json!({"document_id": "test", "items": 123})),
("search_text", json!({"document_id": "test"})), // Missing search_term
("convert_to_pdf", json!({"document_id": "test"})), // Missing output_path
]
}
pub fn security_blocked_operations() -> Vec<(&'static str, Value)> {
vec![
("create_document", json!({})),
("add_paragraph", json!({"document_id": "test", "text": "blocked"})),
("save_document", json!({"document_id": "test", "output_path": "/tmp/test.docx"})),
("convert_to_pdf", json!({"document_id": "test", "output_path": "/tmp/test.pdf"})),
("find_and_replace", json!({"document_id": "test", "find_text": "a", "replace_text": "b"})),
]
}
}
+509
View File
@@ -0,0 +1,509 @@
//! Sample document templates and content for testing
use anyhow::Result;
use docx_mcp::docx_handler::{DocxHandler, DocxStyle, TableData};
use super::{TestStyles, TestTables, TestLists, TestContent};
/// Creates a business letter document for testing
pub fn create_business_letter(handler: &mut DocxHandler) -> Result<String> {
let doc_id = handler.create_document()?;
// Header
handler.set_header(&doc_id, "ACME Corporation | 123 Business St, City, State 12345")?;
// Date
handler.add_paragraph(&doc_id, "December 15, 2024", Some(TestStyles::basic()))?;
handler.add_paragraph(&doc_id, "", None)?; // Empty line
// Recipient
handler.add_paragraph(&doc_id, "Ms. Jane Smith", Some(TestStyles::basic()))?;
handler.add_paragraph(&doc_id, "Director of Operations", Some(TestStyles::basic()))?;
handler.add_paragraph(&doc_id, "XYZ Company", Some(TestStyles::basic()))?;
handler.add_paragraph(&doc_id, "456 Corporate Ave", Some(TestStyles::basic()))?;
handler.add_paragraph(&doc_id, "Business City, State 67890", Some(TestStyles::basic()))?;
handler.add_paragraph(&doc_id, "", None)?; // Empty line
// Subject
handler.add_paragraph(&doc_id, "RE: Partnership Proposal", Some(TestStyles::emphasis()))?;
handler.add_paragraph(&doc_id, "", None)?; // Empty line
// Salutation
handler.add_paragraph(&doc_id, "Dear Ms. Smith,", Some(TestStyles::basic()))?;
handler.add_paragraph(&doc_id, "", None)?; // Empty line
// Body paragraphs
handler.add_paragraph(&doc_id,
"I am writing to propose a strategic partnership between ACME Corporation and XYZ Company that would benefit both organizations significantly. Our companies share similar values and complementary strengths that could create substantial value for our respective customers.",
Some(TestStyles::basic()))?;
handler.add_paragraph(&doc_id,
"ACME Corporation has been a leader in technology solutions for over 15 years, with a strong track record of innovation and customer satisfaction. We believe that combining our technical expertise with your operational excellence would create a powerful synergy in the marketplace.",
Some(TestStyles::basic()))?;
handler.add_paragraph(&doc_id,
"The proposed partnership would include joint product development, shared marketing initiatives, and coordinated customer support efforts. We estimate this collaboration could increase revenue for both companies by 25% within the first year.",
Some(TestStyles::basic()))?;
handler.add_paragraph(&doc_id,
"I would welcome the opportunity to discuss this proposal in more detail at your convenience. Please let me know when you might be available for a meeting or conference call.",
Some(TestStyles::basic()))?;
handler.add_paragraph(&doc_id, "", None)?; // Empty line
// Closing
handler.add_paragraph(&doc_id, "Sincerely,", Some(TestStyles::basic()))?;
handler.add_paragraph(&doc_id, "", None)?; // Space for signature
handler.add_paragraph(&doc_id, "", None)?; // Space for signature
handler.add_paragraph(&doc_id, "John Doe", Some(TestStyles::basic()))?;
handler.add_paragraph(&doc_id, "Chief Executive Officer", Some(TestStyles::basic()))?;
handler.add_paragraph(&doc_id, "ACME Corporation", Some(TestStyles::basic()))?;
// Footer
handler.set_footer(&doc_id, "ACME Corporation - Confidential and Proprietary")?;
Ok(doc_id)
}
/// Creates a technical report document for testing
pub fn create_technical_report(handler: &mut DocxHandler) -> Result<String> {
let doc_id = handler.create_document()?;
// Title page
handler.add_paragraph(&doc_id, "", None)?; // Empty line for spacing
handler.add_paragraph(&doc_id, "", None)?;
handler.add_paragraph(&doc_id, "", None)?;
handler.add_heading(&doc_id, "System Performance Analysis Report", 1)?;
handler.add_paragraph(&doc_id, "", None)?;
handler.add_paragraph(&doc_id, "Quarterly Assessment - Q4 2024", Some(TestStyles::centered()))?;
handler.add_paragraph(&doc_id, "", None)?;
handler.add_paragraph(&doc_id, "Prepared by: Technical Team", Some(TestStyles::centered()))?;
handler.add_paragraph(&doc_id, "Date: December 15, 2024", Some(TestStyles::centered()))?;
handler.add_page_break(&doc_id)?;
// Executive Summary
handler.add_heading(&doc_id, "Executive Summary", 1)?;
handler.add_paragraph(&doc_id,
"This report provides a comprehensive analysis of system performance metrics for Q4 2024. Key findings include significant improvements in response times, enhanced security measures, and successful implementation of new monitoring capabilities.",
Some(TestStyles::basic()))?;
let summary_points = vec![
"Average response time improved by 35%".to_string(),
"System uptime achieved 99.97%".to_string(),
"Security incidents reduced by 60%".to_string(),
"User satisfaction increased to 94%".to_string(),
];
handler.add_list(&doc_id, summary_points, false)?;
// Performance Metrics
handler.add_heading(&doc_id, "Performance Metrics", 1)?;
handler.add_heading(&doc_id, "Response Time Analysis", 2)?;
handler.add_paragraph(&doc_id,
"Response time measurements were collected continuously throughout Q4 2024. The data shows consistent improvement across all service endpoints.",
Some(TestStyles::basic()))?;
let response_time_data = TableData {
rows: vec![
vec!["Service".to_string(), "Q3 2024 (ms)".to_string(), "Q4 2024 (ms)".to_string(), "Improvement".to_string()],
vec!["Authentication".to_string(), "245".to_string(), "158".to_string(), "35.5%".to_string()],
vec!["Database Query".to_string(), "892".to_string(), "623".to_string(), "30.2%".to_string()],
vec!["File Processing".to_string(), "1,240".to_string(), "789".to_string(), "36.4%".to_string()],
vec!["Report Generation".to_string(), "3,450".to_string(), "2,180".to_string(), "36.8%".to_string()],
],
headers: Some(vec!["Service".to_string(), "Q3 2024 (ms)".to_string(), "Q4 2024 (ms)".to_string(), "Improvement".to_string()]),
border_style: Some("single".to_string()),
};
handler.add_table(&doc_id, response_time_data)?;
handler.add_heading(&doc_id, "System Reliability", 2)?;
handler.add_paragraph(&doc_id,
"System reliability metrics demonstrate exceptional stability and availability throughout the quarter.",
Some(TestStyles::basic()))?;
let reliability_data = TableData {
rows: vec![
vec!["Metric".to_string(), "Target".to_string(), "Actual".to_string(), "Status".to_string()],
vec!["Uptime".to_string(), "99.9%".to_string(), "99.97%".to_string(), "✓ Exceeded".to_string()],
vec!["MTBF (hours)".to_string(), "720".to_string(), "892".to_string(), "✓ Exceeded".to_string()],
vec!["Recovery Time (min)".to_string(), "15".to_string(), "8.5".to_string(), "✓ Exceeded".to_string()],
],
headers: Some(vec!["Metric".to_string(), "Target".to_string(), "Actual".to_string(), "Status".to_string()]),
border_style: Some("single".to_string()),
};
handler.add_table(&doc_id, reliability_data)?;
// Security Analysis
handler.add_heading(&doc_id, "Security Analysis", 1)?;
handler.add_paragraph(&doc_id,
"Security monitoring and incident response capabilities were significantly enhanced during Q4 2024.",
Some(TestStyles::basic()))?;
let security_improvements = vec![
"Implemented advanced threat detection algorithms".to_string(),
"Enhanced encryption protocols for data transmission".to_string(),
"Deployed automated incident response systems".to_string(),
"Conducted comprehensive security audits".to_string(),
"Updated access control mechanisms".to_string(),
];
handler.add_list(&doc_id, security_improvements, true)?;
// Recommendations
handler.add_heading(&doc_id, "Recommendations", 1)?;
handler.add_paragraph(&doc_id,
"Based on the analysis conducted, the following recommendations are proposed for Q1 2025:",
Some(TestStyles::basic()))?;
let recommendations = vec![
"Continue performance optimization initiatives".to_string(),
"Expand monitoring coverage to include new services".to_string(),
"Implement predictive analytics for proactive maintenance".to_string(),
"Enhance disaster recovery procedures".to_string(),
"Invest in additional security training for staff".to_string(),
];
handler.add_list(&doc_id, recommendations, true)?;
// Footer
handler.set_footer(&doc_id, "Technical Report Q4 2024 - Confidential")?;
Ok(doc_id)
}
/// Creates a meeting minutes document for testing
pub fn create_meeting_minutes(handler: &mut DocxHandler) -> Result<String> {
let doc_id = handler.create_document()?;
// Header
handler.add_heading(&doc_id, "Project Steering Committee Meeting Minutes", 1)?;
handler.add_paragraph(&doc_id, "", None)?;
// Meeting details
let meeting_details = TableData {
rows: vec![
vec!["Date:".to_string(), "December 15, 2024".to_string()],
vec!["Time:".to_string(), "2:00 PM - 3:30 PM PST".to_string()],
vec!["Location:".to_string(), "Conference Room A / Virtual".to_string()],
vec!["Chair:".to_string(), "Sarah Johnson".to_string()],
vec!["Secretary:".to_string(), "Mike Chen".to_string()],
],
headers: None,
border_style: Some("single".to_string()),
};
handler.add_table(&doc_id, meeting_details)?;
// Attendees
handler.add_heading(&doc_id, "Attendees", 2)?;
let attendees = vec![
"Sarah Johnson (Chair) - Project Director".to_string(),
"Mike Chen (Secretary) - Technical Lead".to_string(),
"Lisa Wang - Product Manager".to_string(),
"David Rodriguez - Engineering Manager".to_string(),
"Jennifer Kim - QA Manager".to_string(),
"Alex Thompson - DevOps Lead".to_string(),
];
handler.add_list(&doc_id, attendees, false)?;
// Agenda Items
handler.add_heading(&doc_id, "Agenda Items Discussed", 2)?;
handler.add_heading(&doc_id, "1. Project Status Update", 3)?;
handler.add_paragraph(&doc_id,
"Mike Chen presented the current project status, highlighting that development is 85% complete and on schedule for the January 31st deadline.",
Some(TestStyles::basic()))?;
let status_highlights = vec![
"Core functionality implementation: 100% complete".to_string(),
"User interface development: 90% complete".to_string(),
"Testing and QA: 70% complete".to_string(),
"Documentation: 60% complete".to_string(),
];
handler.add_list(&doc_id, status_highlights, false)?;
handler.add_heading(&doc_id, "2. Budget Review", 3)?;
handler.add_paragraph(&doc_id,
"Lisa Wang reported that the project is currently 5% under budget with strong cost controls in place.",
Some(TestStyles::basic()))?;
let budget_data = TableData {
rows: vec![
vec!["Category".to_string(), "Budgeted".to_string(), "Actual".to_string(), "Remaining".to_string()],
vec!["Development".to_string(), "$180,000".to_string(), "$168,000".to_string(), "$12,000".to_string()],
vec!["Testing".to_string(), "$45,000".to_string(), "$38,000".to_string(), "$7,000".to_string()],
vec!["Infrastructure".to_string(), "$30,000".to_string(), "$28,000".to_string(), "$2,000".to_string()],
vec!["Total".to_string(), "$255,000".to_string(), "$234,000".to_string(), "$21,000".to_string()],
],
headers: Some(vec!["Category".to_string(), "Budgeted".to_string(), "Actual".to_string(), "Remaining".to_string()]),
border_style: Some("single".to_string()),
};
handler.add_table(&doc_id, budget_data)?;
handler.add_heading(&doc_id, "3. Risk Assessment", 3)?;
handler.add_paragraph(&doc_id,
"David Rodriguez presented the updated risk register with mitigation strategies for identified risks.",
Some(TestStyles::basic()))?;
let risks = vec![
"Third-party API integration delays - Medium risk, mitigation plan in place".to_string(),
"Resource availability during holidays - Low risk, backup resources identified".to_string(),
"Performance requirements validation - Medium risk, load testing scheduled".to_string(),
];
handler.add_list(&doc_id, risks, false)?;
// Action Items
handler.add_heading(&doc_id, "Action Items", 2)?;
let action_items_data = TableData {
rows: vec![
vec!["Action Item".to_string(), "Owner".to_string(), "Due Date".to_string(), "Status".to_string()],
vec!["Complete load testing scenarios".to_string(), "Jennifer Kim".to_string(), "Dec 22, 2024".to_string(), "In Progress".to_string()],
vec!["Finalize API integration testing".to_string(), "Mike Chen".to_string(), "Dec 20, 2024".to_string(), "Not Started".to_string()],
vec!["Update project documentation".to_string(), "Lisa Wang".to_string(), "Jan 10, 2025".to_string(), "Not Started".to_string()],
vec!["Prepare deployment checklist".to_string(), "Alex Thompson".to_string(), "Jan 15, 2025".to_string(), "Not Started".to_string()],
],
headers: Some(vec!["Action Item".to_string(), "Owner".to_string(), "Due Date".to_string(), "Status".to_string()]),
border_style: Some("single".to_string()),
};
handler.add_table(&doc_id, action_items_data)?;
// Next Meeting
handler.add_heading(&doc_id, "Next Meeting", 2)?;
handler.add_paragraph(&doc_id,
"The next steering committee meeting is scheduled for January 5, 2025, at 2:00 PM PST in Conference Room A.",
Some(TestStyles::basic()))?;
// Footer
handler.set_footer(&doc_id, "Project Steering Committee - Meeting Minutes")?;
Ok(doc_id)
}
/// Creates a product specification document for testing
pub fn create_product_spec(handler: &mut DocxHandler) -> Result<String> {
let doc_id = handler.create_document()?;
// Title page
handler.add_paragraph(&doc_id, "", None)?;
handler.add_paragraph(&doc_id, "", None)?;
handler.add_heading(&doc_id, "Product Requirements Specification", 1)?;
handler.add_paragraph(&doc_id, "", None)?;
handler.add_paragraph(&doc_id, "Document Management System v2.0", Some(TestStyles::centered()))?;
handler.add_paragraph(&doc_id, "", None)?;
handler.add_paragraph(&doc_id, "Version 1.0", Some(TestStyles::centered()))?;
handler.add_paragraph(&doc_id, "December 15, 2024", Some(TestStyles::centered()))?;
handler.add_page_break(&doc_id)?;
// Table of Contents (simplified)
handler.add_heading(&doc_id, "Table of Contents", 1)?;
let toc_items = vec![
"1. Introduction".to_string(),
"2. System Overview".to_string(),
"3. Functional Requirements".to_string(),
"4. Non-Functional Requirements".to_string(),
"5. User Interface Requirements".to_string(),
"6. System Architecture".to_string(),
"7. Security Requirements".to_string(),
];
handler.add_list(&doc_id, toc_items, true)?;
// Introduction
handler.add_heading(&doc_id, "1. Introduction", 1)?;
handler.add_heading(&doc_id, "1.1 Purpose", 2)?;
handler.add_paragraph(&doc_id,
"This document specifies the requirements for the Document Management System version 2.0. The system is designed to provide comprehensive document storage, retrieval, and collaboration capabilities for enterprise users.",
Some(TestStyles::basic()))?;
handler.add_heading(&doc_id, "1.2 Scope", 2)?;
handler.add_paragraph(&doc_id,
"The Document Management System will support multiple file formats, version control, user collaboration, and advanced search capabilities. The system will be deployed as a web-based application with mobile support.",
Some(TestStyles::basic()))?;
// System Overview
handler.add_heading(&doc_id, "2. System Overview", 1)?;
handler.add_paragraph(&doc_id,
"The Document Management System consists of several integrated components working together to provide a seamless document management experience.",
Some(TestStyles::basic()))?;
let system_components = vec![
"Document Storage Engine".to_string(),
"Version Control System".to_string(),
"Search and Indexing Service".to_string(),
"User Authentication and Authorization".to_string(),
"Collaboration Tools".to_string(),
"Reporting and Analytics".to_string(),
];
handler.add_list(&doc_id, system_components, false)?;
// Functional Requirements
handler.add_heading(&doc_id, "3. Functional Requirements", 1)?;
handler.add_heading(&doc_id, "3.1 Document Upload and Storage", 2)?;
let upload_requirements = vec![
"FR-001: System shall support upload of files up to 100MB in size".to_string(),
"FR-002: System shall support common file formats (PDF, DOCX, XLSX, PPTX, TXT)".to_string(),
"FR-003: System shall automatically generate file metadata upon upload".to_string(),
"FR-004: System shall provide drag-and-drop upload functionality".to_string(),
];
handler.add_list(&doc_id, upload_requirements, false)?;
handler.add_heading(&doc_id, "3.2 Search and Retrieval", 2)?;
let search_requirements = vec![
"FR-005: System shall provide full-text search capabilities".to_string(),
"FR-006: System shall support advanced search with multiple criteria".to_string(),
"FR-007: System shall provide search result ranking and relevance scoring".to_string(),
"FR-008: System shall support search within specific document types".to_string(),
];
handler.add_list(&doc_id, search_requirements, false)?;
// Non-Functional Requirements
handler.add_heading(&doc_id, "4. Non-Functional Requirements", 1)?;
let nfr_data = TableData {
rows: vec![
vec!["Requirement".to_string(), "Specification".to_string(), "Priority".to_string()],
vec!["Performance".to_string(), "Page load time < 3 seconds".to_string(), "High".to_string()],
vec!["Scalability".to_string(), "Support 1000+ concurrent users".to_string(), "High".to_string()],
vec!["Availability".to_string(), "99.9% uptime".to_string(), "High".to_string()],
vec!["Security".to_string(), "Role-based access control".to_string(), "Critical".to_string()],
vec!["Usability".to_string(), "Intuitive interface, minimal training".to_string(), "Medium".to_string()],
],
headers: Some(vec!["Requirement".to_string(), "Specification".to_string(), "Priority".to_string()]),
border_style: Some("single".to_string()),
};
handler.add_table(&doc_id, nfr_data)?;
// Security Requirements
handler.add_heading(&doc_id, "7. Security Requirements", 1)?;
handler.add_paragraph(&doc_id,
"Security is paramount for the Document Management System. The following security measures must be implemented:",
Some(TestStyles::basic()))?;
let security_requirements = vec![
"SEC-001: All data transmission must use HTTPS/TLS 1.3".to_string(),
"SEC-002: User passwords must meet complexity requirements".to_string(),
"SEC-003: System must support multi-factor authentication".to_string(),
"SEC-004: All user actions must be logged for audit purposes".to_string(),
"SEC-005: Document access must be controlled by user permissions".to_string(),
"SEC-006: System must support data encryption at rest".to_string(),
];
handler.add_list(&doc_id, security_requirements, true)?;
// Footer
handler.set_footer(&doc_id, "Product Requirements Specification v1.0 - Confidential")?;
Ok(doc_id)
}
/// Creates a test document with international content
pub fn create_multilingual_document(handler: &mut DocxHandler) -> Result<String> {
let doc_id = handler.create_document()?;
handler.add_heading(&doc_id, "Multilingual Content Test Document", 1)?;
handler.add_paragraph(&doc_id,
"This document contains text in multiple languages to test internationalization and Unicode support.",
Some(TestStyles::basic()))?;
for (language, text) in TestContent::multilingual_content() {
handler.add_heading(&doc_id, language, 2)?;
handler.add_paragraph(&doc_id, text, Some(TestStyles::basic()))?;
handler.add_paragraph(&doc_id, "", None)?; // Empty line
}
handler.add_heading(&doc_id, "Special Characters and Symbols", 2)?;
handler.add_paragraph(&doc_id, TestContent::special_characters(), Some(TestStyles::basic()))?;
handler.add_paragraph(&doc_id, TestContent::symbols_and_math(), Some(TestStyles::basic()))?;
// Currency symbols
handler.add_paragraph(&doc_id, "Currency symbols: $ € £ ¥ ₹ ₽ ₩ ₪ ₫ ₱", Some(TestStyles::basic()))?;
// Emoji (if supported)
handler.add_paragraph(&doc_id, "Emoji test: 📄 📝 💼 🔒 🌍 ✅ ❌ ⚠️", Some(TestStyles::basic()))?;
Ok(doc_id)
}
/// Creates a document with complex formatting for testing
pub fn create_formatted_document(handler: &mut DocxHandler) -> Result<String> {
let doc_id = handler.create_document()?;
handler.add_heading(&doc_id, "Formatting Test Document", 1)?;
// Different paragraph styles
handler.add_paragraph(&doc_id, "This paragraph uses the default style.", Some(TestStyles::basic()))?;
handler.add_paragraph(&doc_id, "This paragraph uses bold formatting.", Some(DocxStyle {
bold: Some(true),
..TestStyles::basic()
}))?;
handler.add_paragraph(&doc_id, "This paragraph uses italic formatting.", Some(DocxStyle {
italic: Some(true),
..TestStyles::basic()
}))?;
handler.add_paragraph(&doc_id, "This paragraph is centered.", Some(TestStyles::centered()))?;
handler.add_paragraph(&doc_id, "This paragraph uses emphasis styling.", Some(TestStyles::emphasis()))?;
// Different font sizes
handler.add_heading(&doc_id, "Font Size Tests", 2)?;
for size in [8, 10, 12, 14, 16, 18, 24] {
let style = DocxStyle {
font_size: Some(size),
..TestStyles::basic()
};
handler.add_paragraph(&doc_id, &format!("This text is {} point size.", size), Some(style))?;
}
// Color tests
handler.add_heading(&doc_id, "Color Tests", 2)?;
let colors = vec![
("#000000", "Black"),
("#FF0000", "Red"),
("#00FF00", "Green"),
("#0000FF", "Blue"),
("#FF00FF", "Magenta"),
("#00FFFF", "Cyan"),
("#800080", "Purple"),
];
for (color_code, color_name) in colors {
let style = DocxStyle {
color: Some(color_code.to_string()),
..TestStyles::basic()
};
handler.add_paragraph(&doc_id, &format!("This text is in {}", color_name), Some(style))?;
}
// Alignment tests
handler.add_heading(&doc_id, "Alignment Tests", 2)?;
let alignments = vec![
("left", "Left aligned text"),
("center", "Center aligned text"),
("right", "Right aligned text"),
("justify", "Justified text that should span the full width of the line when there is enough content to make it meaningful"),
];
for (alignment, text) in alignments {
let style = DocxStyle {
alignment: Some(alignment.to_string()),
..TestStyles::basic()
};
handler.add_paragraph(&doc_id, text, Some(style))?;
}
// Complex table with formatting
handler.add_heading(&doc_id, "Formatted Table", 2)?;
let formatted_table = TableData {
rows: vec![
vec!["Item".to_string(), "Price".to_string(), "Discount".to_string(), "Final Price".to_string()],
vec!["Widget A".to_string(), "$100.00".to_string(), "10%".to_string(), "$90.00".to_string()],
vec!["Widget B".to_string(), "$150.00".to_string(), "15%".to_string(), "$127.50".to_string()],
vec!["Widget C".to_string(), "$200.00".to_string(), "20%".to_string(), "$160.00".to_string()],
vec!["Total".to_string(), "$450.00".to_string(), "".to_string(), "$377.50".to_string()],
],
headers: Some(vec!["Item".to_string(), "Price".to_string(), "Discount".to_string(), "Final Price".to_string()]),
border_style: Some("single".to_string()),
};
handler.add_table(&doc_id, formatted_table)?;
Ok(doc_id)
}
+392
View File
@@ -0,0 +1,392 @@
//! Test data generators and utilities
use serde_json::{json, Value};
use std::collections::HashMap;
/// Generates test data for various document types and scenarios
pub struct TestDataGenerator;
impl TestDataGenerator {
/// Generate test paragraphs with varying complexity
pub fn generate_paragraphs(count: usize, complexity: ParagraphComplexity) -> Vec<String> {
let base_sentences = match complexity {
ParagraphComplexity::Simple => vec![
"This is a simple sentence.",
"Another basic statement follows.",
"The text remains straightforward.",
"No complex structures here.",
"Plain language is used throughout.",
],
ParagraphComplexity::Medium => vec![
"This sentence demonstrates moderate complexity with additional clauses and descriptive elements.",
"Furthermore, the content includes various punctuation marks, numbers like 123, and technical terms.",
"The writing style incorporates both simple and compound sentence structures for variety.",
"Additionally, references to specific dates (December 15, 2024) and percentages (85%) are included.",
"These paragraphs simulate realistic document content found in business communications.",
],
ParagraphComplexity::Complex => vec![
"This comprehensive sentence exemplifies sophisticated linguistic structures, incorporating multiple subordinate clauses, technical terminology, and complex syntactical arrangements that challenge both human readers and automated processing systems.",
"Moreover, the content integrates diverse elements including numerical data (such as 42.7% improvement rates), temporal references (spanning Q3 2024 through Q1 2025), geographical locations (Silicon Valley, New York, London), and industry-specific jargon that reflects real-world document complexity.",
"The methodology employed in generating these test paragraphs considers various factors: readability indices, sentence length distribution, vocabulary diversity, and the inclusion of special characters (e.g., àáâãäå, €£¥, ∑∏∫) to ensure comprehensive testing coverage.",
"Consequently, these multi-faceted paragraphs serve as effective benchmarks for evaluating system performance under realistic conditions, while simultaneously providing sufficient content variation to identify potential edge cases and optimization opportunities.",
],
};
(0..count)
.map(|i| {
let sentence_count = match complexity {
ParagraphComplexity::Simple => 2 + (i % 3),
ParagraphComplexity::Medium => 3 + (i % 4),
ParagraphComplexity::Complex => 2 + (i % 3),
};
let mut paragraph = String::new();
for j in 0..sentence_count {
let sentence = &base_sentences[j % base_sentences.len()];
if j > 0 {
paragraph.push(' ');
}
paragraph.push_str(sentence);
}
paragraph
})
.collect()
}
/// Generate table data with specified dimensions and content type
pub fn generate_table_data(rows: usize, cols: usize, content_type: TableContentType) -> Vec<Vec<String>> {
let mut table_data = Vec::new();
// Generate header row
let headers: Vec<String> = (0..cols)
.map(|i| match content_type {
TableContentType::Generic => format!("Column {}", i + 1),
TableContentType::Financial => match i {
0 => "Period".to_string(),
1 => "Revenue".to_string(),
2 => "Expenses".to_string(),
3 => "Profit".to_string(),
_ => format!("Metric {}", i + 1),
},
TableContentType::Personnel => match i {
0 => "Name".to_string(),
1 => "Department".to_string(),
2 => "Role".to_string(),
3 => "Start Date".to_string(),
_ => format!("Field {}", i + 1),
},
TableContentType::Technical => match i {
0 => "Component".to_string(),
1 => "Version".to_string(),
2 => "Status".to_string(),
3 => "Last Updated".to_string(),
_ => format!("Attribute {}", i + 1),
},
})
.collect();
table_data.push(headers);
// Generate data rows
for row in 0..rows {
let row_data: Vec<String> = (0..cols)
.map(|col| match content_type {
TableContentType::Generic => format!("R{}C{}", row + 1, col + 1),
TableContentType::Financial => match col {
0 => format!("Q{} 2024", (row % 4) + 1),
1 => format!("${:.1}M", 100.0 + row as f64 * 12.5),
2 => format!("${:.1}M", 70.0 + row as f64 * 8.2),
3 => format!("${:.1}M", 30.0 + row as f64 * 4.3),
_ => format!("{:.1}%", 15.0 + row as f64 * 2.1),
},
TableContentType::Personnel => match col {
0 => format!("Employee {}", row + 1),
1 => ["Engineering", "Sales", "Marketing", "Operations"][(row % 4)].to_string(),
2 => ["Manager", "Developer", "Analyst", "Specialist"][(row % 4)].to_string(),
3 => format!("2024-{:02}-{:02}", ((row % 12) + 1), ((row % 28) + 1)),
_ => format!("Data {}", row + 1),
},
TableContentType::Technical => match col {
0 => format!("Component-{}", row + 1),
1 => format!("v{}.{}.{}", (row % 3) + 1, (row % 5), (row % 10)),
2 => ["Active", "Pending", "Deprecated", "Testing"][(row % 4)].to_string(),
3 => format!("2024-12-{:02}", ((row % 28) + 1)),
_ => format!("Value {}", row + 1),
},
})
.collect();
table_data.push(row_data);
}
table_data
}
/// Generate list items with specified count and category
pub fn generate_list_items(count: usize, category: ListCategory) -> Vec<String> {
let base_items = match category {
ListCategory::Tasks => vec![
"Complete project documentation",
"Review code changes and pull requests",
"Update system configuration files",
"Run comprehensive test suite",
"Deploy to staging environment",
"Conduct security audit",
"Optimize database performance",
"Update user interface components",
"Implement new feature requirements",
"Fix reported bugs and issues",
],
ListCategory::Features => vec![
"Advanced search and filtering capabilities",
"Real-time collaboration tools",
"Automated backup and recovery",
"Multi-language support",
"Mobile-responsive design",
"Integration with third-party services",
"Customizable dashboard and reports",
"Role-based access control",
"API for external integrations",
"Advanced analytics and insights",
],
ListCategory::Requirements => vec![
"System must support 1000+ concurrent users",
"Response time must be under 200ms for 95% of requests",
"Uptime must exceed 99.9% availability",
"Data must be encrypted both in transit and at rest",
"User interface must be accessible (WCAG 2.1 AA)",
"System must support multi-factor authentication",
"Backup processes must complete within 2 hours",
"Security patches must be applied within 24 hours",
"System must scale horizontally to handle peak loads",
"Audit logs must be maintained for minimum 7 years",
],
ListCategory::Benefits => vec![
"Increased operational efficiency by 35%",
"Reduced manual processing time by 60%",
"Improved data accuracy and consistency",
"Enhanced security and compliance posture",
"Better user experience and satisfaction",
"Lower total cost of ownership",
"Faster time-to-market for new features",
"Improved scalability and performance",
"Better decision-making through analytics",
"Reduced maintenance and support costs",
],
};
(0..count)
.map(|i| {
let base_item = &base_items[i % base_items.len()];
if count > base_items.len() {
format!("{} (item {})", base_item, i + 1)
} else {
base_item.clone()
}
})
.collect()
}
/// Generate realistic business data for testing
pub fn generate_business_data() -> BusinessDataSet {
BusinessDataSet {
companies: vec![
"Acme Corporation".to_string(),
"Global Tech Solutions".to_string(),
"Innovation Partners LLC".to_string(),
"Digital Dynamics Inc".to_string(),
"Future Systems Ltd".to_string(),
],
departments: vec![
"Engineering".to_string(),
"Sales & Marketing".to_string(),
"Human Resources".to_string(),
"Operations".to_string(),
"Finance & Accounting".to_string(),
"Research & Development".to_string(),
],
positions: vec![
"Software Engineer".to_string(),
"Product Manager".to_string(),
"Sales Representative".to_string(),
"Data Analyst".to_string(),
"Project Manager".to_string(),
"UX Designer".to_string(),
],
locations: vec![
"San Francisco, CA".to_string(),
"New York, NY".to_string(),
"Austin, TX".to_string(),
"Seattle, WA".to_string(),
"Boston, MA".to_string(),
"Chicago, IL".to_string(),
],
}
}
/// Generate MCP tool call test data
pub fn generate_mcp_test_calls() -> Vec<McpTestCall> {
vec![
McpTestCall {
tool_name: "create_document".to_string(),
args: json!({}),
expected_success: true,
expected_result_keys: vec!["success".to_string(), "document_id".to_string()],
},
McpTestCall {
tool_name: "add_paragraph".to_string(),
args: json!({
"document_id": "test-doc-id",
"text": "Test paragraph content"
}),
expected_success: true,
expected_result_keys: vec!["success".to_string()],
},
McpTestCall {
tool_name: "add_heading".to_string(),
args: json!({
"document_id": "test-doc-id",
"text": "Test Heading",
"level": 1
}),
expected_success: true,
expected_result_keys: vec!["success".to_string()],
},
McpTestCall {
tool_name: "extract_text".to_string(),
args: json!({
"document_id": "test-doc-id"
}),
expected_success: true,
expected_result_keys: vec!["success".to_string(), "text".to_string()],
},
McpTestCall {
tool_name: "get_metadata".to_string(),
args: json!({
"document_id": "test-doc-id"
}),
expected_success: true,
expected_result_keys: vec!["success".to_string(), "metadata".to_string()],
},
]
}
/// Generate performance test scenarios
pub fn generate_performance_scenarios() -> Vec<PerformanceScenario> {
vec![
PerformanceScenario {
name: "Small Document".to_string(),
paragraph_count: 10,
table_count: 1,
list_count: 2,
expected_max_time_ms: 1000,
},
PerformanceScenario {
name: "Medium Document".to_string(),
paragraph_count: 100,
table_count: 5,
list_count: 10,
expected_max_time_ms: 5000,
},
PerformanceScenario {
name: "Large Document".to_string(),
paragraph_count: 500,
table_count: 20,
list_count: 30,
expected_max_time_ms: 15000,
},
PerformanceScenario {
name: "Extra Large Document".to_string(),
paragraph_count: 1000,
table_count: 50,
list_count: 50,
expected_max_time_ms: 30000,
},
]
}
}
/// Complexity levels for generated paragraphs
#[derive(Debug, Clone)]
pub enum ParagraphComplexity {
Simple,
Medium,
Complex,
}
/// Content types for generated tables
#[derive(Debug, Clone)]
pub enum TableContentType {
Generic,
Financial,
Personnel,
Technical,
}
/// Categories for generated lists
#[derive(Debug, Clone)]
pub enum ListCategory {
Tasks,
Features,
Requirements,
Benefits,
}
/// Business data set for realistic testing
#[derive(Debug, Clone)]
pub struct BusinessDataSet {
pub companies: Vec<String>,
pub departments: Vec<String>,
pub positions: Vec<String>,
pub locations: Vec<String>,
}
/// MCP tool call test data
#[derive(Debug, Clone)]
pub struct McpTestCall {
pub tool_name: String,
pub args: Value,
pub expected_success: bool,
pub expected_result_keys: Vec<String>,
}
/// Performance test scenario data
#[derive(Debug, Clone)]
pub struct PerformanceScenario {
pub name: String,
pub paragraph_count: usize,
pub table_count: usize,
pub list_count: usize,
pub expected_max_time_ms: u64,
}
/// Utility functions for test data validation
pub struct TestDataValidator;
impl TestDataValidator {
/// Validate that text contains expected content
pub fn validate_text_content(text: &str, expected_keywords: &[&str]) -> bool {
expected_keywords.iter().all(|keyword| text.contains(keyword))
}
/// Validate table structure
pub fn validate_table_structure(rows: &[Vec<String>], expected_cols: usize) -> bool {
!rows.is_empty() && rows.iter().all(|row| row.len() == expected_cols)
}
/// Validate MCP response structure
pub fn validate_mcp_response(response: &Value, expected_keys: &[String]) -> bool {
expected_keys.iter().all(|key| response.get(key).is_some())
}
/// Generate hash for test data consistency checking
pub fn generate_content_hash(content: &str) -> u64 {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
content.hash(&mut hasher);
hasher.finish()
}
}