mod common;
use std::time::Duration;
use common::{MockBackend, Scratch, request, within};
use modelpipe::{ConnectOptions, PipeStatus, ServeOptions, Ticket, TokenPolicy};
use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _};
const OK_BODY: &str = r#"{"object":"list","data":[]}"#;
async fn paired(
backend: &MockBackend,
auth: TokenPolicy,
) -> (modelpipe::ServeHandle, modelpipe::ConnectHandle, String) {
let mut serve_opts = ServeOptions::default();
serve_opts.auth = auth;
let serving = within(
"serve must bind",
Box::pin(modelpipe::serve(&backend.url, serve_opts)),
)
.await
.expect("serve");
let ticket = serving.ticket();
let connected = within(
"connect must pair with the listener",
Box::pin(modelpipe::connect(&ticket, ConnectOptions::default())),
)
.await
.expect("connect");
let url = connected.base_url();
(serving, connected, url)
}
#[tokio::test]
async fn a_pairing_still_forms_with_discovery_and_port_mapping_off() {
let backend = MockBackend::json(200, OK_BODY).await;
let mut serve_opts = ServeOptions::default();
serve_opts.auth = TokenPolicy::Generate;
serve_opts.port_mapping = false;
serve_opts.discovery = false;
let serving = within(
"serve must bind without discovery",
Box::pin(modelpipe::serve(&backend.url, serve_opts)),
)
.await
.expect("serve");
let mut connect_opts = ConnectOptions::default();
connect_opts.port_mapping = false;
connect_opts.discovery = false;
let connected = within(
"connect must pair on the ticket's own paths",
Box::pin(modelpipe::connect(&serving.ticket(), connect_opts)),
)
.await
.expect("connect");
let response = within(
"a request must cross the pipe",
request(&connected.base_url(), "/v1/models", Some(&bearer(&serving))),
)
.await
.expect("request");
assert!(response.starts_with("HTTP/1.1 200 OK"), "got: {response}");
connected.shutdown().await;
serving.shutdown().await;
}
async fn settles_on(handle: &modelpipe::ConnectHandle, wanted: PipeStatus) {
while handle.status() != wanted {
tokio::time::sleep(Duration::from_millis(10)).await;
}
}
fn bearer(handle: &modelpipe::ServeHandle) -> String {
format!("Bearer {}", handle.token().expect("a token is enforced"))
}
#[tokio::test]
async fn a_request_crosses_the_pipe_and_the_response_comes_back() {
let backend = MockBackend::json(200, OK_BODY).await;
let (serving, connected, url) = paired(&backend, TokenPolicy::Generate).await;
let response = within(
"a request must cross the pipe",
request(&url, "/v1/models", Some(&bearer(&serving))),
)
.await
.expect("request");
assert!(response.starts_with("HTTP/1.1 200 OK"), "got: {response}");
assert!(
response.contains(OK_BODY),
"the body must arrive: {response}"
);
assert_eq!(
backend.accepts(),
1,
"and the backend served it exactly once"
);
let sent = backend.received().await;
assert!(sent.contains("GET /v1/models"), "the path survives: {sent}");
assert!(
sent.contains(&format!(
"Host: {}",
backend.url.trim_start_matches("http://")
)),
"the Host names the backend: {sent}"
);
assert!(
sent.contains("Via: 1.1 modelpipe"),
"the backend is told the request came through the tunnel: {sent}"
);
let peer = sent
.lines()
.find_map(|line| line.strip_prefix("X-Modelpipe-Peer: "))
.expect("the backend is told which peer");
assert_eq!(peer.len(), 12, "a twelve-hex-character fingerprint: {peer}");
assert!(peer.chars().all(|c| c.is_ascii_hexdigit()), "{peer}");
connected.shutdown().await;
serving.shutdown().await;
}
#[tokio::test]
async fn the_base_url_is_something_a_client_can_actually_use() {
let backend = MockBackend::json(200, OK_BODY).await;
let (serving, connected, url) = paired(&backend, TokenPolicy::Generate).await;
assert!(
url.starts_with("http://127.0.0.1:"),
"loopback by default: {url}"
);
assert!(url.ends_with("/v1"), "and the OpenAI base path: {url}");
assert_eq!(
connected.local_addr().to_string(),
url.trim_start_matches("http://").trim_end_matches("/v1"),
"the URL names the port actually bound"
);
connected.shutdown().await;
serving.shutdown().await;
}
#[tokio::test]
async fn an_unauthorized_request_never_reaches_the_backend() {
let backend = MockBackend::json(200, OK_BODY).await;
let (serving, connected, url) = paired(&backend, TokenPolicy::Generate).await;
for auth in [None, Some("Bearer wrong"), Some("Basic whatever")] {
let response = within("a refusal must arrive", request(&url, "/v1/models", auth))
.await
.expect("request");
assert!(
response.starts_with("HTTP/1.1 401"),
"{auth:?} must be refused: {response}"
);
}
assert_eq!(
backend.accepts(),
0,
"after three refused requests the backend was never contacted"
);
connected.shutdown().await;
serving.shutdown().await;
}
#[tokio::test]
async fn serving_open_forwards_without_a_credential() {
let backend = MockBackend::json(200, OK_BODY).await;
let (serving, connected, url) = paired(&backend, TokenPolicy::InsecureNoAuth).await;
assert_eq!(serving.token(), None, "there is no token to report");
let response = within("must forward", request(&url, "/v1/models", None))
.await
.expect("request");
assert!(response.starts_with("HTTP/1.1 200"), "got: {response}");
connected.shutdown().await;
serving.shutdown().await;
}
#[tokio::test]
async fn rotating_the_token_leaves_the_ticket_and_the_live_pairing_intact() {
let backend = MockBackend::json(200, OK_BODY).await;
let (serving, connected, url) = paired(&backend, TokenPolicy::Generate).await;
let ticket_before = serving.ticket().to_string();
let old = bearer(&serving);
assert!(
within("first request", request(&url, "/v1/models", Some(&old)))
.await
.expect("request")
.starts_with("HTTP/1.1 200")
);
let fresh = serving.rotate_token();
assert_eq!(
serving.ticket().to_string(),
ticket_before,
"rotating a token must not disturb the ticket"
);
let refused = within("old credential", request(&url, "/v1/models", Some(&old)))
.await
.expect("request");
assert!(
refused.starts_with("HTTP/1.1 401"),
"the old token dies immediately: {refused}"
);
let accepted = within(
"new credential",
request(&url, "/v1/models", Some(&format!("Bearer {fresh}"))),
)
.await
.expect("request");
assert!(
accepted.starts_with("HTTP/1.1 200"),
"and the same pairing carries the new one: {accepted}"
);
connected.shutdown().await;
serving.shutdown().await;
}
#[tokio::test]
async fn a_supplied_credential_can_be_replaced_in_place() {
let backend = MockBackend::json(200, OK_BODY).await;
let (serving, connected, url) =
paired(&backend, TokenPolicy::Supplied("first-key".to_owned())).await;
assert_eq!(serving.token().as_deref(), Some("first-key"));
serving
.set_token("second-key".to_owned())
.expect("a usable token is installed");
assert!(
within("old", request(&url, "/v1/models", Some("Bearer first-key")))
.await
.expect("request")
.starts_with("HTTP/1.1 401")
);
assert!(
within(
"new",
request(&url, "/v1/models", Some("Bearer second-key"))
)
.await
.expect("request")
.starts_with("HTTP/1.1 200")
);
connected.shutdown().await;
serving.shutdown().await;
}
#[tokio::test]
async fn a_grant_admits_one_request_through_a_live_pipe_and_then_none() {
let backend = MockBackend::json(200, OK_BODY).await;
let (serving, connected, url) =
paired(&backend, TokenPolicy::Supplied("the-real-key".to_owned())).await;
serving
.grant_once("483920".to_owned(), Duration::from_mins(2))
.expect("a presentable code is granted");
let first = within(
"the code admits once",
request(&url, "/v1/models", Some("Bearer 483920")),
)
.await
.expect("request");
assert!(first.starts_with("HTTP/1.1 200"), "got: {first}");
let second = within(
"the spent code is a wrong token",
request(&url, "/v1/models", Some("Bearer 483920")),
)
.await
.expect("request");
assert!(second.starts_with("HTTP/1.1 401"), "got: {second}");
let token = within(
"the real key still works",
request(&url, "/v1/models", Some("Bearer the-real-key")),
)
.await
.expect("request");
assert!(token.starts_with("HTTP/1.1 200"), "got: {token}");
assert_eq!(serving.token().as_deref(), Some("the-real-key"));
assert_eq!(
backend.accepts(),
2,
"the refusal never reached the backend"
);
connected.shutdown().await;
serving.shutdown().await;
}
#[tokio::test]
async fn restarting_the_listener_mints_a_ticket_the_old_one_cannot_impersonate() {
let backend = MockBackend::json(200, OK_BODY).await;
let first = within(
"serve",
Box::pin(modelpipe::serve(&backend.url, ServeOptions::default())),
)
.await
.expect("serve");
let old_ticket = first.ticket().to_string();
first.shutdown().await;
drop(first);
let second = within(
"serve again",
Box::pin(modelpipe::serve(&backend.url, ServeOptions::default())),
)
.await
.expect("serve");
let new_ticket = second.ticket().to_string();
assert_ne!(
old_ticket, new_ticket,
"a restart must mint a different ticket"
);
let old: Ticket = old_ticket.parse().expect("the old ticket still parses");
let new: Ticket = new_ticket.parse().expect("parses");
assert_ne!(
old.fingerprint(),
new.fingerprint(),
"and a different identity, not merely different addresses"
);
second.shutdown().await;
}
#[tokio::test]
async fn a_streaming_response_arrives_as_it_is_produced() {
let backend =
MockBackend::streaming(&["data: one\n\n", "data: two\n\n", "data: [DONE]\n\n"]).await;
let (serving, connected, url) = paired(&backend, TokenPolicy::Generate).await;
let started = std::time::Instant::now();
let response = within(
"the stream must complete",
request(&url, "/v1/chat/completions", Some(&bearer(&serving))),
)
.await
.expect("request");
assert!(response.contains("data: one"), "got: {response}");
assert!(response.contains("data: [DONE]"), "got: {response}");
assert!(
started.elapsed() >= Duration::from_millis(60),
"the backend paused between frames, so a response that arrived \
instantly would mean the frames were produced before being sent"
);
connected.shutdown().await;
serving.shutdown().await;
}
#[tokio::test]
async fn a_shutdown_pipe_reports_closed_and_never_blocks_a_watcher() {
let backend = MockBackend::json(200, OK_BODY).await;
let (serving, connected, _url) = paired(&backend, TokenPolicy::Generate).await;
serving.shutdown().await;
assert_eq!(serving.status(), PipeStatus::Closed);
assert_eq!(
within(
"a closed pipe must not block a watcher",
serving.status_changed()
)
.await,
PipeStatus::Closed
);
connected.shutdown().await;
assert_eq!(connected.status(), PipeStatus::Closed);
}
#[tokio::test]
async fn a_completed_shutdown_releases_the_local_port() {
let backend = MockBackend::json(200, OK_BODY).await;
let (serving, connected, _url) = paired(&backend, TokenPolicy::Generate).await;
let port = connected.local_addr();
connected.shutdown().await;
drop(connected);
tokio::net::TcpListener::bind(port)
.await
.expect("the port must be free the moment shutdown returns");
serving.shutdown().await;
}
#[tokio::test]
async fn shutting_down_twice_is_harmless() {
let backend = MockBackend::json(200, OK_BODY).await;
let (serving, connected, _url) = paired(&backend, TokenPolicy::Generate).await;
serving.shutdown().await;
within("the second call must not hang", serving.shutdown()).await;
connected.shutdown().await;
within("nor on the connect side", connected.shutdown()).await;
}
#[tokio::test]
async fn a_serve_shutdown_lets_an_admitted_request_finish() {
let backend = MockBackend::streaming(&[
"data: one\n\n",
"data: two\n\n",
"data: three\n\n",
"data: [DONE]\n\n",
])
.await;
let (serving, connected, url) = paired(&backend, TokenPolicy::Generate).await;
let auth = bearer(&serving);
let authority = url
.trim_start_matches("http://")
.trim_end_matches("/v1")
.to_owned();
let reading = tokio::spawn(async move {
use tokio::io::{AsyncReadExt, AsyncWriteExt};
let mut socket = tokio::net::TcpStream::connect(&authority)
.await
.expect("connect");
socket
.write_all(
format!(
"GET /v1/chat/completions HTTP/1.1\r\nHost: x\r\n\
Authorization: {auth}\r\n\r\n"
)
.as_bytes(),
)
.await
.expect("write");
let mut seen = Vec::new();
socket.read_to_end(&mut seen).await.expect("read");
String::from_utf8_lossy(&seen).into_owned()
});
tokio::time::sleep(Duration::from_millis(40)).await;
within("the drain must not hang", serving.shutdown()).await;
let body = within("the admitted request must complete", reading)
.await
.expect("reader");
assert!(
body.contains("data: [DONE]"),
"shutdown promises the drain, so an admitted request runs to \
completion; the client got: {body}"
);
connected.shutdown().await;
}
#[tokio::test]
async fn an_idle_local_connection_does_not_wedge_the_connect_side_drain() {
let backend = MockBackend::json(200, OK_BODY).await;
let (serving, connected, url) = paired(&backend, TokenPolicy::Generate).await;
let authority = url
.trim_start_matches("http://")
.trim_end_matches("/v1")
.to_owned();
let _idle = tokio::net::TcpStream::connect(&authority)
.await
.expect("an SDK preconnect");
tokio::time::sleep(Duration::from_millis(50)).await;
within(
"one silent connection must not hold the drain open",
connected.shutdown(),
)
.await;
serving.shutdown().await;
}
#[tokio::test]
async fn a_connect_shutdown_timeout_releases_the_port_and_leaves_the_latch_honest() {
let backend = MockBackend::json(200, OK_BODY).await;
let (serving, connected, _url) = paired(&backend, TokenPolicy::Generate).await;
let port = connected.local_addr();
let drained = within(
"nothing is in flight, so the drain must succeed",
connected.shutdown_timeout(Duration::from_secs(5)),
)
.await;
assert!(drained, "there was nothing to wait for");
tokio::net::TcpListener::bind(port)
.await
.expect("the port must be free the moment shutdown_timeout returns");
within("a second call must not hang", connected.shutdown()).await;
serving.shutdown().await;
}
#[tokio::test]
async fn a_live_pairing_reports_a_transport_path_on_both_sides() {
let backend = MockBackend::json(200, OK_BODY).await;
let (serving, connected, url) = paired(&backend, TokenPolicy::Generate).await;
within(
"a request must cross the pipe",
request(&url, "/v1/models", Some(&bearer(&serving))),
)
.await
.expect("request");
for (side, status) in [("serve", serving.status()), ("connect", connected.status())] {
assert!(
matches!(status, PipeStatus::Direct | PipeStatus::Relayed),
"the {side} side is carrying traffic and reports {status:?}"
);
}
let peers = serving.peers();
assert_eq!(peers.len(), 1, "one connect side is paired: {peers:?}");
assert_eq!(
peers[0].path,
serving.status(),
"one peer: the aggregate is it"
);
assert_eq!(peers[0].fingerprint.len(), 12);
assert!(peers[0].fingerprint.chars().all(|c| c.is_ascii_hexdigit()));
connected.shutdown().await;
within("the peer leaves the set", async {
while !serving.peers().is_empty() {
tokio::time::sleep(Duration::from_millis(10)).await;
}
})
.await;
serving.shutdown().await;
}
#[tokio::test]
async fn a_client_that_disconnects_mid_stream_stops_the_backend() {
let (backend, frames_written) = MockBackend::endless_stream().await;
let (serving, connected, url) = paired(&backend, TokenPolicy::Generate).await;
let authority = url
.trim_start_matches("http://")
.trim_end_matches("/v1")
.to_owned();
let auth = bearer(&serving);
{
let mut socket = tokio::net::TcpStream::connect(&authority)
.await
.expect("connect");
let request = format!(
"GET /v1/chat/completions HTTP/1.1\r\nHost: {authority}\r\n\
Authorization: {auth}\r\n\r\n"
);
tokio::io::AsyncWriteExt::write_all(&mut socket, request.as_bytes())
.await
.expect("write");
let mut seen = vec![0u8; 64];
within(
"the stream must start",
tokio::io::AsyncReadExt::read(&mut socket, &mut seen),
)
.await
.expect("read");
}
tokio::time::sleep(Duration::from_millis(300)).await;
let after_disconnect = frames_written.load(std::sync::atomic::Ordering::SeqCst);
tokio::time::sleep(Duration::from_millis(300)).await;
let later = frames_written.load(std::sync::atomic::Ordering::SeqCst);
assert_eq!(
later,
after_disconnect,
"the backend produced {} more frames after the client left; a \
cancelled request must not leave a generation running",
later - after_disconnect
);
connected.shutdown().await;
serving.shutdown().await;
}
#[tokio::test]
async fn a_response_tells_the_client_not_to_reuse_the_connection() {
let backend = MockBackend::json(200, OK_BODY).await;
let (serving, connected, url) = paired(&backend, TokenPolicy::Generate).await;
let response = within(
"a request must cross the pipe",
request(&url, "/v1/models", Some(&bearer(&serving))),
)
.await
.expect("request");
assert!(
response.to_ascii_lowercase().contains("connection: close"),
"a pooling client will otherwise send its next request down a \
stream nobody is reading: {response}"
);
connected.shutdown().await;
serving.shutdown().await;
}
#[tokio::test]
async fn a_listener_restarted_with_a_stored_identity_keeps_its_ticket() {
let backend = MockBackend::json(200, OK_BODY).await;
let scratch = Scratch::new("identity");
let key = scratch.join("key");
let mut first = ServeOptions::default();
first.identity = Some(key.clone());
let before = within(
"serve must bind",
Box::pin(modelpipe::serve(&backend.url, first)),
)
.await
.expect("serve");
let ticket_before = before.ticket();
before.shutdown().await;
let mut second = ServeOptions::default();
second.identity = Some(key.clone());
let after = within(
"the restarted listener must bind",
Box::pin(modelpipe::serve(&backend.url, second)),
)
.await
.expect("serve");
let ticket_after = after.ticket();
assert_eq!(
ticket_before.fingerprint(),
ticket_after.fingerprint(),
"a stored identity is what makes a ticket outlive the process"
);
after.shutdown().await;
}
#[tokio::test]
async fn a_listener_restarted_without_one_is_a_different_peer_as_before() {
let backend = MockBackend::json(200, OK_BODY).await;
let before = within(
"serve must bind",
Box::pin(modelpipe::serve(&backend.url, ServeOptions::default())),
)
.await
.expect("serve");
let ticket_before = before.ticket();
before.shutdown().await;
let after = within(
"serve must bind again",
Box::pin(modelpipe::serve(&backend.url, ServeOptions::default())),
)
.await
.expect("serve");
assert_ne!(
ticket_before.fingerprint(),
after.ticket().fingerprint(),
"the default stays ephemeral, which is the revocation the README sells"
);
after.shutdown().await;
}
#[tokio::test]
async fn an_unusable_identity_refuses_to_serve_at_all() {
let backend = MockBackend::json(200, OK_BODY).await;
let scratch = Scratch::new("bad-identity");
let key = scratch.join("key");
std::fs::write(&key, "not a key\n").expect("write");
let mut opts = ServeOptions::default();
opts.identity = Some(key);
let refused = within(
"serve must refuse rather than hang",
Box::pin(modelpipe::serve(&backend.url, opts)),
)
.await;
let Err(refused) = refused else {
panic!("an unusable identity must not start a listener");
};
assert!(!refused.is_retryable(), "the operator named this path");
assert_eq!(backend.accepts(), 0, "and nothing was served");
}
#[tokio::test]
async fn a_connect_side_whose_peer_goes_away_reports_idle_rather_than_pretending() {
let backend = MockBackend::json(200, OK_BODY).await;
let (serving, connected, url) = paired(&backend, TokenPolicy::Generate).await;
assert!(
matches!(connected.status(), PipeStatus::Direct | PipeStatus::Relayed),
"a live pairing reports the path it is using: {:?}",
connected.status()
);
serving.shutdown().await;
within(
"the connect side must notice its peer has gone",
settles_on(&connected, PipeStatus::Idle),
)
.await;
assert_ne!(
connected.status(),
PipeStatus::Closed,
"and this side is still up and still looking, not gone"
);
let refused = within(
"a request with no peer must still be answered",
request(&url, "/v1/models", Some("Bearer whatever")),
)
.await
.expect("request");
assert!(refused.starts_with("HTTP/1.1 502"), "got: {refused}");
assert!(
refused.contains(r#""code":"tunnel_unavailable""#),
"the connect side must say the tunnel is down, not blame a backend: {refused}"
);
assert!(
!refused.contains("backend"),
"there is no backend in this failure: {refused}"
);
connected.shutdown().await;
}
#[tokio::test]
async fn a_refused_rotation_reports_it_and_leaves_the_previous_credential_in_force() {
let backend = MockBackend::json(200, OK_BODY).await;
let (serving, connected, url) =
paired(&backend, TokenPolicy::Supplied("the-only-key".to_owned())).await;
for blank in ["", " ", "\t\n"] {
assert!(
serving.set_token(blank.to_owned()).is_err(),
"{blank:?} is a credential no conforming client could ever send"
);
}
assert_eq!(
serving.token().as_deref(),
Some("the-only-key"),
"the handle still reports what it is actually enforcing"
);
assert!(
within(
"the key the operator may now believe is dead",
request(&url, "/v1/models", Some("Bearer the-only-key")),
)
.await
.expect("request")
.starts_with("HTTP/1.1 200"),
"and it is still the key that works"
);
connected.shutdown().await;
serving.shutdown().await;
}
#[tokio::test]
async fn an_aborted_upload_does_not_wedge_the_serve_side_drain() {
let backend = MockBackend::reads_whole_body(
"HTTP/1.1 200 OK\r\nContent-Length: 2\r\nConnection: close\r\n\r\n{}",
)
.await;
let (serving, connected, url) = paired(&backend, TokenPolicy::Generate).await;
let authority = url
.trim_start_matches("http://")
.trim_end_matches("/v1")
.to_owned();
let mut socket = tokio::net::TcpStream::connect(&authority)
.await
.expect("a client");
let request = format!(
"POST /v1/chat/completions HTTP/1.1\r\nHost: {authority}\r\n\
Authorization: {}\r\nContent-Length: 1000\r\n\r\n{{\"model\":\"",
bearer(&serving)
);
socket
.write_all(request.as_bytes())
.await
.expect("the head and a tenth of the body");
socket.flush().await.expect("flush");
socket.shutdown().await.expect("half-close");
let reader = tokio::spawn(async move {
let mut seen = Vec::new();
let _ = socket.read_to_end(&mut seen).await;
String::from_utf8_lossy(&seen).into_owned()
});
tokio::time::sleep(Duration::from_millis(50)).await;
within(
"an aborted upload must not hold the serve-side drain open",
serving.shutdown(),
)
.await;
let seen = reader.await.expect("the reader task");
assert!(
seen.starts_with("HTTP/1.1 400"),
"and the client is told, rather than left with an empty stream: {seen}"
);
connected.shutdown().await;
}