这是indexloc提供的服务,不要输入任何密码
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/turborepo-microfrontends-proxy/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ turborepo-microfrontends = { path = "../turborepo-microfrontends" }
url = "2.2.2"

[dev-dependencies]
serial_test = "3.0"
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
198 changes: 114 additions & 84 deletions crates/turborepo-microfrontends-proxy/tests/integration_test.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::{net::SocketAddr, time::Duration};
use std::time::Duration;

use http_body_util::{BodyExt, Full};
use hyper::{
Expand All @@ -7,58 +7,69 @@ use hyper::{
service::service_fn,
};
use hyper_util::{client::legacy::Client, rt::TokioIo};
use serial_test::serial;
use tokio::net::TcpListener;
use turborepo_microfrontends::Config;
use turborepo_microfrontends_proxy::{ProxyServer, Router};

const WEBSOCKET_CLOSE_DELAY: Duration = Duration::from_millis(100);

#[tokio::test]
#[serial]
async fn test_port_availability_check_ipv4() {
let config_json = r#"{
"options": {
"localProxyPort": 9999
},
"applications": {
"web": {
"development": {
"local": { "port": 3000 }
}
}
}
}"#;
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let port = listener.local_addr().unwrap().port();

let config = Config::from_str(config_json, "test.json").unwrap();
let server = ProxyServer::new(config.clone()).unwrap();
let config_json = format!(
r#"{{
"options": {{
"localProxyPort": {port}
}},
"applications": {{
"web": {{
"development": {{
"local": {{ "port": 3000 }}
}}
}}
}}
}}"#
);

let _listener = TcpListener::bind("127.0.0.1:9999").await.unwrap();
let config = Config::from_str(&config_json, "test.json").unwrap();
let server = ProxyServer::new(config.clone()).unwrap();

let result = server.check_port_available().await;
assert!(!result, "Port should not be available when already bound");

drop(listener);
}

#[tokio::test]
#[serial]
async fn test_port_availability_check_ipv6() {
let config_json = r#"{
"options": {
"localProxyPort": 9997
},
"applications": {
"web": {
"development": {
"local": { "port": 3000 }
}
}
}
}"#;
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let port = listener.local_addr().unwrap().port();

let config = Config::from_str(config_json, "test.json").unwrap();
let server = ProxyServer::new(config).unwrap();
let config_json = format!(
r#"{{
"options": {{
"localProxyPort": {port}
}},
"applications": {{
"web": {{
"development": {{
"local": {{ "port": 3000 }}
}}
}}
}}
}}"#
);

let _listener = TcpListener::bind("127.0.0.1:9997").await.unwrap();
let config = Config::from_str(&config_json, "test.json").unwrap();
let server = ProxyServer::new(config).unwrap();

let result = server.check_port_available().await;
assert!(!result, "Port should not be available when already bound");

drop(listener);
}

#[tokio::test]
Expand Down Expand Up @@ -142,20 +153,26 @@ async fn test_multiple_child_apps() {

#[tokio::test]
async fn test_proxy_server_creation() {
let config_json = r#"{
"options": {
"localProxyPort": 4000
},
"applications": {
"web": {
"development": {
"local": { "port": 3000 }
}
}
}
}"#;
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let port = listener.local_addr().unwrap().port();
drop(listener);

let config = Config::from_str(config_json, "test.json").unwrap();
let config_json = format!(
r#"{{
"options": {{
"localProxyPort": {port}
}},
"applications": {{
"web": {{
"development": {{
"local": {{ "port": 3000 }}
}}
}}
}}
}}"#
);

let config = Config::from_str(&config_json, "test.json").unwrap();
let server = ProxyServer::new(config);

assert!(server.is_ok());
Expand Down Expand Up @@ -197,33 +214,29 @@ async fn test_pattern_matching_edge_cases() {
);
}

async fn find_available_port_range(count: usize) -> Result<Vec<u16>, Box<dyn std::error::Error>> {
// Try to find consecutive available ports within the allowed range (3000-9999)
async fn find_available_port_range(
count: usize,
) -> Result<(Vec<u16>, Vec<TcpListener>), Box<dyn std::error::Error>> {
let mut available_ports = Vec::new();
let mut listeners = Vec::new();

for port in 3000..=9999 {
// Skip commonly blocked ports
if [3306, 5432, 6379].contains(&port) {
continue;
}
if TcpListener::bind(format!("127.0.0.1:{port}")).await.is_ok() {
if let Ok(listener) = TcpListener::bind(format!("127.0.0.1:{port}")).await {
available_ports.push(port);
listeners.push(listener);
if available_ports.len() == count {
return Ok(available_ports);
return Ok((available_ports, listeners));
}
}
}
Err("Not enough available ports in allowed range".into())
}

async fn mock_server(
port: u16,
response_text: &'static str,
) -> Result<tokio::task::JoinHandle<()>, Box<dyn std::error::Error>> {
let addr = SocketAddr::from(([127, 0, 0, 1], port));
let listener = TcpListener::bind(addr).await?;

let handle = tokio::spawn(async move {
fn mock_server(listener: TcpListener, response_text: &'static str) -> tokio::task::JoinHandle<()> {
tokio::spawn(async move {
loop {
let (stream, _) = listener.accept().await.unwrap();
let io = TokioIo::new(stream);
Expand All @@ -241,21 +254,27 @@ async fn mock_server(
.serve_connection(io, service)
.await;
}
});

tokio::time::sleep(WEBSOCKET_CLOSE_DELAY).await;
Ok(handle)
})
}

#[tokio::test]
#[tokio::test(flavor = "multi_thread")]
#[serial]
async fn test_end_to_end_proxy() {
let ports = find_available_port_range(3).await.unwrap();
let (ports, mut listeners) = find_available_port_range(3).await.unwrap();
let web_port = ports[0];
let docs_port = ports[1];
let proxy_port = ports[2];

let web_handle = mock_server(web_port, "web app").await.unwrap();
let docs_handle = mock_server(docs_port, "docs app").await.unwrap();
let web_listener = listeners.remove(0);
let docs_listener = listeners.remove(0);
let proxy_listener = listeners.remove(0);

drop(proxy_listener);

let web_handle = mock_server(web_listener, "web app");
let docs_handle = mock_server(docs_listener, "docs app");

tokio::time::sleep(Duration::from_millis(100)).await;

let config_json = format!(
r#"{{
Expand Down Expand Up @@ -288,30 +307,36 @@ async fn test_end_to_end_proxy() {
let shutdown_handle = server.shutdown_handle();

tokio::spawn(async move {
server.run().await.unwrap();
let _ = server.run().await;
});

tokio::time::sleep(Duration::from_millis(200)).await;
tokio::time::sleep(Duration::from_millis(300)).await;

let connector = hyper_util::client::legacy::connect::HttpConnector::new();
let client: Client<_, Full<Bytes>> =
Client::builder(hyper_util::rt::TokioExecutor::new()).build(connector);

let web_response = client
.get(format!("http://127.0.0.1:{proxy_port}/").parse().unwrap())
.await
.unwrap();
let web_response = tokio::time::timeout(
Duration::from_secs(5),
client.get(format!("http://127.0.0.1:{proxy_port}/").parse().unwrap()),
)
.await
.expect("Request timed out")
.expect("Request failed");
let web_body = web_response.into_body().collect().await.unwrap().to_bytes();
assert_eq!(web_body, "web app");

let docs_response = client
.get(
let docs_response = tokio::time::timeout(
Duration::from_secs(5),
client.get(
format!("http://127.0.0.1:{proxy_port}/docs")
.parse()
.unwrap(),
)
.await
.unwrap();
),
)
.await
.expect("Request timed out")
.expect("Request failed");
let docs_body = docs_response
.into_body()
.collect()
Expand All @@ -320,14 +345,17 @@ async fn test_end_to_end_proxy() {
.to_bytes();
assert_eq!(docs_body, "docs app");

let docs_subpath_response = client
.get(
let docs_subpath_response = tokio::time::timeout(
Duration::from_secs(5),
client.get(
format!("http://127.0.0.1:{proxy_port}/docs/api/reference")
.parse()
.unwrap(),
)
.await
.unwrap();
),
)
.await
.expect("Request timed out")
.expect("Request failed");
let docs_subpath_body = docs_subpath_response
.into_body()
.collect()
Expand All @@ -337,10 +365,12 @@ async fn test_end_to_end_proxy() {
assert_eq!(docs_subpath_body, "docs app");

let _ = shutdown_handle.send(());
let _ = tokio::time::timeout(Duration::from_secs(2), shutdown_complete_rx).await;
let _ = tokio::time::timeout(Duration::from_secs(3), shutdown_complete_rx).await;

web_handle.abort();
docs_handle.abort();

tokio::time::sleep(Duration::from_millis(100)).await;
}

#[tokio::test]
Expand Down
Loading