#[cfg(test)]
mod tests {
use crate::blocks::common::{BlockHeader, BlockParse};
use crate::blocks::conversion::base::ConversionBlock;
use crate::blocks::conversion::types::ConversionType;
#[test]
fn test_deep_conversion_chain_resolution() {
let mut file_data = Vec::new();
let root_conversion = create_test_conversion_block(
ConversionType::ValueToText,
vec![1.0, 2.0], vec![100, 200, 300], );
file_data.extend_from_slice(&root_conversion);
while file_data.len() < 100 {
file_data.push(0);
}
let first_ref_conversion = create_test_conversion_block(
ConversionType::ValueToText,
vec![1.0], vec![400], );
file_data.extend_from_slice(&first_ref_conversion);
while file_data.len() < 200 {
file_data.push(0);
}
let second_ref_conversion = create_test_conversion_block(
ConversionType::ValueToText,
vec![2.0], vec![500], );
file_data.extend_from_slice(&second_ref_conversion);
while file_data.len() < 300 {
file_data.push(0);
}
let default_conversion = create_test_conversion_block(
ConversionType::ValueToText,
vec![999.0], vec![600], );
file_data.extend_from_slice(&default_conversion);
for (offset, text) in [(400, "Text_1"), (500, "Text_2"), (600, "Default_Text")] {
while file_data.len() < offset {
file_data.push(0);
}
let text_block = create_test_text_block(text);
file_data.extend_from_slice(&text_block);
}
let mut root_conv = ConversionBlock::from_bytes(&file_data[0..]).unwrap();
let result = root_conv.resolve_all_dependencies(&file_data);
println!("Resolution result: {:?}", result);
assert!(result.is_ok(), "Deep chain resolution should succeed");
println!("Root conversion type: {:?}", root_conv.conversion_type);
println!("Root refs: {:?}", root_conv.refs);
println!("Root values: {:?}", root_conv.values);
println!("Resolved texts: {:?}", root_conv.resolved_texts.is_some());
println!(
"Resolved conversions: {:?}",
root_conv.resolved_conversions.is_some()
);
println!(
"Default conversion: {:?}",
root_conv.default_conversion.is_some()
);
assert!(
root_conv.resolved_conversions.is_some() || root_conv.default_conversion.is_some(),
"Should have resolved conversions or default conversion"
);
if let Some(resolved_conversions) = &root_conv.resolved_conversions {
for (idx, conv) in resolved_conversions.iter() {
println!(
"Resolved conversion {}: has texts={:?}, has conversions={:?}",
idx,
conv.resolved_texts.is_some(),
conv.resolved_conversions.is_some()
);
assert!(
conv.resolved_texts.is_some()
|| conv.resolved_conversions.is_some()
|| conv.default_conversion.is_some(),
"Nested conversion should have resolved dependencies"
);
}
}
if let Some(default_conv) = &root_conv.default_conversion {
println!(
"Default conversion: has texts={:?}, has conversions={:?}",
default_conv.resolved_texts.is_some(),
default_conv.resolved_conversions.is_some()
);
assert!(
default_conv.resolved_texts.is_some()
|| default_conv.resolved_conversions.is_some()
|| default_conv.default_conversion.is_some(),
"Default conversion should have resolved dependencies"
);
}
}
#[test]
fn test_conversion_chain_cycle_detection() {
let mut file_data = Vec::new();
while file_data.len() < 100 {
file_data.push(0);
}
let conv1 = create_test_conversion_block(
ConversionType::BitfieldText,
vec![1.0], vec![200], );
file_data.extend_from_slice(&conv1);
while file_data.len() < 200 {
file_data.push(0);
}
let conv2 = create_test_conversion_block(
ConversionType::BitfieldText,
vec![1.0], vec![100], );
file_data.extend_from_slice(&conv2);
let mut root_conv = ConversionBlock::from_bytes(&file_data[100..]).unwrap();
let result = root_conv.resolve_all_dependencies_with_address(&file_data, 100);
assert!(
result.is_err(),
"Cycle detection should catch the circular reference"
);
if let Err(crate::error::Error::ConversionChainCycle { address }) = result {
assert_eq!(address, 100, "Should identify the cyclic address");
} else {
panic!("Should return ConversionChainCycle error");
}
}
#[test]
fn test_conversion_chain_depth_limit() {
let mut file_data = Vec::new();
let mut current_offset = 0;
for i in 0..25 {
while file_data.len() < current_offset {
file_data.push(0);
}
let next_offset = if i < 24 { current_offset + 100 } else { 0 };
let conv = create_test_conversion_block(
ConversionType::ValueToText,
vec![1.0],
if next_offset > 0 {
vec![next_offset]
} else {
vec![]
},
);
file_data.extend_from_slice(&conv);
current_offset += 100;
}
let mut root_conv = ConversionBlock::from_bytes(&file_data[0..]).unwrap();
let result = root_conv.resolve_all_dependencies(&file_data);
assert!(result.is_err(), "Depth limit should be enforced");
if let Err(crate::error::Error::ConversionChainTooDeep { max_depth }) = result {
assert_eq!(max_depth, 20, "Should report the correct maximum depth");
} else {
panic!("Should return ConversionChainTooDeep error");
}
}
#[test]
fn test_default_conversion_resolution() {
let mut file_data = Vec::new();
let conv = create_test_conversion_block(
ConversionType::ValueToText,
vec![1.0, 2.0], vec![100, 200, 300], );
file_data.extend_from_slice(&conv);
for offset in [100, 200, 300] {
while file_data.len() < offset {
file_data.push(0);
}
let text_block = create_test_text_block(&format!("Text_{}", offset));
file_data.extend_from_slice(&text_block);
}
let mut root_conv = ConversionBlock::from_bytes(&file_data[0..]).unwrap();
let result = root_conv.resolve_all_dependencies(&file_data);
assert!(
result.is_ok(),
"Resolution with default conversion should succeed"
);
assert!(
root_conv.resolved_texts.is_some() || root_conv.default_conversion.is_some(),
"Should have resolved either texts or default conversion"
);
}
fn create_test_conversion_block(
conv_type: ConversionType,
values: Vec<f64>,
refs: Vec<usize>,
) -> Vec<u8> {
let mut block = Vec::new();
let header = BlockHeader {
id: "##CC".to_string(),
reserved: 0,
length: (24 + 4 * 8 + refs.len() * 8 + 8 + values.len() * 8) as u64,
link_count: (4 + refs.len()) as u64,
};
block.extend_from_slice(&header.to_bytes().unwrap());
for _ in 0..4 {
block.extend_from_slice(&0u64.to_le_bytes()); }
for &ref_addr in &refs {
block.extend_from_slice(&(ref_addr as u64).to_le_bytes());
}
block.push(conv_type.to_u8()); block.push(0); block.extend_from_slice(&0u16.to_le_bytes()); block.extend_from_slice(&(refs.len() as u16).to_le_bytes()); block.extend_from_slice(&(values.len() as u16).to_le_bytes());
for val in values {
block.extend_from_slice(&val.to_le_bytes());
}
block
}
fn create_test_text_block(text: &str) -> Vec<u8> {
let mut block = Vec::new();
let header = BlockHeader {
id: "##TX".to_string(),
reserved: 0,
length: (24 + text.len()) as u64,
link_count: 0,
};
block.extend_from_slice(&header.to_bytes().unwrap());
block.extend_from_slice(text.as_bytes());
block
}
}