tidaldb/tidal/src/db/builder_tests.rs
jordan f4cfd6c81f feat: complete M8 replication primitives + forage enhancements + docs
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>
2026-02-24 13:17:19 -07:00

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");
}