use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use vti_common::config::StoreConfig;
use vti_common::store::{KeyspaceHandle, Store};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
struct Sample {
n: u32,
label: String,
}
fn local_handle() -> (KeyspaceHandle, tempfile::TempDir) {
let dir = tempfile::tempdir().expect("tempdir");
let store = Store::open(&StoreConfig {
data_dir: dir.path().to_path_buf(),
})
.expect("open store");
let ks = store.keyspace("conformance").expect("keyspace");
(ks, dir)
}
#[tokio::test]
async fn typed_insert_get_round_trip() {
let (ks, _dir) = local_handle();
let v = Sample {
n: 42,
label: "hello".into(),
};
ks.insert("k1", &v).await.expect("insert");
let loaded: Option<Sample> = ks.get("k1").await.expect("get");
assert_eq!(loaded, Some(v));
}
#[tokio::test]
async fn raw_insert_get_round_trip() {
let (ks, _dir) = local_handle();
let bytes = b"raw bytes".to_vec();
ks.insert_raw("k1", bytes.clone()).await.expect("insert");
let loaded = ks.get_raw("k1").await.expect("get").expect("present");
assert_eq!(loaded, bytes);
}
#[tokio::test]
async fn get_returns_none_for_missing_key() {
let (ks, _dir) = local_handle();
let loaded: Option<Sample> = ks.get("never-existed").await.expect("get");
assert!(loaded.is_none());
let loaded_raw = ks.get_raw("never-existed").await.expect("get_raw");
assert!(loaded_raw.is_none());
}
#[tokio::test]
async fn insert_overwrites_existing_value() {
let (ks, _dir) = local_handle();
ks.insert_raw("k1", b"v1".to_vec()).await.unwrap();
ks.insert_raw("k1", b"v2".to_vec()).await.unwrap();
let v = ks.get_raw("k1").await.unwrap().unwrap();
assert_eq!(v, b"v2");
}
#[tokio::test]
async fn remove_makes_get_return_none() {
let (ks, _dir) = local_handle();
ks.insert_raw("k1", b"v1".to_vec()).await.unwrap();
ks.remove("k1").await.unwrap();
assert!(ks.get_raw("k1").await.unwrap().is_none());
}
#[tokio::test]
async fn remove_missing_key_is_noop() {
let (ks, _dir) = local_handle();
ks.remove("never-existed").await.expect("remove");
}
#[tokio::test]
async fn prefix_iter_returns_only_matching_keys() {
let (ks, _dir) = local_handle();
ks.insert_raw("foo:1", b"a".to_vec()).await.unwrap();
ks.insert_raw("foo:2", b"b".to_vec()).await.unwrap();
ks.insert_raw("bar:1", b"c".to_vec()).await.unwrap();
let pairs = ks.prefix_iter_raw("foo:").await.unwrap();
let keys: HashSet<String> = pairs
.iter()
.map(|(k, _)| String::from_utf8_lossy(k).into_owned())
.collect();
assert_eq!(
keys,
HashSet::from(["foo:1".into(), "foo:2".into()]),
"prefix scan must return exactly the matching keys"
);
}
#[tokio::test]
async fn prefix_iter_empty_prefix_returns_all() {
let (ks, _dir) = local_handle();
ks.insert_raw("a", b"1".to_vec()).await.unwrap();
ks.insert_raw("b", b"2".to_vec()).await.unwrap();
ks.insert_raw("c", b"3".to_vec()).await.unwrap();
let pairs = ks.prefix_iter_raw("").await.unwrap();
assert_eq!(pairs.len(), 3);
}
#[tokio::test]
async fn prefix_iter_no_match_returns_empty() {
let (ks, _dir) = local_handle();
ks.insert_raw("foo", b"v".to_vec()).await.unwrap();
let pairs = ks.prefix_iter_raw("bar").await.unwrap();
assert!(pairs.is_empty());
}
#[tokio::test]
async fn prefix_keys_returns_keys_only() {
let (ks, _dir) = local_handle();
ks.insert_raw("k1", b"v1".to_vec()).await.unwrap();
ks.insert_raw("k2", b"v2".to_vec()).await.unwrap();
let mut keys: Vec<String> = ks
.prefix_keys("")
.await
.unwrap()
.into_iter()
.map(|k| String::from_utf8_lossy(&k).into_owned())
.collect();
keys.sort();
assert_eq!(keys, vec!["k1", "k2"]);
}
#[tokio::test]
async fn large_value_round_trips() {
let (ks, _dir) = local_handle();
let big = vec![0xABu8; 256 * 1024];
ks.insert_raw("big", big.clone()).await.unwrap();
let back = ks.get_raw("big").await.unwrap().unwrap();
assert_eq!(back, big);
}
#[tokio::test]
async fn binary_safe_keys_round_trip() {
let (ks, _dir) = local_handle();
let key = vec![0x00, 0xff, 0x42, 0x00, 0x99];
ks.insert_raw(key.clone(), b"v".to_vec()).await.unwrap();
let v = ks.get_raw(key.clone()).await.unwrap().unwrap();
assert_eq!(v, b"v");
let pairs = ks.prefix_iter_raw(vec![0x00u8]).await.unwrap();
assert_eq!(pairs.len(), 1);
assert_eq!(pairs[0].0, key);
}
#[tokio::test]
async fn empty_value_round_trips() {
let (ks, _dir) = local_handle();
ks.insert_raw("k", Vec::<u8>::new()).await.unwrap();
let v = ks.get_raw("k").await.unwrap().unwrap();
assert!(
v.is_empty(),
"empty-value round-trip must preserve length 0"
);
}
#[tokio::test]
async fn approximate_len_grows_with_inserts() {
let (ks, _dir) = local_handle();
let before = ks.approximate_len().await.unwrap();
for i in 0..10 {
ks.insert_raw(format!("k{i}"), vec![0u8; 16]).await.unwrap();
}
let after = ks.approximate_len().await.unwrap();
assert!(
after > before,
"approximate_len must grow after inserts (before={before}, after={after})"
);
}