#[cfg(test)]
mod milestone1_tests {
use std::fs;
use std::path::Path;
#[test]
fn can_download_or_load_cached_json() {
assert!(Path::new("test_blocks_data.json").exists());
}
#[test]
fn json_has_expected_structure() {
let json_data = fs::read_to_string("test_blocks_data.json").unwrap();
let json: serde_json::Value = serde_json::from_str(&json_data).unwrap();
assert!(json.is_object());
assert!(json.get("blocks").is_some());
assert!(json["blocks"].as_object().unwrap().len() >= 3); }
#[test]
fn sample_blocks_have_required_fields() {
let json_data = fs::read_to_string("test_blocks_data.json").unwrap();
let blocks = serde_json::from_str::<serde_json::Value>(&json_data).unwrap();
let stone = &blocks["blocks"]["minecraft:stone"];
assert!(stone.get("properties").is_some());
assert!(stone.get("default_state").is_some());
}
#[test]
fn can_parse_block_properties() {
let json_data = fs::read_to_string("test_blocks_data.json").unwrap();
let blocks = serde_json::from_str::<serde_json::Value>(&json_data).unwrap();
let repeater = &blocks["blocks"]["minecraft:repeater"];
let props = repeater["properties"].as_object().unwrap();
assert!(props.contains_key("delay"));
assert_eq!(props["delay"], serde_json::json!(["1", "2", "3", "4"]));
}
}
#[cfg(test)]
mod milestone7_tests {
use crate::{errors::*, queries::validated::*};
#[test]
fn validation_rejects_invalid_block_ids() {
let result = validation::validate_block_id("");
assert!(result.is_err());
let result = validation::validate_block_id("minecraft:invalid!block");
assert!(result.is_err());
let result = validation::validate_block_id("minecraft:valid_block");
assert!(result.is_ok());
}
#[test]
fn validation_rejects_invalid_property_names() {
let result = validation::validate_property_name("");
assert!(result.is_err());
let result = validation::validate_property_name("invalid-property!");
assert!(result.is_err());
let result = validation::validate_property_name("valid_property");
assert!(result.is_ok());
}
#[test]
fn error_types_display_correctly() {
let error = BlockpediaError::block_not_found("test:block");
let error_string = error.to_string();
assert!(error_string.contains("Block error"));
assert!(error_string.contains("test:block"));
let error = BlockpediaError::invalid_property_value(
"test:block",
"prop",
"value",
vec!["valid1".to_string(), "valid2".to_string()],
);
let error_string = error.to_string();
assert!(error_string.contains("Property error"));
assert!(error_string.contains("Invalid value"));
}
#[test]
fn safe_query_functions_validate_input() {
let result = find_blocks_by_property_safe("invalid-prop!", "value");
assert!(result.is_err());
let result = search_blocks_safe("");
assert!(result.is_err());
let result = find_blocks_by_property_safe("delay", "1");
assert!(result.is_ok());
}
#[test]
fn error_recovery_provides_suggestions() {
let suggestions = recovery::suggest_similar_blocks("stone");
assert!(!suggestions.is_empty());
assert!(suggestions.iter().any(|s| s.contains("minecraft:stone")));
let suggestions = recovery::suggest_property_values(
"delay",
"5",
&[
"1".to_string(),
"2".to_string(),
"3".to_string(),
"4".to_string(),
],
);
assert!(!suggestions.is_empty());
}
#[test]
fn comprehensive_validation_catches_multiple_errors() {
let properties = vec![
("delay".to_string(), "5".to_string()), ("invalid_prop".to_string(), "value".to_string()), ];
let result = validate_block_properties_safe("minecraft:repeater", &properties);
assert!(result.is_err());
if let Err(BlockpediaError::State(StateError::ValidationFailed { errors, .. })) = result {
assert!(errors.len() >= 2); } else {
panic!("Expected ValidationFailed error with multiple errors");
}
}
#[test]
fn safe_block_state_creation_with_helpful_errors() {
let properties = vec![
("delay".to_string(), "3".to_string()), ("facing".to_string(), "invalid_direction".to_string()), ];
let result = create_block_state_safe("minecraft:repeater", &properties);
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("Invalid value") || error_msg.contains("ValidationFailed"));
}
#[test]
fn parse_error_recovery_fixes_common_mistakes() {
let broken_input = "minecraft:repeater delay=3,facing=north";
let fixed = recovery::fix_common_parse_errors(broken_input);
assert_ne!(broken_input, fixed);
}
#[test]
fn structured_errors_are_comparable() {
let error1 = BlockpediaError::block_not_found("test:block");
let error2 = BlockpediaError::block_not_found("test:block");
let error3 = BlockpediaError::block_not_found("other:block");
assert_eq!(error1, error2);
assert_ne!(error1, error3);
}
#[test]
fn query_timeout_validation() {
let result = query_with_timeout("valid_query", || "result");
assert!(result.is_ok());
let very_long_name = "a".repeat(100);
let result = query_with_timeout(&very_long_name, || "result");
assert!(result.is_err());
}
}
#[cfg(test)]
mod milestone6_tests {
use crate::BLOCKS;
#[test]
fn fetcher_framework_provides_extra_data() {
let stone = BLOCKS.get("minecraft:stone").unwrap();
assert_eq!(stone.extras.mock_data, Some(42));
assert!(stone.extras.color.is_some());
let color = stone.extras.color.unwrap();
assert_eq!(color.rgb, [125, 125, 125]);
assert!((color.oklab[0] - 0.49).abs() < 0.01); }
#[test]
fn fetcher_data_varies_by_block() {
let stone = BLOCKS.get("minecraft:stone").unwrap();
let dirt = BLOCKS.get("minecraft:dirt").unwrap();
assert_eq!(stone.extras.mock_data, Some(42));
assert_eq!(dirt.extras.mock_data, Some(123));
let stone_color = stone.extras.color.unwrap();
let dirt_color = dirt.extras.color.unwrap();
assert_ne!(stone_color.rgb, dirt_color.rgb);
}
#[test]
fn blocks_without_fetcher_data_have_none() {
let repeater = BLOCKS.get("minecraft:repeater").unwrap();
assert_eq!(repeater.extras.mock_data, None);
assert!(repeater.extras.color.is_none());
}
#[test]
fn generated_color_queries_work() {
let closest = crate::BlockFacts::closest_to_color([125, 125, 125]);
assert!(closest.is_some());
let closest_block = closest.unwrap();
assert!(closest_block.id().contains("minecraft:"));
assert!(closest_block.extras.color.is_some());
}
#[test]
fn color_range_query_works() {
let similar_blocks = crate::BlockFacts::blocks_in_color_range([100, 100, 100], 100.0);
assert!(!similar_blocks.is_empty());
let block_ids: Vec<_> = similar_blocks.iter().map(|b| b.id()).collect();
assert!(block_ids.contains(&"minecraft:stone"));
assert!(block_ids.contains(&"minecraft:dirt"));
}
#[test]
fn extras_struct_has_proper_defaults() {
let default_extras = crate::Extras::default();
assert_eq!(default_extras.mock_data, None);
assert!(default_extras.color.is_none());
let new_extras = crate::Extras::new();
assert_eq!(new_extras.mock_data, None);
assert!(new_extras.color.is_none());
}
}
#[cfg(test)]
mod milestone5_tests {
use crate::queries::*;
#[test]
fn enhanced_block_families_logical() {
let families = get_enhanced_block_families();
assert!(!families.is_empty());
for blocks in families.values() {
assert!(!blocks.is_empty());
}
}
#[test]
fn blocks_with_complex_properties_work() {
let requirements = vec![("delay".to_string(), vec!["2".to_string()])];
let results: Vec<_> = blocks_with_complex_properties(&requirements).collect();
assert!(!results.is_empty());
}
#[test]
fn analyze_property_correlation_accuracy() {
let correlations = analyze_property_correlation();
assert!(!correlations.is_empty());
}
#[test]
fn find_similar_blocks_effective() {
let similar_blocks = find_similar_blocks("minecraft:repeater", 1);
let non_existent = find_similar_blocks("minecraft:nonexistent", 1);
assert!(non_existent.is_empty());
for (block, shared_properties) in similar_blocks {
println!(
"Block: {}, Shared Properties: {}",
block.id(),
shared_properties
);
assert!(shared_properties >= 1); }
}
#[test]
fn get_advanced_property_stats_correct() {
let stats = get_advanced_property_stats();
assert!(stats.basic_stats.total_unique_properties > 0);
println!(
"Most diverse property: {} with {} values",
stats.most_diverse_property.0, stats.most_diverse_property.1
);
println!(
"Most correlated properties: {:?}",
stats.most_correlated_properties
);
}
}
#[cfg(test)]
mod milestone2_tests {
use crate::{BlockState, BLOCKS};
#[test]
fn phf_table_loads_successfully() {
assert!(BLOCKS.len() >= 3);
assert!(BLOCKS.get("minecraft:stone").is_some());
}
#[test]
fn block_facts_has_expected_data() {
let stone = BLOCKS.get("minecraft:stone").unwrap();
assert_eq!(stone.id(), "minecraft:stone");
assert!(stone.properties().is_empty());
let repeater = BLOCKS.get("minecraft:repeater").unwrap();
assert!(repeater.has_property("delay"));
assert_eq!(
repeater.get_property_values("delay"),
Some(vec![
"1".to_string(),
"2".to_string(),
"3".to_string(),
"4".to_string()
])
);
}
#[test]
fn block_state_creates_successfully() {
let state = BlockState::new("minecraft:stone").unwrap();
assert_eq!(state.to_string(), "minecraft:stone");
let state = BlockState::new("nonexistent:block");
assert!(state.is_err());
}
#[test]
fn block_state_with_properties() {
let state = BlockState::new("minecraft:repeater")
.unwrap()
.with("delay", "3")
.unwrap();
assert!(state.to_string().contains("delay=3"));
}
}
#[cfg(test)]
mod milestone3_tests {
use crate::{BlockState, BLOCKS};
#[test]
fn valid_properties_accepted() {
let valid = BlockState::new("minecraft:repeater")
.unwrap()
.with("delay", "2")
.unwrap()
.with("facing", "north")
.unwrap();
assert!(valid.to_string().contains("delay=2"));
assert!(valid.to_string().contains("facing=north"));
}
#[test]
fn invalid_property_values_rejected() {
let result = BlockState::new("minecraft:repeater")
.unwrap()
.with("delay", "5"); assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("Invalid value '5'"));
let result = BlockState::new("minecraft:repeater")
.unwrap()
.with("nonexistent_prop", "value");
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("does not have property") || error_msg.contains("not found"));
}
#[test]
fn property_case_sensitivity() {
let result = BlockState::new("minecraft:repeater")
.unwrap()
.with("Delay", "1"); assert!(result.is_err());
let result = BlockState::new("minecraft:repeater")
.unwrap()
.with("facing", "North"); assert!(result.is_err());
}
#[test]
fn default_state_roundtrip() {
let block = BLOCKS.get("minecraft:repeater").unwrap();
let default_state = BlockState::from_default(block).unwrap();
let rebuilt = BlockState::parse(&default_state.to_string()).unwrap();
assert_eq!(default_state.to_string(), rebuilt.to_string());
}
#[test]
fn unknown_block_rejected() {
let result = BlockState::new("minecraft:nonexistent_block");
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("not found"));
}
#[test]
fn parse_blockstate_string() {
let state = BlockState::parse("minecraft:stone").unwrap();
assert_eq!(state.to_string(), "minecraft:stone");
let state = BlockState::parse("minecraft:repeater[delay=3,facing=south]").unwrap();
assert!(state.to_string().contains("delay=3"));
assert!(state.to_string().contains("facing=south"));
let result = BlockState::parse("minecraft:repeater[delay=3,facing=south"); assert!(result.is_err());
}
}
#[cfg(test)]
mod milestone4_tests {
use crate::{queries::*, BLOCKS};
#[test]
fn find_blocks_by_property_works() {
let _waterloggable: Vec<_> = find_blocks_by_property("waterlogged", "true").collect();
let delay_blocks: Vec<_> = find_blocks_by_property("delay", "1").collect();
assert!(delay_blocks.iter().any(|b| b.id() == "minecraft:repeater"));
}
#[test]
fn find_blocks_matching_predicate() {
let complex_redstone: Vec<_> = find_blocks_matching(|block| {
block.has_property("powered") && block.has_property("facing")
})
.collect();
assert!(complex_redstone
.iter()
.any(|b| b.id() == "minecraft:repeater"));
}
#[test]
fn search_blocks_pattern_matching() {
let stone_blocks: Vec<_> = search_blocks("stone").collect();
assert!(stone_blocks.iter().any(|b| b.id() == "minecraft:stone"));
let minecraft_blocks: Vec<_> = search_blocks("minecraft:*").collect();
assert!(minecraft_blocks.len() >= 3); }
#[test]
fn get_property_values_comprehensive() {
let delay_values = get_property_values("delay").unwrap();
assert_eq!(delay_values, vec!["1", "2", "3", "4"]);
let facing_values = get_property_values("facing").unwrap();
assert!(facing_values.contains(&"north".to_string()));
assert!(facing_values.len() >= 4); }
#[test]
fn count_blocks_where_accurate() {
let total_blocks = BLOCKS.len();
let counted = count_blocks_where(|_| true);
assert_eq!(total_blocks, counted);
let blocks_with_properties = count_blocks_where(|b| !b.properties.is_empty());
assert!(blocks_with_properties >= 1); }
#[test]
fn get_block_families_logical() {
let families = get_block_families();
assert!(!families.is_empty());
for blocks in families.values() {
assert!(!blocks.is_empty());
}
}
#[test]
fn blocks_with_properties_works() {
let results: Vec<_> = blocks_with_properties(&[
("delay", "*"), ("facing", "north"), ])
.collect();
assert!(!results.is_empty());
for block in results {
assert!(block.has_property("delay"));
assert!(block.has_property("facing"));
}
}
#[test]
fn find_rare_properties_works() {
let rare = find_rare_properties(0.5);
assert!(!rare.is_empty());
for count in rare.values() {
assert!(*count > 0);
}
}
#[test]
fn property_statistics_accurate() {
let stats = get_property_stats();
assert!(stats.total_unique_properties > 0);
assert!(stats.most_common_property.1 > 0); assert!(stats.blocks_with_no_properties >= 1); assert!(stats.average_properties_per_block >= 0.0);
}
}
#[cfg(test)]
mod bedrock_translation_tests {
use crate::{BlockState, BLOCKS};
use std::collections::HashMap;
#[test]
fn redstone_wire_java_to_bedrock() {
if let Some(java_facts) = BLOCKS.get("minecraft:redstone_wire") {
let java_state = BlockState::from_default(java_facts)
.unwrap()
.with("power", "15")
.unwrap();
let bedrock_state = java_state.to_bedrock().unwrap();
assert_eq!(bedrock_state.id(), "minecraft:redstone_wire");
assert_eq!(bedrock_state.get_property("redstone_signal"), Some("15"));
}
}
#[test]
fn redstone_wire_bedrock_to_java() {
let java_facts = BLOCKS
.values()
.find(|f| f.id == "minecraft:redstone_wire" && f.extras.bedrock.is_some());
if java_facts.is_some() {
let mut bedrock_props = HashMap::new();
bedrock_props.insert("redstone_signal".to_string(), "15".to_string());
let java_state =
BlockState::from_bedrock("minecraft:redstone_wire", bedrock_props).unwrap();
assert_eq!(java_state.id(), "minecraft:redstone_wire");
assert_eq!(java_state.get_property("power"), Some("15"));
}
}
#[test]
fn repeater_java_to_bedrock_facing() {
if let Some(java_facts) = BLOCKS.get("minecraft:repeater") {
let java_state = BlockState::from_default(java_facts)
.unwrap()
.with("facing", "north")
.unwrap()
.with("powered", "false")
.unwrap();
if java_facts.extras.bedrock.is_some() {
let bedrock_state = java_state.to_bedrock().unwrap();
assert_eq!(bedrock_state.id(), "minecraft:unpowered_repeater");
assert_eq!(
bedrock_state.get_property("minecraft:cardinal_direction"),
Some("north")
);
}
}
}
#[test]
fn powered_repeater_java_to_bedrock() {
if let Some(java_facts) = BLOCKS.get("minecraft:repeater") {
let java_state = BlockState::from_default(java_facts)
.unwrap()
.with("powered", "true")
.unwrap();
if java_facts.extras.bedrock.is_some() {
let bedrock_state = java_state.to_bedrock().unwrap();
assert_eq!(bedrock_state.id(), "minecraft:powered_repeater");
}
}
}
#[test]
fn wall_torch_java_to_bedrock() {
if let Some(java_facts) = BLOCKS.get("minecraft:wall_torch") {
let java_state = BlockState::from_default(java_facts)
.unwrap()
.with("facing", "north")
.unwrap();
if java_facts.extras.bedrock.is_some() {
let bedrock_state = java_state.to_bedrock().unwrap();
assert_eq!(bedrock_state.id(), "minecraft:torch");
assert_eq!(
bedrock_state.get_property("torch_facing_direction"),
Some("south")
);
}
}
}
#[test]
fn bedrock_torch_to_java() {
let mut props = HashMap::new();
props.insert("torch_facing_direction".to_string(), "south".to_string());
let java_state = BlockState::from_bedrock("minecraft:torch", props).unwrap();
assert_eq!(java_state.id(), "minecraft:wall_torch");
assert_eq!(java_state.get_property("facing"), Some("north"));
let mut props = HashMap::new();
props.insert("torch_facing_direction".to_string(), "top".to_string());
let java_state = BlockState::from_bedrock("minecraft:torch", props).unwrap();
assert_eq!(java_state.id(), "minecraft:torch");
}
#[test]
fn stairs_java_to_bedrock_half() {
if let Some(java_facts) = BLOCKS.get("minecraft:oak_stairs") {
let java_state = BlockState::from_default(java_facts)
.unwrap()
.with("half", "top")
.unwrap();
if java_facts.extras.bedrock.is_some() {
let bedrock_state = java_state.to_bedrock().unwrap();
assert_eq!(bedrock_state.get_property("upside_down_bit"), Some("true"));
}
}
}
#[test]
fn door_java_to_bedrock_half() {
if let Some(java_facts) = BLOCKS.get("minecraft:oak_door") {
let java_state = BlockState::from_default(java_facts)
.unwrap()
.with("half", "upper")
.unwrap();
if java_facts.extras.bedrock.is_some() {
let bedrock_state = java_state.to_bedrock().unwrap();
assert_eq!(bedrock_state.get_property("upper_block_bit"), Some("true"));
}
}
}
fn java_state(id: &str, props: &[(&str, &str)]) -> Option<BlockState> {
let facts = BLOCKS.get(id)?;
if facts.extras.bedrock.is_none() {
return None;
}
let mut s = BlockState::from_default(facts).ok()?;
for (k, v) in props {
s = s.with(k, v).ok()?;
}
Some(s)
}
#[test]
fn hopper_java_to_bedrock_facing_north() {
if let Some(j) = java_state("minecraft:hopper", &[("facing", "north")]) {
let b = j.to_bedrock().unwrap();
assert_eq!(b.id(), "minecraft:hopper");
assert_eq!(
b.get_property("facing_direction"),
Some("2"),
"hopper north -> facing_direction=2; got {:?}",
b.properties()
);
}
}
#[test]
fn hopper_java_to_bedrock_all_facings() {
let cases = &[
("down", "0"),
("north", "2"),
("south", "3"),
("west", "4"),
("east", "5"),
];
for (java_facing, expected_dir) in cases {
if let Some(j) = java_state("minecraft:hopper", &[("facing", java_facing)]) {
let b = j.to_bedrock().unwrap();
assert_eq!(
b.get_property("facing_direction"),
Some(*expected_dir),
"hopper facing={} -> expected facing_direction={}, got {:?}",
java_facing,
expected_dir,
b.properties()
);
}
}
}
#[test]
fn sticky_piston_java_to_bedrock_facings() {
let cases = &[
("down", "0"),
("up", "1"),
("north", "3"),
("south", "2"),
("west", "5"),
("east", "4"),
];
for (java_facing, expected_dir) in cases {
if let Some(j) = java_state(
"minecraft:sticky_piston",
&[("facing", java_facing), ("extended", "false")],
) {
let b = j.to_bedrock().unwrap();
assert!(
b.id().contains("sticky_piston"),
"expected sticky_piston id, got '{}'",
b.id()
);
assert_eq!(
b.get_property("facing_direction"),
Some(*expected_dir),
"sticky_piston facing={} -> expected facing_direction={}, got {:?}",
java_facing,
expected_dir,
b.properties()
);
}
}
}
#[test]
fn observer_java_to_bedrock_facing_and_powered() {
if let Some(j) = java_state(
"minecraft:observer",
&[("facing", "east"), ("powered", "true")],
) {
let b = j.to_bedrock().unwrap();
assert!(b.id().contains("observer"), "got id={}", b.id());
assert_eq!(
b.get_property("minecraft:facing_direction"),
Some("east"),
"observer facing=east -> minecraft:facing_direction=east, got {:?}",
b.properties()
);
assert_eq!(
b.get_property("powered_bit"),
Some("true"),
"observer powered=true -> powered_bit=true, got {:?}",
b.properties()
);
}
}
#[test]
fn comparator_java_to_bedrock_mode_and_facing() {
if let Some(j) = java_state(
"minecraft:comparator",
&[
("facing", "north"),
("mode", "subtract"),
("powered", "true"),
],
) {
let b = j.to_bedrock().unwrap();
assert!(
b.id().contains("comparator"),
"expected comparator id, got '{}'",
b.id()
);
assert_eq!(
b.get_property("output_subtract_bit"),
Some("true"),
"comparator mode=subtract -> output_subtract_bit=true, got {:?}",
b.properties()
);
}
}
#[test]
fn lever_java_to_bedrock_face_wall() {
if let Some(j) = java_state(
"minecraft:lever",
&[("face", "wall"), ("facing", "east"), ("powered", "true")],
) {
let b = j.to_bedrock().unwrap();
assert_eq!(b.id(), "minecraft:lever");
assert_eq!(
b.get_property("open_bit"),
Some("true"),
"lever powered=true -> open_bit=true, got {:?}",
b.properties()
);
}
}
#[test]
fn furnace_java_to_bedrock_facing_and_lit() {
if let Some(j) = java_state(
"minecraft:furnace",
&[("facing", "south"), ("lit", "true")],
) {
let b = j.to_bedrock().unwrap();
assert!(b.id().contains("furnace"), "got id={}", b.id());
assert_eq!(
b.get_property("minecraft:cardinal_direction"),
Some("south"),
"furnace facing=south -> minecraft:cardinal_direction=south, got {:?}",
b.properties()
);
}
}
#[test]
fn dispenser_java_to_bedrock_facing_and_triggered() {
if let Some(j) = java_state(
"minecraft:dispenser",
&[("facing", "down"), ("triggered", "true")],
) {
let b = j.to_bedrock().unwrap();
assert_eq!(b.id(), "minecraft:dispenser");
assert_eq!(
b.get_property("facing_direction"),
Some("0"),
"dispenser facing=down -> facing_direction=0, got {:?}",
b.properties()
);
assert_eq!(
b.get_property("triggered_bit"),
Some("true"),
"dispenser triggered=true -> triggered_bit=true, got {:?}",
b.properties()
);
}
}
#[test]
fn dropper_java_to_bedrock_facing() {
if let Some(j) = java_state(
"minecraft:dropper",
&[("facing", "up"), ("triggered", "false")],
) {
let b = j.to_bedrock().unwrap();
assert_eq!(b.id(), "minecraft:dropper");
assert_eq!(
b.get_property("facing_direction"),
Some("1"),
"dropper facing=up -> facing_direction=1, got {:?}",
b.properties()
);
}
}
}