#![cfg(not(target_arch = "wasm32"))]
use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
use std::sync::Arc;
use std::time::{Duration, Instant};
use kevy_config::Config;
use kevy_resp::Argv;
use kevy_store::Store;
fn spawn_mock_target() -> (u16, std::sync::mpsc::Receiver<Vec<u8>>) {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let port = listener.local_addr().unwrap().port();
let (tx, rx) = std::sync::mpsc::channel();
std::thread::spawn(move || {
let (mut s, _) = listener.accept().expect("mock target accept");
s.set_read_timeout(Some(Duration::from_secs(5))).ok();
let mut buf = Vec::new();
let mut chunk = [0u8; 8192];
let deadline = Instant::now() + Duration::from_secs(2);
loop {
match s.read(&mut chunk) {
Ok(0) => break,
Ok(n) => buf.extend_from_slice(&chunk[..n]),
Err(_) => break,
}
if buf.len() >= 32 && Instant::now() > deadline - Duration::from_secs(1) {
break;
}
if Instant::now() > deadline {
break;
}
}
let _ = s.write_all(b"+OK 1\r\n");
let _ = tx.send(buf);
});
for _ in 0..50 {
if TcpStream::connect_timeout(
&std::net::SocketAddr::from(([127, 0, 0, 1], port)),
Duration::from_millis(20),
)
.is_ok()
{
break;
}
std::thread::sleep(Duration::from_millis(5));
}
(port, rx)
}
fn argv(parts: &[&[u8]]) -> Argv {
let mut a = Argv::default();
for p in parts {
a.push(p);
}
a
}
#[test]
fn move_scope_ships_prefix_slice_to_mock_target_and_commits() {
let (port, rx) = spawn_mock_target();
let mut cfg = Config::default();
cfg.cluster.node_id = "A".to_string();
cfg.cluster.peers = kevy_config::PeerEntry::parse_list(&format!(
"A@127.0.0.1:11000,B@127.0.0.1:{port}",
))
.unwrap();
let kevy = kevy::KevyCommands::with_state(Arc::new(
kevy::RuntimeState::new(Arc::new(cfg), std::path::PathBuf::new(), 1).unwrap(),
));
let mut store = Store::new();
store.set(b"test:a", b"1".to_vec(), None, false, false);
store.set(b"test:b", b"2".to_vec(), None, false, false);
let args = argv(&[b"MOVE-SCOPE", b"test:", b"FROM", b"A", b"TO", b"B"]);
let reply = kevy.dispatch(&mut store, &args);
let reply_s = String::from_utf8_lossy(&reply).to_string();
assert!(
reply_s.starts_with('+') || reply_s.starts_with('-'),
"reply should be a simple-string or error: {reply_s:?}"
);
if reply_s.starts_with('+') {
let request = rx
.recv_timeout(Duration::from_secs(2))
.expect("mock target should have received the request");
let req_s = String::from_utf8_lossy(&request);
assert!(
req_s.contains("MOVE-SCOPE-INGEST"),
"request shape: {req_s:?}",
);
assert!(req_s.contains("test:a"), "key 1 in request: {req_s:?}");
assert!(req_s.contains("test:b"), "key 2 in request: {req_s:?}");
}
}