Milestone 8 (phases 1-4): - Shard-aware WAL segment naming, BatchHeader v2, ShardRouter - Transport trait, InProcessTransport, WalShipper, FollowerDb - HLC, PNCounter, LWWRegister, CrdtSignalState, ReconciliationEngine - Session replication bridge with SeqNo/HWM, idempotency store Forage application: - Multi-source discovery engine with MAB exploration - Embedding-based label system, server handlers, UI refresh Other: - QUICKSTART.md, README.md, milestone-8 planning docs - Hard negative union semantics, RLHF export enhancements - Recovery benchmark and visibility test expansions - Split 8 oversized source files per CODING_GUIDELINES §9 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
207 lines
6.1 KiB
Rust
207 lines
6.1 KiB
Rust
use super::*;
|
|
|
|
#[test]
|
|
fn builder_ephemeral_succeeds() {
|
|
let db = TidalDb::builder().ephemeral().open();
|
|
assert!(db.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn builder_default_is_ephemeral() {
|
|
let db = TidalDb::builder().open();
|
|
assert!(db.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn builder_persistent_requires_data_dir() {
|
|
// Construct a persistent-mode builder without calling with_data_dir
|
|
// by manually setting mode.
|
|
let builder = TidalDbBuilder {
|
|
config: Config {
|
|
mode: StorageMode::Persistent,
|
|
data_dir: None,
|
|
wal_dir: None,
|
|
cache_dir: None,
|
|
cluster: Default::default(),
|
|
},
|
|
metrics_addr: None,
|
|
schema: None,
|
|
rate_limiter_config: None,
|
|
};
|
|
let result = builder.validate();
|
|
assert!(result.is_err());
|
|
let err = result.expect_err("should fail");
|
|
assert!(
|
|
matches!(err, ConfigError::MissingDataDir),
|
|
"expected MissingDataDir, got: {err}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn builder_persistent_missing_dir() {
|
|
let result = TidalDb::builder()
|
|
.with_data_dir("/nonexistent/path/that/does/not/exist")
|
|
.open();
|
|
assert!(result.is_err());
|
|
let err_msg = result.expect_err("should fail").to_string();
|
|
assert!(
|
|
err_msg.contains("does not exist"),
|
|
"expected DirectoryNotFound, got: {err_msg}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn builder_persistent_existing_dir() {
|
|
let tmp = tempfile::tempdir().expect("failed to create tempdir");
|
|
let result = TidalDb::builder().with_data_dir(tmp.path()).open();
|
|
assert!(result.is_ok(), "open with valid tempdir should succeed");
|
|
}
|
|
|
|
#[test]
|
|
fn health_check_ok() {
|
|
let db = TidalDb::builder().ephemeral().open().expect("open failed");
|
|
assert!(db.health_check().is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn close_ok() {
|
|
let db = TidalDb::builder().ephemeral().open().expect("open failed");
|
|
assert!(db.close().is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn builder_with_wal_and_cache_dir() {
|
|
let tmp = tempfile::tempdir().expect("failed to create tempdir");
|
|
let wal = tmp.path().join("wal");
|
|
let cache = tmp.path().join("cache");
|
|
std::fs::create_dir_all(&wal).expect("mkdir wal");
|
|
std::fs::create_dir_all(&cache).expect("mkdir cache");
|
|
|
|
let result = TidalDb::builder()
|
|
.with_data_dir(tmp.path())
|
|
.wal_dir(&wal)
|
|
.cache_dir(&cache)
|
|
.open();
|
|
assert!(
|
|
result.is_ok(),
|
|
"open with explicit wal/cache dirs should succeed"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn builder_ephemeral_resets_dirs() {
|
|
let builder = TidalDb::builder()
|
|
.with_data_dir("/some/path")
|
|
.wal_dir("/some/wal")
|
|
.cache_dir("/some/cache")
|
|
.ephemeral();
|
|
|
|
assert_eq!(builder.config.mode, StorageMode::Ephemeral);
|
|
assert!(builder.config.data_dir.is_none());
|
|
assert!(builder.config.wal_dir.is_none());
|
|
assert!(builder.config.cache_dir.is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn builder_wal_dir_nonexistent() {
|
|
let tmp = tempfile::tempdir().expect("failed to create tempdir");
|
|
let result = TidalDb::builder()
|
|
.with_data_dir(tmp.path())
|
|
.wal_dir("/nonexistent/wal")
|
|
.open();
|
|
assert!(result.is_err());
|
|
let err_msg = result.expect_err("should fail").to_string();
|
|
assert!(err_msg.contains("does not exist"));
|
|
}
|
|
|
|
#[test]
|
|
fn resolve_defaults_sets_wal_and_cache() {
|
|
let tmp = tempfile::tempdir().expect("failed to create tempdir");
|
|
let mut builder = TidalDb::builder().with_data_dir(tmp.path());
|
|
assert!(builder.config.wal_dir.is_none());
|
|
assert!(builder.config.cache_dir.is_none());
|
|
|
|
builder.resolve_defaults();
|
|
|
|
let paths = Paths::new(tmp.path());
|
|
assert_eq!(builder.config.wal_dir.as_ref(), Some(&paths.wal_dir()));
|
|
assert_eq!(builder.config.cache_dir.as_ref(), Some(&paths.cache_dir()));
|
|
}
|
|
|
|
#[test]
|
|
fn resolve_defaults_preserves_explicit_overrides() {
|
|
let tmp = tempfile::tempdir().expect("failed to create tempdir");
|
|
let custom_wal = tmp.path().join("custom_wal");
|
|
let custom_cache = tmp.path().join("custom_cache");
|
|
|
|
let mut builder = TidalDb::builder()
|
|
.with_data_dir(tmp.path())
|
|
.wal_dir(&custom_wal)
|
|
.cache_dir(&custom_cache);
|
|
|
|
builder.resolve_defaults();
|
|
|
|
assert_eq!(builder.config.wal_dir.as_ref(), Some(&custom_wal));
|
|
assert_eq!(builder.config.cache_dir.as_ref(), Some(&custom_cache));
|
|
}
|
|
|
|
#[test]
|
|
fn resolve_defaults_noop_for_ephemeral() {
|
|
let mut builder = TidalDb::builder().ephemeral();
|
|
builder.resolve_defaults();
|
|
|
|
assert!(builder.config.wal_dir.is_none());
|
|
assert!(builder.config.cache_dir.is_none());
|
|
}
|
|
|
|
// ── Fix A: Directory lock tests ─────────────────────────────────────
|
|
|
|
#[test]
|
|
fn dual_open_same_directory_fails() {
|
|
let dir = tempfile::tempdir().expect("tempdir");
|
|
// First open succeeds (no schema -- M0 mode, but still persistent).
|
|
let _db1 = TidalDb::builder()
|
|
.with_data_dir(dir.path().to_path_buf())
|
|
.open()
|
|
.expect("first open should succeed");
|
|
|
|
// Second open on the same directory must fail with DataDirLocked.
|
|
let result = TidalDb::builder()
|
|
.with_data_dir(dir.path().to_path_buf())
|
|
.open();
|
|
assert!(result.is_err(), "expected error for dual open");
|
|
let err_msg = result.expect_err("should fail").to_string();
|
|
assert!(
|
|
err_msg.contains("already open"),
|
|
"expected DataDirLocked, got: {err_msg}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn lock_released_after_close() {
|
|
let dir = tempfile::tempdir().expect("tempdir");
|
|
{
|
|
let db = TidalDb::builder()
|
|
.with_data_dir(dir.path().to_path_buf())
|
|
.open()
|
|
.expect("open");
|
|
db.close().expect("close");
|
|
}
|
|
// After close (and drop), the lock should be released.
|
|
let db2 = TidalDb::builder()
|
|
.with_data_dir(dir.path().to_path_buf())
|
|
.open();
|
|
assert!(
|
|
db2.is_ok(),
|
|
"second open after close should succeed: {:?}",
|
|
db2.err()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn ephemeral_mode_skips_lock() {
|
|
// Two ephemeral databases should both succeed (no lock file).
|
|
let _db1 = TidalDb::builder().ephemeral().open().expect("open 1");
|
|
let _db2 = TidalDb::builder().ephemeral().open().expect("open 2");
|
|
}
|