use std::fmt::Write as _;
use chia_protocol::Bytes32;
use dig_coinstore::storage::StorageError;
use dig_coinstore::CoinId;
use dig_coinstore::CoinStoreError;
mod helpers;
fn sample_coin_id() -> CoinId {
Bytes32::from([0xABu8; 32])
}
#[test]
fn vv_req_api_004_all_fifteen_variants_constructible() {
let _ = CoinStoreError::HeightMismatch {
expected: 1,
got: 9,
};
let _ = CoinStoreError::ParentHashMismatch {
expected: Bytes32::from([1u8; 32]),
got: Bytes32::from([2u8; 32]),
};
let _ = CoinStoreError::StateRootMismatch {
expected: Bytes32::from([3u8; 32]),
computed: Bytes32::from([4u8; 32]),
};
let _ = CoinStoreError::CoinNotFound(sample_coin_id());
let _ = CoinStoreError::CoinAlreadyExists(sample_coin_id());
let _ = CoinStoreError::DoubleSpend(sample_coin_id());
let _ = CoinStoreError::SpendCountMismatch {
expected: 3,
actual: 0,
};
let _ = CoinStoreError::InvalidRewardCoinCount {
expected: ">= 2".into(),
got: 1,
};
let _ = CoinStoreError::HintTooLong {
length: 33,
max: 32,
};
let _ = CoinStoreError::GenesisAlreadyInitialized;
let _ = CoinStoreError::NotInitialized;
let _ = CoinStoreError::PuzzleHashBatchTooLarge {
size: 10_000,
max: 990,
};
let _ = CoinStoreError::StorageError("io".into());
let _ = CoinStoreError::SerializationError("ser".into());
let _ = CoinStoreError::DeserializationError("de".into());
}
#[test]
fn vv_req_api_004_clone_equals_original_for_each_shape() {
let cases: Vec<CoinStoreError> = vec![
CoinStoreError::HeightMismatch {
expected: 5,
got: 6,
},
CoinStoreError::ParentHashMismatch {
expected: Bytes32::from([7u8; 32]),
got: Bytes32::from([8u8; 32]),
},
CoinStoreError::StateRootMismatch {
expected: Bytes32::from([9u8; 32]),
computed: Bytes32::from([10u8; 32]),
},
CoinStoreError::CoinNotFound(sample_coin_id()),
CoinStoreError::CoinAlreadyExists(sample_coin_id()),
CoinStoreError::DoubleSpend(sample_coin_id()),
CoinStoreError::SpendCountMismatch {
expected: 2,
actual: 1,
},
CoinStoreError::InvalidRewardCoinCount {
expected: "0".into(),
got: 1,
},
CoinStoreError::HintTooLong {
length: 40,
max: 32,
},
CoinStoreError::GenesisAlreadyInitialized,
CoinStoreError::NotInitialized,
CoinStoreError::PuzzleHashBatchTooLarge { size: 100, max: 50 },
CoinStoreError::StorageError("e".into()),
CoinStoreError::SerializationError("s".into()),
CoinStoreError::DeserializationError("d".into()),
];
for e in cases {
assert_eq!(
e,
e.clone(),
"Clone must preserve discriminant + data: {:?}",
e
);
}
}
#[test]
fn vv_req_api_004_partial_eq_distinct_payloads() {
let a = CoinStoreError::HeightMismatch {
expected: 1,
got: 2,
};
let b = CoinStoreError::HeightMismatch {
expected: 1,
got: 3,
};
assert_ne!(a, b);
assert_eq!(a, a);
let c = CoinStoreError::CoinNotFound(Bytes32::from([0x01u8; 32]));
let d = CoinStoreError::CoinNotFound(Bytes32::from([0x02u8; 32]));
assert_ne!(c, d);
}
#[test]
fn vv_req_api_004_display_templates_match_normative_wording() {
let mut buf = String::new();
let err = CoinStoreError::HeightMismatch {
expected: 10,
got: 12,
};
write!(&mut buf, "{}", err).unwrap();
assert!(buf.contains("10"), "{}", buf);
assert!(buf.contains("12"), "{}", buf);
buf.clear();
write!(
&mut buf,
"{}",
CoinStoreError::InvalidRewardCoinCount {
expected: ">= 2".into(),
got: 0,
}
)
.unwrap();
assert!(buf.contains(">= 2"), "{}", buf);
assert!(buf.contains('0'), "{}", buf);
buf.clear();
write!(&mut buf, "{}", CoinStoreError::GenesisAlreadyInitialized).unwrap();
assert!(buf.to_lowercase().contains("genesis"), "{}", buf);
buf.clear();
write!(&mut buf, "{}", CoinStoreError::NotInitialized).unwrap();
assert!(buf.contains("init_genesis"), "{}", buf);
}
#[cfg(feature = "rocksdb-storage")]
#[test]
fn vv_req_api_004_genesis_already_initialized_reachable_from_store() {
use dig_coinstore::coin_store::CoinStore;
let dir = helpers::temp_dir();
let mut store = CoinStore::new(dir.path()).unwrap();
store.init_genesis(vec![], 1).unwrap();
let err = store.init_genesis(vec![], 2).unwrap_err();
assert!(
matches!(err, CoinStoreError::GenesisAlreadyInitialized),
"got {:?}",
err
);
}
#[test]
fn vv_req_api_004_storage_error_from_conversion() {
let inner = StorageError::UnknownColumnFamily("cf_x".into());
let outer: CoinStoreError = inner.into();
match outer {
CoinStoreError::StorageError(s) => {
assert!(s.contains("cf_x"), "{}", s);
}
other => panic!("expected StorageError variant, got {:?}", other),
}
}
#[test]
fn vv_req_api_004_bincode_error_maps_via_from_to_serialization_error() {
let io_err = std::io::Error::other("bincode io shim");
let b_err: bincode::Error = io_err.into();
let mapped: CoinStoreError = b_err.into();
assert!(
matches!(mapped, CoinStoreError::SerializationError(_)),
"{:?}",
mapped
);
}
#[test]
fn vv_req_api_004_bincode_decode_maps_to_deserialization_error() {
let err = bincode::deserialize::<u64>(&[0u8; 1]).unwrap_err();
let mapped = CoinStoreError::from_bincode_deserialize(err);
assert!(
matches!(mapped, CoinStoreError::DeserializationError(_)),
"{:?}",
mapped
);
}
#[cfg(feature = "rocksdb-storage")]
#[test]
fn vv_req_api_004_from_rocksdb_error_stringifies() {
let dir = tempfile::tempdir().unwrap();
let bad = dir.path().join("not_a_directory");
std::fs::write(&bad, b"block").unwrap();
let inner = rocksdb::DB::open(&rocksdb::Options::default(), &bad).unwrap_err();
let outer: CoinStoreError = inner.into();
match outer {
CoinStoreError::StorageError(s) => assert!(!s.is_empty(), "{}", s),
other => panic!("expected StorageError, got {:?}", other),
}
}
#[cfg(feature = "lmdb-storage")]
#[test]
fn vv_req_api_004_from_heed_error_stringifies() {
let inner = heed::Error::Io(std::io::Error::other("heed test"));
let outer: CoinStoreError = inner.into();
match outer {
CoinStoreError::StorageError(s) => assert!(!s.is_empty(), "{}", s),
other => panic!("expected StorageError, got {:?}", other),
}
}