Skip to content

Commit 3935e48

Browse files
pimpinclaude
andcommitted
Add tombstone_quick_check example for rapid validation
Add a fast-running test (~30 seconds) to validate tombstone detection in CBS without waiting for purge intervals. Test scenario: 1. Create document → verify LIVE in CBS 2. Delete document → verify TOMBSTONE in CBS 3. Re-create document → verify LIVE in CBS 4. Check replication flags throughout This example is useful for: - Quickly validating _sync xattr query corrections - Debugging tombstone visibility issues - Understanding tombstone lifecycle without long waits - Verifying that re-created documents are treated as new (flags=0) Runtime: ~30 seconds vs 6+ minutes for other tests 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 2736cc1 commit 3935e48

File tree

1 file changed

+142
-0
lines changed

1 file changed

+142
-0
lines changed

examples/tombstone_quick_check.rs

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
mod utils;
2+
3+
use couchbase_lite::*;
4+
use std::path::Path;
5+
use utils::*;
6+
7+
fn main() {
8+
println!("=== Tombstone Quick Check (30 seconds) ===");
9+
println!("This is a rapid validation test for tombstone detection via XATTRs.\n");
10+
11+
let mut db = Database::open(
12+
"tombstone_quick_check",
13+
Some(DatabaseConfiguration {
14+
directory: Path::new("./"),
15+
#[cfg(feature = "enterprise")]
16+
encryption_key: None,
17+
}),
18+
)
19+
.unwrap();
20+
21+
// Setup user with access to channel1 only
22+
add_or_update_user("quick_test_user", vec!["channel1".into()]);
23+
let session_token = get_session("quick_test_user");
24+
println!("Session token: {session_token}\n");
25+
26+
// Setup replicator with auto-purge enabled
27+
let mut repl =
28+
setup_replicator(db.clone(), session_token).add_document_listener(Box::new(doc_listener));
29+
30+
repl.start(false);
31+
std::thread::sleep(std::time::Duration::from_secs(3));
32+
33+
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
34+
println!("TEST 1: Create document and check CBS state");
35+
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
36+
37+
create_doc(&mut db, "quick_doc", "channel1");
38+
std::thread::sleep(std::time::Duration::from_secs(3));
39+
40+
println!("\n📊 CBS State after creation:");
41+
check_doc_in_cbs("quick_doc");
42+
println!("✓ Expected: Document exists as LIVE document\n");
43+
44+
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
45+
println!("TEST 2: Delete document and check CBS state");
46+
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
47+
48+
let mut doc = db.get_document("quick_doc").unwrap();
49+
db.delete_document(&mut doc).unwrap();
50+
println!("Document deleted locally");
51+
std::thread::sleep(std::time::Duration::from_secs(3));
52+
53+
println!("\n📊 CBS State after deletion:");
54+
check_doc_in_cbs("quick_doc");
55+
println!("✓ Expected: Document exists as TOMBSTONE (deleted: true)\n");
56+
57+
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
58+
println!("TEST 3: Re-create document and check CBS state");
59+
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
60+
61+
create_doc(&mut db, "quick_doc", "channel1");
62+
std::thread::sleep(std::time::Duration::from_secs(3));
63+
64+
println!("\n📊 CBS State after re-creation:");
65+
check_doc_in_cbs("quick_doc");
66+
println!("✓ Expected: Document exists as LIVE document\n");
67+
68+
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
69+
println!("TEST 4: Check replication flags");
70+
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
71+
72+
println!("Review the replication logs above:");
73+
println!(" - Initial creation: should have flags=0 (new)");
74+
println!(" - After deletion: should have flags=1 (deleted)");
75+
println!(" - After re-creation: should have flags=0 (new) ✓\n");
76+
77+
repl.stop(None);
78+
println!("=== Quick check complete ===");
79+
}
80+
81+
fn create_doc(db: &mut Database, id: &str, channel: &str) {
82+
let mut doc = Document::new_with_id(id);
83+
doc.set_properties_as_json(
84+
&serde_json::json!({
85+
"channels": channel,
86+
"test_data": "quick check",
87+
"timestamp": std::time::SystemTime::now()
88+
.duration_since(std::time::UNIX_EPOCH)
89+
.unwrap()
90+
.as_secs()
91+
})
92+
.to_string(),
93+
)
94+
.unwrap();
95+
db.save_document(&mut doc).unwrap();
96+
println!(" Created doc {id}");
97+
}
98+
99+
fn setup_replicator(db: Database, session_token: String) -> Replicator {
100+
let repl_conf = ReplicatorConfiguration {
101+
database: Some(db.clone()),
102+
endpoint: Endpoint::new_with_url(SYNC_GW_URL).unwrap(),
103+
replicator_type: ReplicatorType::PushAndPull,
104+
continuous: true,
105+
disable_auto_purge: false,
106+
max_attempts: 3,
107+
max_attempt_wait_time: 1,
108+
heartbeat: 60,
109+
authenticator: None,
110+
proxy: None,
111+
headers: vec![(
112+
"Cookie".to_string(),
113+
format!("SyncGatewaySession={session_token}"),
114+
)]
115+
.into_iter()
116+
.collect(),
117+
pinned_server_certificate: None,
118+
trusted_root_certificates: None,
119+
channels: MutableArray::default(),
120+
document_ids: MutableArray::default(),
121+
collections: None,
122+
accept_parent_domain_cookies: false,
123+
#[cfg(feature = "enterprise")]
124+
accept_only_self_signed_server_certificate: false,
125+
};
126+
let repl_context = ReplicationConfigurationContext::default();
127+
Replicator::new(repl_conf, Box::new(repl_context)).unwrap()
128+
}
129+
130+
fn doc_listener(direction: Direction, documents: Vec<ReplicatedDocument>) {
131+
for document in documents {
132+
let flag_meaning = match document.flags {
133+
0 => "NEW",
134+
1 => "DELETED",
135+
_ => "OTHER",
136+
};
137+
println!(
138+
" 📡 Replicated [{:?}]: {} (flags={} - {})",
139+
direction, document.id, document.flags, flag_meaning
140+
);
141+
}
142+
}

0 commit comments

Comments
 (0)