#![allow(dead_code)]
mod common;
#[allow(unused_imports)]
use common::*;
use serde_json::json;
use std::time::Duration;
const NS: &str = "delta-sync-connected-e2e";
fn wait_tick() {
std::thread::sleep(Duration::from_secs(3));
}
#[test]
fn delta_sync_background_loop_applies_ops() {
let server = TestServer::spawn();
let c = server.client();
let b = format!("{}/api", server.url);
let h = vec![("X-Namespace-ID", NS)];
let (s, body) = post_json(
&c,
&format!("{}/namespaces", b),
&[],
&json!({"namespace_id": NS}),
);
assert!((200..300).contains(&s), "create namespace: {}", body);
post_json(
&c,
&format!("{}/intents", b),
&h,
&json!({"id": "greet", "phrases": ["hello", "hi there", "hey"]}),
);
post_json(
&c,
&format!("{}/intents", b),
&h,
&json!({"id": "farewell", "phrases": ["goodbye", "see you", "bye"]}),
);
let engine = microresolve::MicroResolve::new(microresolve::MicroResolveConfig {
server: Some(microresolve::ServerConfig {
url: server.url.clone(),
api_key: Some(server.api_key.clone()),
subscribe: vec![NS.to_string()],
tick_interval_secs: 1,
log_buffer_max: 100,
}),
..Default::default()
})
.expect("connected engine boots");
assert_eq!(
engine.namespace(NS).intent_count(),
2,
"connected engine should have 2 intents after initial full sync"
);
let (s, _) = post_json(
&c,
&format!("{}/intents/greet/phrases", b),
&h,
&json!({"phrase": "howdy partner", "lang": "en"}),
);
assert!((200..300).contains(&s), "add phrase HTTP");
wait_tick();
let (result, _) =
engine
.namespace(NS)
.resolve_with_options("howdy partner", Some(0.05), 1.5, 0.05, false);
let matched = result.intents.iter().any(|m| m.id == "greet");
assert!(
matched,
"connected engine should route 'howdy partner' to 'greet' after delta sync; got: {:?}",
result.intents
);
let version_after_phrase_sync = engine.namespace(NS).version();
assert!(
version_after_phrase_sync > 0,
"connected engine version must be non-zero"
);
let (s, body) = post_json(
&c,
&format!("{}/namespaces/decay", b),
&[],
&json!({"namespace_id": NS, "queries": ["goodbye"], "alpha": 0.2}),
);
assert_eq!(s, 200, "decay: {}", body);
let (s, sync_body) = post_json(
&c,
&format!("{}/sync", b),
&[],
&json!({
"local_versions": { NS: version_after_phrase_sync },
"logs": [],
"corrections": [],
}),
);
assert_eq!(s, 200, "sync probe: {}", sync_body);
let sync_val: serde_json::Value = serde_json::from_str(&sync_body).expect("sync parse");
let server_version_after_decay = sync_val["namespaces"][NS]["version"]
.as_u64()
.expect("server version field");
if server_version_after_decay > version_after_phrase_sync {
wait_tick();
let (s2, sync_body2) = post_json(
&c,
&format!("{}/sync", b),
&[],
&json!({
"local_versions": { NS: server_version_after_decay },
"logs": [],
"corrections": [],
}),
);
assert_eq!(s2, 200, "second sync probe: {}", sync_body2);
let sv2: serde_json::Value = serde_json::from_str(&sync_body2).expect("sync parse 2");
assert_eq!(
sv2["namespaces"][NS]["up_to_date"], true,
"server should confirm up_to_date after decay"
);
let (result2, _) = engine.namespace(NS).resolve_with_options(
"howdy partner",
Some(0.05),
1.5,
0.05,
false,
);
let still_routes = result2.intents.iter().any(|m| m.id == "greet");
assert!(
still_routes,
"routing must survive decay delta sync; got: {:?}",
result2.intents
);
}
delete_json(
&c,
&format!("{}/namespaces", b),
&[],
&json!({"namespace_id": NS}),
);
}