Skip to content

Commit b157328

Browse files
committed
Add turmoil test: reset_replication is idempotent
Verifies that calling POST /v1/namespaces/:ns/reset-replication three times in a row succeeds at each attempt and preserves data. This is a critical safety property: the streamer's retry-after-reset path (and any operator running the command twice) must not corrupt the data file through sequential invocations. Previously validated empirically via bench_recovery.sh (run 21 in autoresearch.jsonl: 12ms per call on healthy ns, no data loss) but was not pinned as a turmoil integration test. Now it's in CI. All 4 reset-replication / integrity-check turmoil tests pass (the 2 pre-existing meta_attach flakes reproduce without this change).
1 parent 7d4d26e commit b157328

1 file changed

Lines changed: 73 additions & 0 deletions

File tree

  • libsql-server/tests/namespaces

libsql-server/tests/namespaces/mod.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,3 +302,76 @@ fn reset_replication_on_nonexistent_namespace_returns_404() {
302302

303303
sim.run().unwrap();
304304
}
305+
306+
#[test]
307+
fn reset_replication_is_idempotent() {
308+
// An operator (or the streamer's retry-after-reset path) may call
309+
// reset-replication multiple times in quick succession. Each call
310+
// must succeed independently without corrupting the data file.
311+
let mut sim = Builder::new()
312+
.simulation_duration(Duration::from_secs(1000))
313+
.build();
314+
let tmp = tempdir().unwrap();
315+
make_primary(&mut sim, tmp.path().to_path_buf());
316+
317+
sim.client("client", async {
318+
let client = Client::new();
319+
client
320+
.post("http://primary:9090/v1/namespaces/idem/create", json!({}))
321+
.await?;
322+
323+
let db = Database::open_remote_with_connector(
324+
"http://idem.primary:8080",
325+
"",
326+
TurmoilConnector,
327+
)?;
328+
let conn = db.connect()?;
329+
conn.execute("create table t(v text)", ()).await?;
330+
for i in 0..20 {
331+
conn.execute(&format!("insert into t values ('row-{i}')"), ())
332+
.await?;
333+
}
334+
335+
// Call reset-replication three times in a row. Each must 200.
336+
for attempt in 0..3 {
337+
let resp = client
338+
.post(
339+
"http://primary:9090/v1/namespaces/idem/reset-replication",
340+
json!({}),
341+
)
342+
.await?;
343+
assert_eq!(
344+
resp.status(),
345+
hyper::http::StatusCode::OK,
346+
"attempt {attempt} should return 200"
347+
);
348+
}
349+
350+
// After three resets, the 20 rows are still there.
351+
let db2 = Database::open_remote_with_connector(
352+
"http://idem.primary:8080",
353+
"",
354+
TurmoilConnector,
355+
)?;
356+
let conn2 = db2.connect()?;
357+
let mut rows = conn2.query("select count(*) from t", ()).await?;
358+
assert!(matches!(
359+
rows.next().await.unwrap().unwrap().get_value(0)?,
360+
Value::Integer(20)
361+
));
362+
363+
// And integrity-check still passes.
364+
let resp = client
365+
.post(
366+
"http://primary:9090/v1/namespaces/idem/integrity-check",
367+
json!({}),
368+
)
369+
.await?;
370+
let v = resp.json_value().await?;
371+
assert_eq!(v["ok"], json!(true));
372+
373+
Ok(())
374+
});
375+
376+
sim.run().unwrap();
377+
}

0 commit comments

Comments
 (0)