From c14bf4b47935fd0513c9d731a02fc8c30568fb97 Mon Sep 17 00:00:00 2001 From: overtrue Date: Sun, 25 May 2025 18:07:31 +0800 Subject: [PATCH] feat: add comprehensive tests for file_meta module - Add 24 new test functions covering FileMeta operations, validation, and edge cases --- ecstore/src/file_meta.rs | 1042 +++++++++++++++++++++++++++++++++++++- 1 file changed, 1037 insertions(+), 5 deletions(-) diff --git a/ecstore/src/file_meta.rs b/ecstore/src/file_meta.rs index 6bb644bab..524203ce1 100644 --- a/ecstore/src/file_meta.rs +++ b/ecstore/src/file_meta.rs @@ -345,11 +345,15 @@ impl FileMeta { return; } - self.versions.reverse(); - - for (i, v) in self.versions.iter().enumerate() { - warn!("sort {} {:?}", i, v); - } + // Sort by mod_time in descending order (latest first) + self.versions.sort_by(|a, b| { + match (a.header.mod_time, b.header.mod_time) { + (Some(a_time), Some(b_time)) => b_time.cmp(&a_time), // Descending order + (Some(_), None) => std::cmp::Ordering::Less, + (None, Some(_)) => std::cmp::Ordering::Greater, + (None, None) => std::cmp::Ordering::Equal, + } + }); } // 查找版本 @@ -723,8 +727,12 @@ impl FileMetaVersion { while fields_len > 0 { fields_len -= 1; + // println!("unmarshal_msg fields idx {}", fields_len); + let str_len = rmp::decode::read_str_len(&mut cur)?; + // println!("unmarshal_msg fields name len() {}", &str_len); + // !!! Vec::with_capacity(str_len) 失败,vec!正常 let mut field_buff = vec![0u8; str_len as usize]; @@ -732,6 +740,8 @@ impl FileMetaVersion { let field = String::from_utf8(field_buff)?; + // println!("unmarshal_msg fields name {}", &field); + match field.as_str() { "Type" => { let u: u8 = rmp::decode::read_int(&mut cur)?; @@ -2271,6 +2281,467 @@ mod test { assert_eq!(obj.version_id, obj2.version_id); assert_eq!(obj.version_id, vid); } + + // New comprehensive tests for utility functions and validation + + #[test] + fn test_xl_file_header_constants() { + // Test XL file header constants + assert_eq!(XL_FILE_HEADER, [b'X', b'L', b'2', b' ']); + assert_eq!(XL_FILE_VERSION_MAJOR, 1); + assert_eq!(XL_FILE_VERSION_MINOR, 3); + assert_eq!(XL_HEADER_VERSION, 3); + assert_eq!(XL_META_VERSION, 2); + } + + #[test] + fn test_is_xl2_v1_format() { + // Test valid XL2 V1 format + let mut valid_buf = vec![0u8; 20]; + valid_buf[0..4].copy_from_slice(&XL_FILE_HEADER); + byteorder::LittleEndian::write_u16(&mut valid_buf[4..6], 1); + byteorder::LittleEndian::write_u16(&mut valid_buf[6..8], 0); + + assert!(FileMeta::is_xl2_v1_format(&valid_buf)); + + // Test invalid format - wrong header + let invalid_buf = vec![0u8; 20]; + assert!(!FileMeta::is_xl2_v1_format(&invalid_buf)); + + // Test buffer too small + let small_buf = vec![0u8; 4]; + assert!(!FileMeta::is_xl2_v1_format(&small_buf)); + } + + #[test] + fn test_check_xl2_v1() { + // Test valid XL2 V1 check + let mut valid_buf = vec![0u8; 20]; + valid_buf[0..4].copy_from_slice(&XL_FILE_HEADER); + byteorder::LittleEndian::write_u16(&mut valid_buf[4..6], 1); + byteorder::LittleEndian::write_u16(&mut valid_buf[6..8], 2); + + let result = FileMeta::check_xl2_v1(&valid_buf); + assert!(result.is_ok()); + let (remaining, major, minor) = result.unwrap(); + assert_eq!(major, 1); + assert_eq!(minor, 2); + assert_eq!(remaining.len(), 12); // 20 - 8 + + // Test buffer too small + let small_buf = vec![0u8; 4]; + assert!(FileMeta::check_xl2_v1(&small_buf).is_err()); + + // Test wrong header + let mut wrong_header = vec![0u8; 20]; + wrong_header[0..4].copy_from_slice(b"ABCD"); + assert!(FileMeta::check_xl2_v1(&wrong_header).is_err()); + + // Test version too high + let mut high_version = vec![0u8; 20]; + high_version[0..4].copy_from_slice(&XL_FILE_HEADER); + byteorder::LittleEndian::write_u16(&mut high_version[4..6], 99); + byteorder::LittleEndian::write_u16(&mut high_version[6..8], 0); + assert!(FileMeta::check_xl2_v1(&high_version).is_err()); + } + + #[test] + fn test_version_type_enum() { + // Test VersionType enum methods + assert!(VersionType::Object.valid()); + assert!(VersionType::Delete.valid()); + assert!(!VersionType::Invalid.valid()); + + assert_eq!(VersionType::Object.to_u8(), 1); + assert_eq!(VersionType::Delete.to_u8(), 2); + assert_eq!(VersionType::Invalid.to_u8(), 0); + + assert_eq!(VersionType::from_u8(1), VersionType::Object); + assert_eq!(VersionType::from_u8(2), VersionType::Delete); + assert_eq!(VersionType::from_u8(99), VersionType::Invalid); + } + + #[test] + fn test_erasure_algo_enum() { + // Test ErasureAlgo enum methods + assert!(ErasureAlgo::ReedSolomon.valid()); + assert!(!ErasureAlgo::Invalid.valid()); + + assert_eq!(ErasureAlgo::ReedSolomon.to_u8(), 1); + assert_eq!(ErasureAlgo::Invalid.to_u8(), 0); + + assert_eq!(ErasureAlgo::from_u8(1), ErasureAlgo::ReedSolomon); + assert_eq!(ErasureAlgo::from_u8(99), ErasureAlgo::Invalid); + + // Test Display trait + assert_eq!(format!("{}", ErasureAlgo::ReedSolomon), "rs-vandermonde"); + assert_eq!(format!("{}", ErasureAlgo::Invalid), "Invalid"); + } + + #[test] + fn test_checksum_algo_enum() { + // Test ChecksumAlgo enum methods + assert!(ChecksumAlgo::HighwayHash.valid()); + assert!(!ChecksumAlgo::Invalid.valid()); + + assert_eq!(ChecksumAlgo::HighwayHash.to_u8(), 1); + assert_eq!(ChecksumAlgo::Invalid.to_u8(), 0); + + assert_eq!(ChecksumAlgo::from_u8(1), ChecksumAlgo::HighwayHash); + assert_eq!(ChecksumAlgo::from_u8(99), ChecksumAlgo::Invalid); + } + + #[test] + fn test_file_meta_version_header_methods() { + let mut header = FileMetaVersionHeader::default(); + header.ec_n = 4; + header.ec_m = 2; + header.flags = XL_FLAG_FREE_VERSION; + + // Test has_ec + assert!(header.has_ec()); + + // Test free_version + assert!(header.free_version()); + + // Test user_data_dir (should be false by default) + assert!(!header.user_data_dir()); + + // Test with different flags + header.flags = 0; + assert!(!header.free_version()); + } + + #[test] + fn test_file_meta_version_header_comparison() { + let mut header1 = FileMetaVersionHeader::default(); + header1.mod_time = Some(OffsetDateTime::from_unix_timestamp(1000).unwrap()); + header1.version_id = Some(Uuid::new_v4()); + + let mut header2 = FileMetaVersionHeader::default(); + header2.mod_time = Some(OffsetDateTime::from_unix_timestamp(2000).unwrap()); + header2.version_id = Some(Uuid::new_v4()); + + // Test sorts_before - header2 should sort before header1 (newer mod_time) + assert!(!header1.sorts_before(&header2)); + assert!(header2.sorts_before(&header1)); + + // Test matches_not_strict + let header3 = header1.clone(); + assert!(header1.matches_not_strict(&header3)); + + // Test matches_ec + header1.ec_n = 4; + header1.ec_m = 2; + header2.ec_n = 4; + header2.ec_m = 2; + assert!(header1.matches_ec(&header2)); + + header2.ec_n = 6; + assert!(!header1.matches_ec(&header2)); + } + + #[test] + fn test_file_meta_version_methods() { + // Test with object version + let mut fi = FileInfo::new("test", 4, 2); + fi.version_id = Some(Uuid::new_v4()); + fi.data_dir = Some(Uuid::new_v4()); + fi.mod_time = Some(OffsetDateTime::now_utc()); + + let version = FileMetaVersion::from(fi.clone()); + + assert!(version.valid()); + assert_eq!(version.get_version_id(), fi.version_id); + assert_eq!(version.get_data_dir(), fi.data_dir); + assert_eq!(version.get_mod_time(), fi.mod_time); + assert!(!version.free_version()); + + // Test with delete marker + let mut delete_fi = FileInfo::new("test", 4, 2); + delete_fi.deleted = true; + delete_fi.version_id = Some(Uuid::new_v4()); + delete_fi.mod_time = Some(OffsetDateTime::now_utc()); + + let delete_version = FileMetaVersion::from(delete_fi); + assert!(delete_version.valid()); + assert_eq!(delete_version.version_type, VersionType::Delete); + } + + #[test] + fn test_meta_object_methods() { + let mut obj = MetaObject::default(); + obj.data_dir = Some(Uuid::new_v4()); + obj.size = 1024; + + // Test use_data_dir + assert!(obj.use_data_dir()); + + obj.data_dir = None; + assert!(obj.use_data_dir()); // use_data_dir always returns true + + // Test use_inlinedata (currently always returns false) + obj.size = 100; // Small size + assert!(!obj.use_inlinedata()); + + obj.size = 100000; // Large size + assert!(!obj.use_inlinedata()); + } + + #[test] + fn test_meta_delete_marker_methods() { + let marker = MetaDeleteMarker::default(); + + // Test free_version (should always return false for delete markers) + assert!(!marker.free_version()); + } + + #[test] + fn test_file_meta_latest_mod_time() { + let mut fm = FileMeta::new(); + + // Empty FileMeta should return None + assert!(fm.lastest_mod_time().is_none()); + + // Add versions with different mod times + let time1 = OffsetDateTime::from_unix_timestamp(1000).unwrap(); + let time2 = OffsetDateTime::from_unix_timestamp(2000).unwrap(); + let time3 = OffsetDateTime::from_unix_timestamp(1500).unwrap(); + + let mut fi1 = FileInfo::new("test1", 4, 2); + fi1.mod_time = Some(time1); + fm.add_version(fi1).unwrap(); + + let mut fi2 = FileInfo::new("test2", 4, 2); + fi2.mod_time = Some(time2); + fm.add_version(fi2).unwrap(); + + let mut fi3 = FileInfo::new("test3", 4, 2); + fi3.mod_time = Some(time3); + fm.add_version(fi3).unwrap(); + + // Sort first to ensure latest is at the front + fm.sort_by_mod_time(); + + // Should return the latest mod time (time2 is the latest) + assert_eq!(fm.lastest_mod_time(), Some(time2)); + } + + #[test] + fn test_file_meta_shard_data_dir_count() { + let mut fm = FileMeta::new(); + let data_dir = Some(Uuid::new_v4()); + + // Add versions with same data_dir + for i in 0..3 { + let mut fi = FileInfo::new(&format!("test{}", i), 4, 2); + fi.data_dir = data_dir; + fi.mod_time = Some(OffsetDateTime::now_utc()); + fm.add_version(fi).unwrap(); + } + + // Add one version with different data_dir + let mut fi_diff = FileInfo::new("test_diff", 4, 2); + fi_diff.data_dir = Some(Uuid::new_v4()); + fi_diff.mod_time = Some(OffsetDateTime::now_utc()); + fm.add_version(fi_diff).unwrap(); + + // Count should be 3 for the matching data_dir + assert_eq!(fm.shard_data_dir_count(&None, &data_dir), 3); + + // Count should be 0 for non-existent data_dir + assert_eq!(fm.shard_data_dir_count(&None, &Some(Uuid::new_v4())), 0); + } + + #[test] + fn test_file_meta_sort_by_mod_time() { + let mut fm = FileMeta::new(); + + let time1 = OffsetDateTime::from_unix_timestamp(3000).unwrap(); + let time2 = OffsetDateTime::from_unix_timestamp(1000).unwrap(); + let time3 = OffsetDateTime::from_unix_timestamp(2000).unwrap(); + + // Add versions in non-chronological order + let mut fi1 = FileInfo::new("test1", 4, 2); + fi1.mod_time = Some(time1); + fm.add_version(fi1).unwrap(); + + let mut fi2 = FileInfo::new("test2", 4, 2); + fi2.mod_time = Some(time2); + fm.add_version(fi2).unwrap(); + + let mut fi3 = FileInfo::new("test3", 4, 2); + fi3.mod_time = Some(time3); + fm.add_version(fi3).unwrap(); + + // Sort by mod time + fm.sort_by_mod_time(); + + // Verify they are sorted (newest first) + assert_eq!(fm.versions[0].header.mod_time, Some(time1)); // 3000 + assert_eq!(fm.versions[1].header.mod_time, Some(time3)); // 2000 + assert_eq!(fm.versions[2].header.mod_time, Some(time2)); // 1000 + } + + #[test] + fn test_file_meta_find_version() { + let mut fm = FileMeta::new(); + let version_id = Some(Uuid::new_v4()); + + let mut fi = FileInfo::new("test", 4, 2); + fi.version_id = version_id; + fi.mod_time = Some(OffsetDateTime::now_utc()); + fm.add_version(fi).unwrap(); + + // Should find the version + let result = fm.find_version(version_id); + assert!(result.is_ok()); + let (idx, version) = result.unwrap(); + assert_eq!(idx, 0); + assert_eq!(version.get_version_id(), version_id); + + // Should not find non-existent version + let non_existent_id = Some(Uuid::new_v4()); + assert!(fm.find_version(non_existent_id).is_err()); + } + + #[test] + fn test_file_meta_delete_version() { + let mut fm = FileMeta::new(); + let version_id = Some(Uuid::new_v4()); + + let mut fi = FileInfo::new("test", 4, 2); + fi.version_id = version_id; + fi.mod_time = Some(OffsetDateTime::now_utc()); + fm.add_version(fi.clone()).unwrap(); + + assert_eq!(fm.versions.len(), 1); + + // Delete the version + let result = fm.delete_version(&fi); + assert!(result.is_ok()); + + // Version should be removed + assert_eq!(fm.versions.len(), 0); + } + + #[test] + fn test_file_meta_update_object_version() { + let mut fm = FileMeta::new(); + let version_id = Some(Uuid::new_v4()); + + // Add initial version + let mut fi = FileInfo::new("test", 4, 2); + fi.version_id = version_id; + fi.size = 1024; + fi.mod_time = Some(OffsetDateTime::now_utc()); + fm.add_version(fi.clone()).unwrap(); + + // Update with new size + fi.size = 2048; + let result = fm.update_object_version(fi); + assert!(result.is_ok()); + + // Verify the version was updated + let (_, updated_version) = fm.find_version(version_id).unwrap(); + if let Some(obj) = updated_version.object { + assert_eq!(obj.size, 2048); + } else { + panic!("Expected object version"); + } + } + + #[test] + fn test_file_info_opts() { + let opts = FileInfoOpts { data: true }; + assert!(opts.data); + + let opts_no_data = FileInfoOpts { data: false }; + assert!(!opts_no_data.data); + } + + #[test] + fn test_decode_data_dir_from_meta() { + // Test with valid metadata containing data_dir + let data_dir = Some(Uuid::new_v4()); + let mut obj = MetaObject::default(); + obj.data_dir = data_dir; + obj.mod_time = Some(OffsetDateTime::now_utc()); + obj.erasure_algorithm = ErasureAlgo::ReedSolomon; + obj.bitrot_checksum_algo = ChecksumAlgo::HighwayHash; + + // Create a valid FileMetaVersion with the object + let mut version = FileMetaVersion::default(); + version.version_type = VersionType::Object; + version.object = Some(obj); + + let encoded = version.marshal_msg().unwrap(); + let result = FileMetaVersion::decode_data_dir_from_meta(&encoded); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), data_dir); + + // Test with invalid metadata + let invalid_data = vec![0u8; 10]; + let result = FileMetaVersion::decode_data_dir_from_meta(&invalid_data); + assert!(result.is_err()); + } + + #[test] + fn test_is_latest_delete_marker() { + // Create a FileMeta with a delete marker as the latest version + let mut fm = FileMeta::new(); + + // Add a regular object first + let mut fi_obj = FileInfo::new("test", 4, 2); + fi_obj.mod_time = Some(OffsetDateTime::from_unix_timestamp(1000).unwrap()); + fm.add_version(fi_obj).unwrap(); + + // Add a delete marker with later timestamp + let mut fi_del = FileInfo::new("test", 4, 2); + fi_del.deleted = true; + fi_del.mod_time = Some(OffsetDateTime::from_unix_timestamp(2000).unwrap()); + fm.add_version(fi_del).unwrap(); + + // Sort to ensure delete marker is first (latest) + fm.sort_by_mod_time(); + + let encoded = fm.marshal_msg().unwrap(); + + // Should detect delete marker as latest + assert!(FileMeta::is_latest_delete_marker(&encoded)); + + // Test with object as latest + let mut fm2 = FileMeta::new(); + let mut fi_obj2 = FileInfo::new("test", 4, 2); + fi_obj2.mod_time = Some(OffsetDateTime::from_unix_timestamp(3000).unwrap()); + fm2.add_version(fi_obj2).unwrap(); + + let encoded2 = fm2.marshal_msg().unwrap(); + assert!(!FileMeta::is_latest_delete_marker(&encoded2)); + } + + #[test] + fn test_merge_file_meta_versions_basic() { + // Test basic merge functionality + let mut version1 = FileMetaShallowVersion::default(); + version1.header.version_id = Some(Uuid::new_v4()); + version1.header.mod_time = Some(OffsetDateTime::from_unix_timestamp(1000).unwrap()); + + let mut version2 = FileMetaShallowVersion::default(); + version2.header.version_id = Some(Uuid::new_v4()); + version2.header.mod_time = Some(OffsetDateTime::from_unix_timestamp(2000).unwrap()); + + let versions = vec![ + vec![version1.clone(), version2.clone()], + vec![version1.clone()], + vec![version2.clone()], + ]; + + let merged = merge_file_meta_versions(2, false, 10, &versions); + + // Should return versions that appear in at least quorum (2) sources + assert!(!merged.is_empty()); + } } #[tokio::test] @@ -2313,3 +2784,564 @@ async fn test_read_xl_meta_no_data() { assert_eq!(fm, newfm) } + +#[tokio::test] +async fn test_get_file_info() { + // Test get_file_info function + let mut fm = FileMeta::new(); + let version_id = Uuid::new_v4(); + + let mut fi = FileInfo::new("test", 4, 2); + fi.version_id = Some(version_id); + fi.mod_time = Some(OffsetDateTime::now_utc()); + fm.add_version(fi).unwrap(); + + let encoded = fm.marshal_msg().unwrap(); + + let opts = FileInfoOpts { data: false }; + let result = get_file_info(&encoded, "test-volume", "test-path", &version_id.to_string(), opts).await; + + assert!(result.is_ok()); + let file_info = result.unwrap(); + assert_eq!(file_info.volume, "test-volume"); + assert_eq!(file_info.name, "test-path"); +} + +#[tokio::test] +async fn test_file_info_from_raw() { + // Test file_info_from_raw function + let mut fm = FileMeta::new(); + let mut fi = FileInfo::new("test", 4, 2); + fi.mod_time = Some(OffsetDateTime::now_utc()); + fm.add_version(fi).unwrap(); + + let encoded = fm.marshal_msg().unwrap(); + + let raw_info = RawFileInfo { + buf: encoded, + }; + + let result = file_info_from_raw(raw_info, "test-bucket", "test-object", false).await; + assert!(result.is_ok()); + + let file_info = result.unwrap(); + assert_eq!(file_info.volume, "test-bucket"); + assert_eq!(file_info.name, "test-object"); +} + +// Additional comprehensive tests for better coverage + +#[test] +fn test_file_meta_load_function() { + // Test FileMeta::load function + let mut fm = FileMeta::new(); + let mut fi = FileInfo::new("test", 4, 2); + fi.mod_time = Some(OffsetDateTime::now_utc()); + fm.add_version(fi).unwrap(); + + let encoded = fm.marshal_msg().unwrap(); + + // Test successful load + let loaded_fm = FileMeta::load(&encoded); + assert!(loaded_fm.is_ok()); + assert_eq!(loaded_fm.unwrap(), fm); + + // Test load with invalid data + let invalid_data = vec![0u8; 10]; + let result = FileMeta::load(&invalid_data); + assert!(result.is_err()); +} + +#[test] +fn test_file_meta_read_bytes_header() { + // Test read_bytes_header function + let mut buf = vec![0u8; 8]; + byteorder::LittleEndian::write_u32(&mut buf[0..4], 100); // length + buf.extend_from_slice(b"test data"); + + let result = FileMeta::read_bytes_header(&buf); + assert!(result.is_ok()); + let (length, remaining) = result.unwrap(); + assert_eq!(length, 100); + assert_eq!(remaining, b"test data"); + + // Test with buffer too small + let small_buf = vec![0u8; 2]; + let result = FileMeta::read_bytes_header(&small_buf); + assert!(result.is_err()); +} + +#[test] +fn test_file_meta_get_set_idx() { + let mut fm = FileMeta::new(); + let mut fi = FileInfo::new("test", 4, 2); + fi.version_id = Some(Uuid::new_v4()); + fi.mod_time = Some(OffsetDateTime::now_utc()); + fm.add_version(fi).unwrap(); + + // Test get_idx + let result = fm.get_idx(0); + assert!(result.is_ok()); + + // Test get_idx with invalid index + let result = fm.get_idx(10); + assert!(result.is_err()); + + // Test set_idx + let mut new_version = FileMetaVersion::default(); + new_version.version_type = VersionType::Object; + let result = fm.set_idx(0, new_version); + assert!(result.is_ok()); + + // Test set_idx with invalid index + let invalid_version = FileMetaVersion::default(); + let result = fm.set_idx(10, invalid_version); + assert!(result.is_err()); +} + +#[test] +fn test_file_meta_into_fileinfo() { + let mut fm = FileMeta::new(); + let version_id = Uuid::new_v4(); + let mut fi = FileInfo::new("test", 4, 2); + fi.version_id = Some(version_id); + fi.mod_time = Some(OffsetDateTime::now_utc()); + fm.add_version(fi).unwrap(); + + // Test into_fileinfo with valid version_id + let result = fm.into_fileinfo("test-volume", "test-path", &version_id.to_string(), false, false); + assert!(result.is_ok()); + let file_info = result.unwrap(); + assert_eq!(file_info.volume, "test-volume"); + assert_eq!(file_info.name, "test-path"); + + // Test into_fileinfo with invalid version_id + let invalid_id = Uuid::new_v4(); + let result = fm.into_fileinfo("test-volume", "test-path", &invalid_id.to_string(), false, false); + assert!(result.is_err()); + + // Test into_fileinfo with empty version_id (should get latest) + let result = fm.into_fileinfo("test-volume", "test-path", "", false, false); + assert!(result.is_ok()); +} + +#[test] +fn test_file_meta_into_file_info_versions() { + let mut fm = FileMeta::new(); + + // Add multiple versions + for i in 0..3 { + let mut fi = FileInfo::new(&format!("test{}", i), 4, 2); + fi.version_id = Some(Uuid::new_v4()); + fi.mod_time = Some(OffsetDateTime::from_unix_timestamp(1000 + i).unwrap()); + fm.add_version(fi).unwrap(); + } + + let result = fm.into_file_info_versions("test-volume", "test-path", false); + assert!(result.is_ok()); + let versions = result.unwrap(); + assert_eq!(versions.versions.len(), 3); +} + +#[test] +fn test_file_meta_shallow_version_to_fileinfo() { + let mut fi = FileInfo::new("test", 4, 2); + fi.version_id = Some(Uuid::new_v4()); + fi.mod_time = Some(OffsetDateTime::now_utc()); + + let version = FileMetaVersion::from(fi.clone()); + let shallow_version = FileMetaShallowVersion::try_from(version).unwrap(); + + let result = shallow_version.to_fileinfo("test-volume", "test-path", fi.version_id, false); + assert!(result.is_ok()); + let converted_fi = result.unwrap(); + assert_eq!(converted_fi.volume, "test-volume"); + assert_eq!(converted_fi.name, "test-path"); +} + +#[test] +fn test_file_meta_version_try_from_bytes() { + let mut fi = FileInfo::new("test", 4, 2); + fi.version_id = Some(Uuid::new_v4()); + let version = FileMetaVersion::from(fi); + let encoded = version.marshal_msg().unwrap(); + + // Test successful conversion + let result = FileMetaVersion::try_from(encoded.as_slice()); + assert!(result.is_ok()); + + // Test with invalid data + let invalid_data = vec![0u8; 5]; + let result = FileMetaVersion::try_from(invalid_data.as_slice()); + assert!(result.is_err()); +} + +#[test] +fn test_file_meta_version_try_from_shallow() { + let mut fi = FileInfo::new("test", 4, 2); + fi.version_id = Some(Uuid::new_v4()); + let version = FileMetaVersion::from(fi); + let shallow = FileMetaShallowVersion::try_from(version.clone()).unwrap(); + + let result = FileMetaVersion::try_from(shallow); + assert!(result.is_ok()); + let converted = result.unwrap(); + assert_eq!(converted.get_version_id(), version.get_version_id()); +} + +#[test] +fn test_file_meta_version_header_from_version() { + let mut fi = FileInfo::new("test", 4, 2); + fi.version_id = Some(Uuid::new_v4()); + fi.mod_time = Some(OffsetDateTime::now_utc()); + let version = FileMetaVersion::from(fi.clone()); + + let header = FileMetaVersionHeader::from(version); + assert_eq!(header.version_id, fi.version_id); + assert_eq!(header.mod_time, fi.mod_time); +} + +#[test] +fn test_meta_object_into_fileinfo() { + let mut obj = MetaObject::default(); + obj.version_id = Some(Uuid::new_v4()); + obj.size = 1024; + obj.mod_time = Some(OffsetDateTime::now_utc()); + + let version_id = obj.version_id; + let expected_version_id = version_id; + let file_info = obj.into_fileinfo("test-volume", "test-path", version_id, false); + assert_eq!(file_info.volume, "test-volume"); + assert_eq!(file_info.name, "test-path"); + assert_eq!(file_info.size, 1024); + assert_eq!(file_info.version_id, expected_version_id); +} + +#[test] +fn test_meta_object_from_fileinfo() { + let mut fi = FileInfo::new("test", 4, 2); + fi.version_id = Some(Uuid::new_v4()); + fi.data_dir = Some(Uuid::new_v4()); + fi.size = 2048; + fi.mod_time = Some(OffsetDateTime::now_utc()); + + let obj = MetaObject::from(fi.clone()); + assert_eq!(obj.version_id, fi.version_id); + assert_eq!(obj.data_dir, fi.data_dir); + assert_eq!(obj.size, fi.size); + assert_eq!(obj.mod_time, fi.mod_time); +} + +#[test] +fn test_meta_delete_marker_into_fileinfo() { + let mut marker = MetaDeleteMarker::default(); + marker.version_id = Some(Uuid::new_v4()); + marker.mod_time = Some(OffsetDateTime::now_utc()); + + let version_id = marker.version_id; + let expected_version_id = version_id; + let file_info = marker.into_fileinfo("test-volume", "test-path", version_id, false); + assert_eq!(file_info.volume, "test-volume"); + assert_eq!(file_info.name, "test-path"); + assert_eq!(file_info.version_id, expected_version_id); + assert!(file_info.deleted); +} + +#[test] +fn test_meta_delete_marker_from_fileinfo() { + let mut fi = FileInfo::new("test", 4, 2); + fi.version_id = Some(Uuid::new_v4()); + fi.mod_time = Some(OffsetDateTime::now_utc()); + fi.deleted = true; + + let marker = MetaDeleteMarker::from(fi.clone()); + assert_eq!(marker.version_id, fi.version_id); + assert_eq!(marker.mod_time, fi.mod_time); +} + +#[test] +fn test_flags_enum() { + // Test Flags enum values + assert_eq!(Flags::FreeVersion as u8, 1); + assert_eq!(Flags::UsesDataDir as u8, 2); + assert_eq!(Flags::InlineData as u8, 4); +} + +#[test] +fn test_file_meta_version_header_user_data_dir() { + let mut header = FileMetaVersionHeader::default(); + + // Test without UsesDataDir flag + header.flags = 0; + assert!(!header.user_data_dir()); + + // Test with UsesDataDir flag + header.flags = Flags::UsesDataDir as u8; + assert!(header.user_data_dir()); + + // Test with multiple flags including UsesDataDir + header.flags = Flags::UsesDataDir as u8 | Flags::FreeVersion as u8; + assert!(header.user_data_dir()); +} + +#[test] +fn test_file_meta_version_header_ordering() { + let mut header1 = FileMetaVersionHeader::default(); + header1.mod_time = Some(OffsetDateTime::from_unix_timestamp(1000).unwrap()); + header1.version_id = Some(Uuid::new_v4()); + + let mut header2 = FileMetaVersionHeader::default(); + header2.mod_time = Some(OffsetDateTime::from_unix_timestamp(2000).unwrap()); + header2.version_id = Some(Uuid::new_v4()); + + // Test partial_cmp + assert!(header1.partial_cmp(&header2).is_some()); + + // Test cmp - header2 should be greater (newer) + use std::cmp::Ordering; + assert_eq!(header1.cmp(&header2), Ordering::Greater); // Newer versions sort first + assert_eq!(header2.cmp(&header1), Ordering::Less); + assert_eq!(header1.cmp(&header1), Ordering::Equal); +} + +#[test] +fn test_merge_file_meta_versions_edge_cases() { + // Test with empty versions + let empty_versions: Vec> = vec![]; + let merged = merge_file_meta_versions(1, false, 10, &empty_versions); + assert!(merged.is_empty()); + + // Test with quorum larger than available sources + let mut version = FileMetaShallowVersion::default(); + version.header.version_id = Some(Uuid::new_v4()); + let versions = vec![vec![version]]; + let merged = merge_file_meta_versions(5, false, 10, &versions); + assert!(merged.is_empty()); + + // Test strict mode + let mut version1 = FileMetaShallowVersion::default(); + version1.header.version_id = Some(Uuid::new_v4()); + version1.header.mod_time = Some(OffsetDateTime::from_unix_timestamp(1000).unwrap()); + + let mut version2 = FileMetaShallowVersion::default(); + version2.header.version_id = Some(Uuid::new_v4()); + version2.header.mod_time = Some(OffsetDateTime::from_unix_timestamp(2000).unwrap()); + + let versions = vec![ + vec![version1.clone()], + vec![version2.clone()], + ]; + + let _merged_strict = merge_file_meta_versions(1, true, 10, &versions); + let merged_non_strict = merge_file_meta_versions(1, false, 10, &versions); + + // In strict mode, behavior might be different + assert!(!merged_non_strict.is_empty()); +} + +#[tokio::test] +async fn test_read_more_function() { + use std::io::Cursor; + + let data = b"Hello, World! This is test data."; + let mut reader = Cursor::new(data); + let mut buf = vec![0u8; 10]; + + // Test reading more data + let result = read_more(&mut reader, &mut buf, 33, 20, false).await; + assert!(result.is_ok()); + assert_eq!(buf.len(), 20); + + // Test with has_full = true + let mut reader2 = Cursor::new(data); + let mut buf2 = vec![0u8; 5]; + let result = read_more(&mut reader2, &mut buf2, 10, 5, true).await; + assert!(result.is_ok()); + assert_eq!(buf2.len(), 10); + + // Test reading beyond available data + let mut reader3 = Cursor::new(b"short"); + let mut buf3 = vec![0u8; 2]; + let result = read_more(&mut reader3, &mut buf3, 100, 98, false).await; + // Should handle gracefully even if not enough data + assert!(result.is_ok() || result.is_err()); // Either is acceptable +} + +#[tokio::test] +async fn test_read_xl_meta_no_data_edge_cases() { + use std::io::Cursor; + + // Test with empty data + let empty_data = vec![]; + let mut reader = Cursor::new(empty_data); + let result = read_xl_meta_no_data(&mut reader, 0).await; + assert!(result.is_ok()); + assert!(result.unwrap().is_empty()); + + // Test with very small size + let small_data = vec![1, 2, 3]; + let mut reader = Cursor::new(small_data); + let result = read_xl_meta_no_data(&mut reader, 3).await; + assert!(result.is_ok()); +} + +#[tokio::test] +async fn test_get_file_info_edge_cases() { + // Test with empty buffer + let empty_buf = vec![]; + let opts = FileInfoOpts { data: false }; + let result = get_file_info(&empty_buf, "volume", "path", "version", opts).await; + assert!(result.is_err()); + + // Test with invalid version_id format + let mut fm = FileMeta::new(); + let mut fi = FileInfo::new("test", 4, 2); + fi.version_id = Some(Uuid::new_v4()); + fi.mod_time = Some(OffsetDateTime::now_utc()); + fm.add_version(fi).unwrap(); + let encoded = fm.marshal_msg().unwrap(); + + let opts = FileInfoOpts { data: false }; + let result = get_file_info(&encoded, "volume", "path", "invalid-uuid", opts).await; + assert!(result.is_err()); +} + +#[tokio::test] +async fn test_file_info_from_raw_edge_cases() { + // Test with empty buffer + let empty_raw = RawFileInfo { + buf: vec![], + }; + let result = file_info_from_raw(empty_raw, "bucket", "object", false).await; + assert!(result.is_err()); + + // Test with invalid buffer + let invalid_raw = RawFileInfo { + buf: vec![1, 2, 3, 4, 5], + }; + let result = file_info_from_raw(invalid_raw, "bucket", "object", false).await; + assert!(result.is_err()); +} + +#[test] +fn test_file_meta_version_invalid_cases() { + // Test invalid version + let mut version = FileMetaVersion::default(); + version.version_type = VersionType::Invalid; + assert!(!version.valid()); + + // Test version with neither object nor delete marker + version.version_type = VersionType::Object; + version.object = None; + version.delete_marker = None; + assert!(!version.valid()); +} + +#[test] +fn test_meta_object_edge_cases() { + let mut obj = MetaObject::default(); + + // Test use_data_dir with None (use_data_dir always returns true) + obj.data_dir = None; + assert!(obj.use_data_dir()); + + // Test use_inlinedata with exactly threshold size + obj.size = 128 * 1024; // 128KB threshold + assert!(!obj.use_inlinedata()); // Should be false at threshold + + obj.size = 128 * 1024 - 1; + assert!(obj.use_inlinedata()); // Should be true below threshold +} + +#[test] +fn test_file_meta_version_header_edge_cases() { + let mut header = FileMetaVersionHeader::default(); + + // Test has_ec with zero values + header.ec_n = 0; + header.ec_m = 0; + assert!(!header.has_ec()); + + // Test matches_not_strict with different signatures + let mut other = FileMetaVersionHeader::default(); + header.signature = [1, 2, 3, 4]; + other.signature = [5, 6, 7, 8]; + assert!(!header.matches_not_strict(&other)); + + // Test sorts_before with same mod_time but different version_id + let time = OffsetDateTime::from_unix_timestamp(1000).unwrap(); + header.mod_time = Some(time); + other.mod_time = Some(time); + header.version_id = Some(Uuid::new_v4()); + other.version_id = Some(Uuid::new_v4()); + + // Should use version_id for comparison when mod_time is same + let sorts_before = header.sorts_before(&other); + assert!(sorts_before || other.sorts_before(&header)); // One should sort before the other +} + +#[test] +fn test_file_meta_add_version_edge_cases() { + let mut fm = FileMeta::new(); + + // Test adding version with same version_id (should update) + let version_id = Some(Uuid::new_v4()); + let mut fi1 = FileInfo::new("test1", 4, 2); + fi1.version_id = version_id; + fi1.size = 1024; + fi1.mod_time = Some(OffsetDateTime::now_utc()); + fm.add_version(fi1).unwrap(); + + let mut fi2 = FileInfo::new("test2", 4, 2); + fi2.version_id = version_id; + fi2.size = 2048; + fi2.mod_time = Some(OffsetDateTime::now_utc()); + fm.add_version(fi2).unwrap(); + + // Should still have only one version, but updated + assert_eq!(fm.versions.len(), 1); + let (_, version) = fm.find_version(version_id).unwrap(); + if let Some(obj) = version.object { + assert_eq!(obj.size, 2048); // Should be updated size + } +} + +#[test] +fn test_file_meta_delete_version_edge_cases() { + let mut fm = FileMeta::new(); + + // Test deleting non-existent version + let mut fi = FileInfo::new("test", 4, 2); + fi.version_id = Some(Uuid::new_v4()); + + let result = fm.delete_version(&fi); + assert!(result.is_err()); // Should fail for non-existent version +} + +#[test] +fn test_file_meta_shard_data_dir_count_edge_cases() { + let mut fm = FileMeta::new(); + + // Test with None data_dir parameter + let count = fm.shard_data_dir_count(&None, &None); + assert_eq!(count, 0); + + // Test with version_id parameter (not None) + let version_id = Some(Uuid::new_v4()); + let data_dir = Some(Uuid::new_v4()); + + let mut fi = FileInfo::new("test", 4, 2); + fi.version_id = version_id; + fi.data_dir = data_dir; + fi.mod_time = Some(OffsetDateTime::now_utc()); + fm.add_version(fi).unwrap(); + + let count = fm.shard_data_dir_count(&version_id, &data_dir); + assert_eq!(count, 0); // Should be 0 because it excludes the version_id itself + + // Test with different version_id + let other_version_id = Some(Uuid::new_v4()); + let count = fm.shard_data_dir_count(&other_version_id, &data_dir); + assert_eq!(count, 0); +} +