use chrono::Utc;
use kcode_kweb_db::{Config, Error, KwebDb, NodeData, NoopGossip, Owner, Provenance, WriterId};
use std::{fs, sync::Arc};
fn provenance(label: &str) -> Provenance {
Provenance {
author: "Recovery".into(),
source: label.into(),
source_created_at: Utc::now(),
data: String::new(),
}
}
fn data(name: &str) -> NodeData {
NodeData {
short_name: name.into(),
short_description: String::new(),
long_description: String::new(),
owner: Owner::SelfNode,
fixed_connections: Vec::new(),
recent_connections: Vec::new(),
objects: Vec::new(),
}
}
fn config(key: [u8; 32]) -> Config {
Config {
signing_key: key,
writers_by_priority: vec![WriterId::from_signing_key(&key)],
gossip: Arc::new(NoopGossip),
}
}
#[test]
fn normal_open_does_not_scan_log_or_rebuild_nodes() {
let root = tempfile::tempdir().unwrap();
let key = [31; 32];
let database = KwebDb::open(root.path(), config(key)).unwrap();
let mut transaction = database.start_transaction(provenance("create")).unwrap();
let node = transaction.create_node(data("Durable node")).unwrap();
transaction.finalize().unwrap();
drop(database);
fs::write(
root.path().join("transactions.kwl"),
b"not scanned at ordinary open",
)
.unwrap();
let reopened = KwebDb::open(root.path(), config(key)).unwrap();
assert_eq!(
reopened.get_node(node).unwrap().data.short_name,
"Durable node"
);
}
#[test]
fn corrupt_or_missing_authoritative_node_fails_on_access() {
let root = tempfile::tempdir().unwrap();
let key = [32; 32];
let database = KwebDb::open(root.path(), config(key)).unwrap();
let mut transaction = database.start_transaction(provenance("create")).unwrap();
let node = transaction.create_node(data("Corrupt node")).unwrap();
transaction.finalize().unwrap();
drop(database);
let text = node.to_string();
let path = root
.path()
.join("nodes")
.join(&text[..2])
.join(format!("{}.kwn", &text[2..]));
fs::remove_file(path).unwrap();
let reopened = KwebDb::open(root.path(), config(key)).unwrap();
assert!(matches!(reopened.get_node(node), Err(Error::Corrupt(_))));
}
#[test]
fn legacy_root_requires_offline_upgrade_and_lock_is_exclusive() {
let legacy = tempfile::tempdir().unwrap();
fs::write(legacy.path().join("ledger.kwl"), b"v1").unwrap();
assert!(matches!(
KwebDb::open(legacy.path(), config([33; 32])),
Err(Error::OfflineUpgradeRequired(_))
));
let root = tempfile::tempdir().unwrap();
let first = KwebDb::open(root.path(), config([34; 32])).unwrap();
assert!(matches!(
KwebDb::open(root.path(), config([34; 32])),
Err(Error::Busy(_))
));
drop(first);
assert!(KwebDb::open(root.path(), config([34; 32])).is_ok());
}
#[test]
fn writer_priority_is_part_of_the_persisted_root_contract() {
let root = tempfile::tempdir().unwrap();
let local_key = [35; 32];
let local_writer = WriterId::from_signing_key(&local_key);
let other_writer = WriterId::from_signing_key(&[36; 32]);
let original = Config {
signing_key: local_key,
writers_by_priority: vec![local_writer, other_writer],
gossip: Arc::new(NoopGossip),
};
drop(KwebDb::open(root.path(), original).unwrap());
let reordered = Config {
signing_key: local_key,
writers_by_priority: vec![other_writer, local_writer],
gossip: Arc::new(NoopGossip),
};
assert!(matches!(
KwebDb::open(root.path(), reordered),
Err(Error::InvalidConfig(_))
));
}