use super::*;
use crate::messages::test_data::*;
use crate::builder::{DDEXBuilder, BuildOptions};
#[test]
fn test_update_generator_creation() {
let generator = UpdateGenerator::new();
assert_eq!(generator.operation_counter, 0);
let config = UpdateConfig::default();
let generator_with_config = UpdateGenerator::new_with_config(config);
assert_eq!(generator_with_config.operation_counter, 0);
}
#[test]
fn test_update_config_defaults() {
let config = UpdateConfig::default();
assert!(config.include_non_critical);
assert_eq!(config.max_operations_per_update, 1000);
assert!(config.validate_references);
assert!(config.optimize_references);
assert!(config.excluded_fields.contains("MessageId"));
assert!(config.excluded_fields.contains("MessageCreatedDateTime"));
}
#[test]
fn test_basic_update_generation() {
let mut generator = UpdateGenerator::new();
let original = r#"<Test><Title>Old Title</Title></Test>"#;
let updated = r#"<Test><Title>New Title</Title></Test>"#;
let result = generator.create_update(original, updated, "MSG-001");
assert!(result.is_ok());
let update = result.unwrap();
assert_eq!(update.update_metadata.original_message_id, "MSG-001");
assert!(!update.update_list.is_empty());
assert_eq!(update.update_metadata.validation_status, ValidationStatus::Pending);
}
#[test]
fn test_update_operations_generation() {
let mut generator = UpdateGenerator::new();
let mut changeset = crate::diff::types::ChangeSet::new();
changeset.add_change(crate::diff::types::SemanticChange {
path: crate::diff::types::DiffPath::root().with_element("Release").with_attribute("UPC"),
change_type: crate::diff::types::ChangeType::AttributeModified,
old_value: Some("123456789012".to_string()),
new_value: Some("987654321098".to_string()),
is_critical: true,
description: "UPC changed".to_string(),
});
let operations = generator.changeset_to_operations(&changeset).unwrap();
assert_eq!(operations.len(), 1);
assert_eq!(operations[0].action, UpdateAction::Replace);
assert_eq!(operations[0].entity_type, EntityType::Release);
assert!(operations[0].is_critical);
assert!(operations[0].old_value.is_some());
assert!(operations[0].new_value.is_some());
}
#[test]
fn test_update_action_validation() {
let generator = UpdateGenerator::new();
let add_operation = UpdateOperation {
operation_id: "OP000001".to_string(),
action: UpdateAction::Add,
target_path: "/Release/Title".to_string(),
entity_type: EntityType::Release,
entity_id: "release-001".to_string(),
old_value: None,
new_value: Some("New Title".to_string()),
is_critical: false,
description: "Add title".to_string(),
dependencies: Vec::new(),
};
let update = create_test_update_message(vec![add_operation.clone()]);
assert!(generator.validate_operation(&add_operation, &update).is_ok());
let invalid_add = UpdateOperation {
new_value: None,
..add_operation.clone()
};
let invalid_update = create_test_update_message(vec![invalid_add.clone()]);
assert!(generator.validate_operation(&invalid_add, &invalid_update).is_err());
let delete_operation = UpdateOperation {
action: UpdateAction::Delete,
old_value: Some("Old Title".to_string()),
new_value: None,
..add_operation.clone()
};
let delete_update = create_test_update_message(vec![delete_operation.clone()]);
assert!(generator.validate_operation(&delete_operation, &delete_update).is_ok());
let replace_operation = UpdateOperation {
action: UpdateAction::Replace,
old_value: Some("Old Title".to_string()),
new_value: Some("New Title".to_string()),
..add_operation
};
let replace_update = create_test_update_message(vec![replace_operation.clone()]);
assert!(generator.validate_operation(&replace_operation, &replace_update).is_ok());
}
#[test]
fn test_entity_type_determination() {
let generator = UpdateGenerator::new();
let resource_path = crate::diff::types::DiffPath::root()
.with_element("ResourceList")
.with_element("SoundRecording");
let release_path = crate::diff::types::DiffPath::root()
.with_element("ReleaseList")
.with_element("Release");
let deal_path = crate::diff::types::DiffPath::root()
.with_element("DealList")
.with_element("Deal");
assert_eq!(generator.determine_entity_type(&resource_path), EntityType::Resource);
assert_eq!(generator.determine_entity_type(&release_path), EntityType::Release);
assert_eq!(generator.determine_entity_type(&deal_path), EntityType::Deal);
}
#[test]
fn test_dependency_validation() {
let generator = UpdateGenerator::new();
let op1 = create_test_operation("OP001", UpdateAction::Add);
let op2 = UpdateOperation {
operation_id: "OP002".to_string(),
dependencies: vec!["OP001".to_string()],
..create_test_operation("OP002", UpdateAction::Replace)
};
let operations = vec![op1, op2];
assert!(generator.validate_dependencies(&operations).is_ok());
let op3 = UpdateOperation {
operation_id: "OP003".to_string(),
dependencies: vec!["OP999".to_string()],
..create_test_operation("OP003", UpdateAction::Replace)
};
let invalid_operations = vec![op3];
assert!(generator.validate_dependencies(&invalid_operations).is_err());
}
#[test]
fn test_conflict_detection() {
let generator = UpdateGenerator::new();
let op1 = UpdateOperation {
operation_id: "OP001".to_string(),
target_path: "/Release/Title".to_string(),
..create_test_operation("OP001", UpdateAction::Replace)
};
let op2 = UpdateOperation {
operation_id: "OP002".to_string(),
target_path: "/Release/Title".to_string(),
..create_test_operation("OP002", UpdateAction::Replace)
};
let operations = vec![op1, op2];
let conflicts = generator.detect_conflicts(&operations).unwrap();
assert_eq!(conflicts.len(), 1);
assert!(conflicts[0].contains("conflicting operations"));
}
#[test]
fn test_reference_validation() {
let generator = UpdateGenerator::new();
let resource_operation = UpdateOperation {
entity_type: EntityType::Resource,
entity_id: "resource-001".to_string(),
..create_test_operation("OP001", UpdateAction::Add)
};
let resource_update = ResourceUpdate {
resource_id: "resource-001".to_string(),
resource_reference: "R001".to_string(),
action: UpdateAction::Add,
resource_data: None,
technical_updates: Vec::new(),
metadata_updates: IndexMap::new(),
};
let mut resource_updates = IndexMap::new();
resource_updates.insert("resource-001".to_string(), resource_update);
let update = UpdateReleaseMessage {
header: create_test_header(),
update_list: vec![resource_operation],
resource_updates,
release_updates: IndexMap::new(),
deal_updates: IndexMap::new(),
update_metadata: create_test_metadata(),
};
assert!(generator.validate_references(&update).is_ok());
}
#[test]
fn test_update_serialization() {
let builder = DDEXBuilder::new();
let update = create_test_update_message(vec![
create_test_operation("OP001", UpdateAction::Add),
create_test_operation("OP002", UpdateAction::Replace),
]);
let xml_result = builder.serialize_update(&update);
assert!(xml_result.is_ok());
let xml = xml_result.unwrap();
assert!(xml.contains("<?xml version=\"1.0\""));
assert!(xml.contains("<UpdateReleaseMessage"));
assert!(xml.contains("<UpdateList>"));
assert!(xml.contains("<UpdateOperation>"));
assert!(xml.contains("<OperationId>OP001</OperationId>"));
assert!(xml.contains("<Action>Add</Action>"));
}
#[test]
fn test_update_builder_integration() {
let builder = DDEXBuilder::new();
let original = r#"<Test><Title>Original</Title></Test>"#;
let updated = r#"<Test><Title>Updated</Title></Test>"#;
let update_result = builder.create_update(original, updated, "MSG-001");
assert!(update_result.is_ok());
let update = update_result.unwrap();
assert_eq!(update.update_metadata.original_message_id, "MSG-001");
let validation_result = builder.validate_update(&update);
assert!(validation_result.is_ok());
let serialization_result = builder.serialize_update(&update);
assert!(serialization_result.is_ok());
let xml = serialization_result.unwrap();
assert!(xml.contains("<UpdateReleaseMessage"));
let apply_result = builder.apply_update(original, &update);
}
#[test]
fn test_update_config_customization() {
let mut config = UpdateConfig::default();
config.include_non_critical = false;
config.max_operations_per_update = 100;
config.excluded_fields.insert("CustomField".to_string());
let generator = UpdateGenerator::new_with_config(config.clone());
assert_eq!(generator.config.max_operations_per_update, 100);
assert!(!generator.config.include_non_critical);
assert!(generator.config.excluded_fields.contains("CustomField"));
}
#[test]
fn test_validation_status_display() {
assert_eq!(ValidationStatus::Validated.to_string(), "Validated");
assert_eq!(ValidationStatus::WarningsOnly.to_string(), "Warnings Only");
assert_eq!(ValidationStatus::Invalid.to_string(), "Invalid");
assert_eq!(ValidationStatus::Pending.to_string(), "Pending");
}
#[test]
fn test_update_action_display() {
assert_eq!(UpdateAction::Add.to_string(), "Add");
assert_eq!(UpdateAction::Delete.to_string(), "Delete");
assert_eq!(UpdateAction::Replace.to_string(), "Replace");
assert_eq!(UpdateAction::Move.to_string(), "Move");
}
#[test]
fn test_entity_type_display() {
assert_eq!(EntityType::Resource.to_string(), "Resource");
assert_eq!(EntityType::Release.to_string(), "Release");
assert_eq!(EntityType::Deal.to_string(), "Deal");
assert_eq!(EntityType::Party.to_string(), "Party");
assert_eq!(EntityType::Metadata.to_string(), "Metadata");
}
#[cfg(test)]
mod integration_tests {
use super::*;
#[test]
fn test_real_ddex_update_generation() {
let mut generator = UpdateGenerator::new();
let result = generator.create_update(
SAMPLE_DDEX_V1,
SAMPLE_DDEX_V2_UPDATES,
"MSG-ORIGINAL-001"
);
assert!(result.is_ok());
let update = result.unwrap();
assert!(!update.update_list.is_empty());
assert_eq!(update.update_metadata.original_message_id, "MSG-ORIGINAL-001");
assert!(update.update_metadata.total_operations > 0);
}
#[test]
fn test_critical_changes_detection() {
let mut generator = UpdateGenerator::new();
let result = generator.create_update(
SAMPLE_DDEX_V1,
SAMPLE_DDEX_V3_CRITICAL,
"MSG-ORIGINAL-001"
);
assert!(result.is_ok());
let update = result.unwrap();
assert_eq!(update.update_metadata.impact_level, "High");
let critical_operations: Vec<_> = update.update_list.iter()
.filter(|op| op.is_critical)
.collect();
}
#[test]
fn test_update_round_trip() {
let builder = DDEXBuilder::new();
let update_result = builder.create_update(
SAMPLE_DDEX_V1,
SAMPLE_DDEX_V2_UPDATES,
"MSG-ORIGINAL-001"
);
if let Ok(update) = update_result {
let validation = builder.validate_update(&update);
assert!(validation.is_ok());
let xml_result = builder.serialize_update(&update);
assert!(xml_result.is_ok());
let xml = xml_result.unwrap();
assert!(xml.len() > 100); assert!(xml.contains("<UpdateReleaseMessage"));
assert!(xml.contains("</UpdateReleaseMessage>"));
let apply_result = builder.apply_update(SAMPLE_DDEX_V1, &update);
}
}
}
fn create_test_operation(id: &str, action: UpdateAction) -> UpdateOperation {
UpdateOperation {
operation_id: id.to_string(),
action,
target_path: "/Test/Element".to_string(),
entity_type: EntityType::Metadata,
entity_id: "test-entity".to_string(),
old_value: Some("old".to_string()),
new_value: Some("new".to_string()),
is_critical: false,
description: format!("Test operation {}", id),
dependencies: Vec::new(),
}
}
fn create_test_update_message(operations: Vec<UpdateOperation>) -> UpdateReleaseMessage {
UpdateReleaseMessage {
header: create_test_header(),
update_list: operations,
resource_updates: IndexMap::new(),
release_updates: IndexMap::new(),
deal_updates: IndexMap::new(),
update_metadata: create_test_metadata(),
}
}
fn create_test_header() -> MessageHeaderRequest {
MessageHeaderRequest {
message_id: Some("TEST-UPDATE-001".to_string()),
message_sender: crate::builder::PartyRequest {
party_name: vec![crate::builder::LocalizedStringRequest {
text: "Test Sender".to_string(),
language_code: None,
}],
party_id: None,
party_reference: None,
},
message_recipient: crate::builder::PartyRequest {
party_name: vec![crate::builder::LocalizedStringRequest {
text: "Test Recipient".to_string(),
language_code: None,
}],
party_id: None,
party_reference: None,
},
message_control_type: Some("UpdateMessage".to_string()),
message_created_date_time: Some(chrono::Utc::now().to_rfc3339()),
}
}
fn create_test_metadata() -> UpdateMetadata {
UpdateMetadata {
original_message_id: "MSG-ORIGINAL-001".to_string(),
original_message_version: None,
original_message_timestamp: None,
update_created_timestamp: chrono::Utc::now(),
update_sequence: 1,
total_operations: 1,
impact_level: "Low".to_string(),
validation_status: ValidationStatus::Pending,
custom_metadata: IndexMap::new(),
}
}