use std::time::Duration;
use agent_first_http::host::bootstrap::HealthPublic;
use agent_first_http::host::listener::{AppState, router_for_tests, test_state};
use axum::extract::ws::{Message, WebSocketUpgrade};
use axum::http::{HeaderMap, Uri};
use axum::response::IntoResponse;
use axum::routing::get;
use futures::{SinkExt, StreamExt};
use serde_json::json;
use tokio::net::TcpListener;
pub async fn spawn_fake_provider() -> u16 {
let app = axum::Router::new()
.route("/", get(echo_request))
.route("/echo", get(echo_request))
.route("/ws", get(fake_ws));
let listener = TcpListener::bind("127.0.0.1:0").await.expect("bind fake");
let port = listener.local_addr().expect("addr").port();
tokio::spawn(async move {
let _ = axum::serve(listener, app).await;
});
tokio::time::sleep(Duration::from_millis(20)).await;
port
}
async fn echo_request(uri: Uri, headers: HeaderMap) -> impl IntoResponse {
axum::Json(json!({
"path_and_query": uri.path_and_query().map(|pq| pq.as_str()).unwrap_or(""),
"saw_cookie": headers.get(axum::http::header::COOKIE).is_some(),
"saw_authorization": headers.get(axum::http::header::AUTHORIZATION).is_some(),
}))
}
async fn fake_ws(ws: WebSocketUpgrade) -> impl IntoResponse {
ws.protocols(["binary"]).on_upgrade(|socket| async move {
let (mut tx, mut rx) = socket.split();
while let Some(Ok(msg)) = rx.next().await {
if let Message::Text(text) = msg {
let _ = tx.send(Message::Text(format!("echo:{text}").into())).await;
}
}
})
}
pub async fn spawn_host(token: Option<&str>, upstream_port: u16) -> String {
spawn_host_with_state(token, upstream_port).await.0
}
pub async fn spawn_host_with_state(token: Option<&str>, upstream_port: u16) -> (String, AppState) {
super::ensure_rustls_provider();
let state = test_state(token, HealthPublic::Off).with_takeover_for_tests(upstream_port);
let app = router_for_tests(state.clone());
let listener = TcpListener::bind("127.0.0.1:0").await.expect("bind host");
let addr = listener.local_addr().expect("addr");
tokio::spawn(async move {
let _ = axum::serve(listener, app).await;
});
tokio::time::sleep(Duration::from_millis(20)).await;
(format!("http://{addr}"), state)
}
pub async fn mint_panel_url(base: &str, token: &str) -> String {
let body = reqwest::Client::new()
.post(format!("{base}/takeover/handoff"))
.bearer_auth(token)
.json(&json!({}))
.send()
.await
.expect("handoff send")
.json::<serde_json::Value>()
.await
.expect("handoff json");
agent_first_data::validate_protocol_event(&body, true).expect("strict AFDATA event");
let url = body["result"]["takeover_url_secret"]
.as_str()
.expect("takeover_url_secret");
assert!(url.contains("handoff_secret="), "{body}");
assert!(
body["result"]["takeover_url_ttl_s"]
.as_u64()
.unwrap_or_default()
> 0
);
url.to_string()
}
pub fn handoff_secret_of(takeover_url: &str) -> String {
url::Url::parse(takeover_url)
.expect("parse takeover URL")
.query_pairs()
.find(|(k, _)| k == "handoff_secret")
.map(|(_, v)| v.into_owned())
.expect("handoff query")
}