mod common;
use common::{append_n, counter_reducer};
use eventfold::{Event, EventLog, Snapshot, View};
use serde_json::json;
use std::fs;
use tempfile::tempdir;
#[test]
fn test_valid_snapshot_accepted() {
let dir = tempdir().unwrap();
let mut log = EventLog::open(dir.path()).unwrap();
append_n(&mut log, 5);
let mut view: View<u64> = View::new("counter", counter_reducer, log.views_dir());
view.refresh(&log.reader()).unwrap();
assert_eq!(*view.state(), 5);
append_n(&mut log, 3);
view.refresh(&log.reader()).unwrap();
assert_eq!(*view.state(), 8);
}
#[test]
fn test_offset_beyond_eof() {
let dir = tempdir().unwrap();
let views_dir = {
let mut log = EventLog::open(dir.path()).unwrap();
append_n(&mut log, 5);
let mut view: View<u64> = View::new("counter", counter_reducer, log.views_dir());
view.refresh(&log.reader()).unwrap();
assert_eq!(*view.state(), 5);
log.views_dir().to_path_buf()
};
let log_path = dir.path().join("app.jsonl");
let file = fs::OpenOptions::new()
.write(true)
.open(&log_path)
.unwrap();
file.set_len(10).unwrap();
drop(file);
let log = EventLog::open(dir.path()).unwrap();
let mut view: View<u64> = View::new("counter", counter_reducer, &views_dir);
let state = view.refresh(&log.reader()).unwrap();
assert!(*state < 5);
}
#[test]
fn test_hash_mismatch() {
let dir = tempdir().unwrap();
let views_dir = {
let mut log = EventLog::open(dir.path()).unwrap();
append_n(&mut log, 5);
let mut view: View<u64> = View::new("counter", counter_reducer, log.views_dir());
view.refresh(&log.reader()).unwrap();
assert_eq!(*view.state(), 5);
log.views_dir().to_path_buf()
};
let log_path = dir.path().join("app.jsonl");
let content = fs::read_to_string(&log_path).unwrap();
let lines: Vec<&str> = content.lines().collect();
let modified_event = Event::new("modified_event", json!({"tampered": true}));
let modified_json = serde_json::to_string(&modified_event).unwrap();
let mut new_content = String::new();
for line in &lines[..lines.len() - 1] {
new_content.push_str(line);
new_content.push('\n');
}
new_content.push_str(&modified_json);
new_content.push('\n');
fs::write(&log_path, &new_content).unwrap();
let log = EventLog::open(dir.path()).unwrap();
let mut view: View<u64> = View::new("counter", counter_reducer, &views_dir);
let state = view.refresh(&log.reader()).unwrap();
assert_eq!(*state, 5);
}
#[test]
fn test_empty_log_nonzero_offset() {
let dir = tempdir().unwrap();
let views_dir = {
let mut log = EventLog::open(dir.path()).unwrap();
append_n(&mut log, 5);
let mut view: View<u64> = View::new("counter", counter_reducer, log.views_dir());
view.refresh(&log.reader()).unwrap();
assert_eq!(*view.state(), 5);
log.views_dir().to_path_buf()
};
let log_path = dir.path().join("app.jsonl");
fs::write(&log_path, "").unwrap();
let log = EventLog::open(dir.path()).unwrap();
let mut view: View<u64> = View::new("counter", counter_reducer, &views_dir);
let state = view.refresh(&log.reader()).unwrap();
assert_eq!(*state, 0); }
#[test]
fn test_offset_zero_always_valid() {
let dir = tempdir().unwrap();
let log = EventLog::open(dir.path()).unwrap();
let snapshot_path = log.views_dir().join("counter.snapshot.json");
let snap = Snapshot::new(42u64, 0, String::new());
eventfold::snapshot::save(&snapshot_path, &snap).unwrap();
let mut view: View<u64> = View::new("counter", counter_reducer, log.views_dir());
let state = view.refresh(&log.reader()).unwrap();
assert_eq!(*state, 42);
}
#[test]
fn test_rebuild_correctness_after_integrity_failure() {
let dir = tempdir().unwrap();
let mut log = EventLog::open(dir.path()).unwrap();
append_n(&mut log, 10);
{
let mut view: View<u64> = View::new("counter", counter_reducer, log.views_dir());
view.refresh(&log.reader()).unwrap();
assert_eq!(*view.state(), 10);
}
let snapshot_path = log.views_dir().join("counter.snapshot.json");
let bogus_snap = Snapshot::new(9999u64, 999999, "bogus".to_string());
eventfold::snapshot::save(&snapshot_path, &bogus_snap).unwrap();
let mut view: View<u64> = View::new("counter", counter_reducer, log.views_dir());
let state = view.refresh(&log.reader()).unwrap();
assert_eq!(*state, 10);
}
#[test]
fn test_manual_log_edit_detected() {
let dir = tempdir().unwrap();
let views_dir = {
let mut log = EventLog::open(dir.path()).unwrap();
append_n(&mut log, 3);
let mut view: View<u64> = View::new("counter", counter_reducer, log.views_dir());
view.refresh(&log.reader()).unwrap();
assert_eq!(*view.state(), 3);
log.views_dir().to_path_buf()
};
let log_path = dir.path().join("app.jsonl");
let content = fs::read_to_string(&log_path).unwrap();
let lines: Vec<&str> = content.lines().collect();
let mut new_content = String::new();
for (i, line) in lines.iter().enumerate() {
new_content.push_str(line);
new_content.push('\n');
if i == 1 {
let extra = Event::new("inserted", json!({"extra": true}));
let extra_json = serde_json::to_string(&extra).unwrap();
new_content.push_str(&extra_json);
new_content.push('\n');
}
}
fs::write(&log_path, &new_content).unwrap();
let log = EventLog::open(dir.path()).unwrap();
let mut view: View<u64> = View::new("counter", counter_reducer, &views_dir);
let state = view.refresh(&log.reader()).unwrap();
assert_eq!(*state, 4);
}
#[test]
fn test_no_false_positives() {
let dir = tempdir().unwrap();
let mut log = EventLog::open(dir.path()).unwrap();
let mut view: View<u64> = View::new("counter", counter_reducer, log.views_dir());
for batch in 1..=10 {
append_n(&mut log, 5);
let state = view.refresh(&log.reader()).unwrap();
assert_eq!(*state, batch * 5);
}
drop(view);
let mut view: View<u64> = View::new("counter", counter_reducer, log.views_dir());
let state = view.refresh(&log.reader()).unwrap();
assert_eq!(*state, 50);
append_n(&mut log, 5);
let state = view.refresh(&log.reader()).unwrap();
assert_eq!(*state, 55);
}