snapshot of https://github.com/mountain-loop/yaak.git (main, 2026-09-23; blobs > 131072B omitted)
This commit is contained in:
98
crates-server/yaak-web/tests/logging.rs
Normal file
98
crates-server/yaak-web/tests/logging.rs
Normal file
@@ -0,0 +1,98 @@
|
||||
use axum::Router;
|
||||
use axum::body::{Body, to_bytes};
|
||||
use axum::extract::{ConnectInfo, Request};
|
||||
use axum::http::StatusCode;
|
||||
use axum::routing::get;
|
||||
use clap::Parser;
|
||||
use std::io::{self, Write};
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tower::ServiceExt;
|
||||
use yaak_web::{Config, DEFAULT_LOG_FILTER, router};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct LogCapture(Arc<Mutex<Vec<u8>>>);
|
||||
|
||||
impl Write for LogCapture {
|
||||
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
|
||||
self.0.lock().unwrap().extend_from_slice(bytes);
|
||||
Ok(bytes.len())
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn send_request(url: &str) -> Request {
|
||||
let payload = serde_json::json!({
|
||||
"request": { "url": url, "method": "GET" },
|
||||
"settings": {
|
||||
"validateCertificates": true,
|
||||
"followRedirects": true,
|
||||
"timeoutMs": 2000,
|
||||
"sendCookies": true,
|
||||
"storeCookies": true
|
||||
}
|
||||
});
|
||||
Request::builder()
|
||||
.method("POST")
|
||||
.uri("/v1/http/send")
|
||||
.header("content-type", "application/json")
|
||||
.extension(ConnectInfo("192.0.2.42:1234".parse::<SocketAddr>().unwrap()))
|
||||
.body(Body::from(payload.to_string()))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn default_logging_keeps_operational_warnings_without_request_details() {
|
||||
let capture = LogCapture(Arc::new(Mutex::new(Vec::new())));
|
||||
env_logger::Builder::new()
|
||||
.parse_filters(DEFAULT_LOG_FILTER)
|
||||
.target(env_logger::Target::Pipe(Box::new(capture.clone())))
|
||||
.init();
|
||||
|
||||
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let port = listener.local_addr().unwrap().port();
|
||||
let upstream = tokio::spawn(async move {
|
||||
axum::serve(
|
||||
listener,
|
||||
Router::new().route("/private-path", get(|| async { "secret body" })),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
});
|
||||
let mut config = Config::parse_from(["yaak-web"]);
|
||||
config.allow_private_networks = true;
|
||||
config.rate_limit_per_minute = 1;
|
||||
let app = router(config.clone());
|
||||
assert!(String::from_utf8_lossy(&capture.0.lock().unwrap()).contains("ALLOWED"));
|
||||
capture.0.lock().unwrap().clear();
|
||||
|
||||
// Exercise the HTTP engine and DNS lookup as well as the proxy's own logs.
|
||||
let url = format!("http://localhost:{port}/private-path?token=secret");
|
||||
let response = app.clone().oneshot(send_request(&url)).await.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let body = to_bytes(response.into_body(), 64 * 1024).await.unwrap();
|
||||
assert!(String::from_utf8_lossy(&body).contains("\"type\":\"done\""));
|
||||
assert_eq!(
|
||||
app.oneshot(send_request(&url)).await.unwrap().status(),
|
||||
StatusCode::TOO_MANY_REQUESTS
|
||||
);
|
||||
|
||||
config.allow_private_networks = false;
|
||||
let app = router(config.clone());
|
||||
assert_eq!(
|
||||
app.oneshot(send_request("http://127.0.0.1/private-path")).await.unwrap().status(),
|
||||
StatusCode::FORBIDDEN
|
||||
);
|
||||
config.max_concurrent = 0;
|
||||
assert_eq!(
|
||||
router(config).oneshot(send_request(&url)).await.unwrap().status(),
|
||||
StatusCode::SERVICE_UNAVAILABLE
|
||||
);
|
||||
|
||||
upstream.abort();
|
||||
let logs = capture.0.lock().unwrap();
|
||||
assert!(logs.is_empty(), "Unexpected request logs: {}", String::from_utf8_lossy(&logs));
|
||||
}
|
||||
89
crates-server/yaak-web/tests/router.rs
Normal file
89
crates-server/yaak-web/tests/router.rs
Normal file
@@ -0,0 +1,89 @@
|
||||
use axum::body::{Body, to_bytes};
|
||||
use axum::extract::Request;
|
||||
use axum::http::{HeaderValue, StatusCode, header};
|
||||
use axum::middleware::{self, Next};
|
||||
use axum::response::Response;
|
||||
use std::path::PathBuf;
|
||||
use tower::ServiceExt;
|
||||
use yaak_web::{Config, router};
|
||||
|
||||
fn config(serve: Option<PathBuf>) -> Config {
|
||||
Config {
|
||||
host: "127.0.0.1".to_string(),
|
||||
port: 0,
|
||||
app_port: None,
|
||||
serve,
|
||||
allow_private_networks: false,
|
||||
allowed_origins: vec!["*".into()],
|
||||
max_request_bytes: 1024,
|
||||
max_response_bytes: 2048,
|
||||
max_timeout_secs: 5,
|
||||
rate_limit_per_minute: 10,
|
||||
max_concurrent: 2,
|
||||
trust_forwarded_for: false,
|
||||
}
|
||||
}
|
||||
|
||||
async fn mark_response(request: Request, next: Next) -> Response {
|
||||
let mut response = next.run(request).await;
|
||||
response.headers_mut().insert("x-hosted-middleware", HeaderValue::from_static("yes"));
|
||||
response
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn embedded_router_serves_api_without_static_files() {
|
||||
let app = router(config(None)).layer(middleware::from_fn(mark_response));
|
||||
let response = app
|
||||
.clone()
|
||||
.oneshot(Request::builder().uri("/v1/health").body(Body::empty()).unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
assert_eq!(response.headers()["x-hosted-middleware"], "yes");
|
||||
let body = to_bytes(response.into_body(), 4096).await.unwrap();
|
||||
let health: serde_json::Value = serde_json::from_slice(&body).unwrap();
|
||||
assert_eq!(health["ok"], true);
|
||||
assert_eq!(health["maxResponseBytes"], 2048);
|
||||
assert_eq!(health["maxTimeoutSecs"], 5);
|
||||
|
||||
let response = app.oneshot(Request::new(Body::empty())).await.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn middleware_wraps_html_fallback_and_assets_alongside_api() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
std::fs::write(dir.path().join("index.html"), "<html>Yaak</html>").unwrap();
|
||||
std::fs::create_dir(dir.path().join("assets")).unwrap();
|
||||
std::fs::write(dir.path().join("assets/app-abc.js"), "// app").unwrap();
|
||||
let app = router(config(Some(dir.path().into()))).layer(middleware::from_fn(mark_response));
|
||||
|
||||
for path in [
|
||||
"/",
|
||||
"/workspace/example",
|
||||
"/assets/app-abc.js",
|
||||
"/v1/health",
|
||||
] {
|
||||
let response = app
|
||||
.clone()
|
||||
.oneshot(Request::builder().uri(path).body(Body::empty()).unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::OK, "{path}");
|
||||
assert_eq!(response.headers()["x-hosted-middleware"], "yes", "{path}");
|
||||
if path.starts_with("/assets/") {
|
||||
assert_eq!(
|
||||
response.headers()[header::CACHE_CONTROL],
|
||||
"public, max-age=31536000, immutable"
|
||||
);
|
||||
} else if path == "/v1/health" {
|
||||
assert_eq!(response.headers()[header::CONTENT_TYPE], "application/json");
|
||||
} else {
|
||||
assert_eq!(response.headers()[header::CACHE_CONTROL], "no-cache");
|
||||
assert!(
|
||||
response.headers()[header::CONTENT_TYPE].to_str().unwrap().starts_with("text/html")
|
||||
);
|
||||
assert_eq!(to_bytes(response.into_body(), 4096).await.unwrap(), "<html>Yaak</html>");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user