huskies: merge 583_bug_add_test_that_builds_gateway_route_tree_to_catch_duplicate_route_panics
This commit is contained in:
+65
-45
@@ -1634,50 +1634,12 @@ pub async fn gateway_bot_config_page_handler() -> Response {
|
|||||||
|
|
||||||
// ── Gateway server startup ───────────────────────────────────────────
|
// ── Gateway server startup ───────────────────────────────────────────
|
||||||
|
|
||||||
/// Start the gateway HTTP server. This is the entry point when `--gateway` is used.
|
/// Build the complete gateway route tree.
|
||||||
pub async fn run(config_path: &Path, port: u16) -> Result<(), std::io::Error> {
|
///
|
||||||
// Locate the gateway config directory (parent of `projects.toml`).
|
/// Extracted from `run` so that tests can construct the full route tree and
|
||||||
let config_dir = config_path
|
/// catch duplicate-route panics before they reach production.
|
||||||
.parent()
|
pub fn build_gateway_route(state_arc: Arc<GatewayState>) -> impl poem::Endpoint {
|
||||||
.unwrap_or(std::path::Path::new("."))
|
poem::Route::new()
|
||||||
.to_path_buf();
|
|
||||||
|
|
||||||
let config = GatewayConfig::load(config_path).map_err(std::io::Error::other)?;
|
|
||||||
let state =
|
|
||||||
GatewayState::new(config, config_dir.clone(), port).map_err(std::io::Error::other)?;
|
|
||||||
let state_arc = Arc::new(state);
|
|
||||||
|
|
||||||
let active = state_arc.active_project.read().await.clone();
|
|
||||||
crate::slog!("[gateway] Starting gateway on port {port}, active project: {active}");
|
|
||||||
crate::slog!(
|
|
||||||
"[gateway] Registered projects: {}",
|
|
||||||
state_arc
|
|
||||||
.projects
|
|
||||||
.read()
|
|
||||||
.await
|
|
||||||
.keys()
|
|
||||||
.cloned()
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(", ")
|
|
||||||
);
|
|
||||||
|
|
||||||
// Write `.mcp.json` so that the gateway's Matrix bot's Claude Code CLI
|
|
||||||
// connects to this gateway's MCP endpoint (which proxies to the active project).
|
|
||||||
if let Err(e) = write_gateway_mcp_json(&config_dir, port) {
|
|
||||||
crate::slog!("[gateway] Warning: could not write .mcp.json: {e}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Spawn the Matrix bot if `.huskies/bot.toml` exists in the config directory.
|
|
||||||
let gateway_projects: Vec<String> = state_arc.projects.read().await.keys().cloned().collect();
|
|
||||||
let bot_abort = spawn_gateway_bot(
|
|
||||||
&config_dir,
|
|
||||||
Arc::clone(&state_arc.active_project),
|
|
||||||
gateway_projects,
|
|
||||||
port,
|
|
||||||
);
|
|
||||||
*state_arc.bot_handle.lock().await = bot_abort;
|
|
||||||
|
|
||||||
let route = poem::Route::new()
|
|
||||||
.at("/bot-config", poem::get(gateway_bot_config_page_handler))
|
.at("/bot-config", poem::get(gateway_bot_config_page_handler))
|
||||||
.at("/api/gateway", poem::get(gateway_api_handler))
|
.at("/api/gateway", poem::get(gateway_api_handler))
|
||||||
.at("/api/gateway/switch", poem::post(gateway_switch_handler))
|
.at("/api/gateway/switch", poem::post(gateway_switch_handler))
|
||||||
@@ -1732,7 +1694,53 @@ pub async fn run(config_path: &Path, port: u16) -> Result<(), std::io::Error> {
|
|||||||
)
|
)
|
||||||
.at("/*path", poem::get(crate::http::assets::embedded_file))
|
.at("/*path", poem::get(crate::http::assets::embedded_file))
|
||||||
.at("/", poem::get(crate::http::assets::embedded_index))
|
.at("/", poem::get(crate::http::assets::embedded_index))
|
||||||
.data(state_arc);
|
.data(state_arc)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start the gateway HTTP server. This is the entry point when `--gateway` is used.
|
||||||
|
pub async fn run(config_path: &Path, port: u16) -> Result<(), std::io::Error> {
|
||||||
|
// Locate the gateway config directory (parent of `projects.toml`).
|
||||||
|
let config_dir = config_path
|
||||||
|
.parent()
|
||||||
|
.unwrap_or(std::path::Path::new("."))
|
||||||
|
.to_path_buf();
|
||||||
|
|
||||||
|
let config = GatewayConfig::load(config_path).map_err(std::io::Error::other)?;
|
||||||
|
let state =
|
||||||
|
GatewayState::new(config, config_dir.clone(), port).map_err(std::io::Error::other)?;
|
||||||
|
let state_arc = Arc::new(state);
|
||||||
|
|
||||||
|
let active = state_arc.active_project.read().await.clone();
|
||||||
|
crate::slog!("[gateway] Starting gateway on port {port}, active project: {active}");
|
||||||
|
crate::slog!(
|
||||||
|
"[gateway] Registered projects: {}",
|
||||||
|
state_arc
|
||||||
|
.projects
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.keys()
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ")
|
||||||
|
);
|
||||||
|
|
||||||
|
// Write `.mcp.json` so that the gateway's Matrix bot's Claude Code CLI
|
||||||
|
// connects to this gateway's MCP endpoint (which proxies to the active project).
|
||||||
|
if let Err(e) = write_gateway_mcp_json(&config_dir, port) {
|
||||||
|
crate::slog!("[gateway] Warning: could not write .mcp.json: {e}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spawn the Matrix bot if `.huskies/bot.toml` exists in the config directory.
|
||||||
|
let gateway_projects: Vec<String> = state_arc.projects.read().await.keys().cloned().collect();
|
||||||
|
let bot_abort = spawn_gateway_bot(
|
||||||
|
&config_dir,
|
||||||
|
Arc::clone(&state_arc.active_project),
|
||||||
|
gateway_projects,
|
||||||
|
port,
|
||||||
|
);
|
||||||
|
*state_arc.bot_handle.lock().await = bot_abort;
|
||||||
|
|
||||||
|
let route = build_gateway_route(state_arc);
|
||||||
|
|
||||||
let host = std::env::var("HUSKIES_HOST").unwrap_or_else(|_| "127.0.0.1".to_string());
|
let host = std::env::var("HUSKIES_HOST").unwrap_or_else(|_| "127.0.0.1".to_string());
|
||||||
let addr = format!("{host}:{port}");
|
let addr = format!("{host}:{port}");
|
||||||
@@ -2260,4 +2268,16 @@ enabled = false
|
|||||||
.await;
|
.await;
|
||||||
assert_eq!(resp.0.status(), StatusCode::NOT_FOUND);
|
assert_eq!(resp.0.status(), StatusCode::NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Build the full gateway route tree and verify it does not panic.
|
||||||
|
///
|
||||||
|
/// Poem panics at construction time when duplicate routes are registered.
|
||||||
|
/// This test catches any regression where a duplicate route is re-introduced
|
||||||
|
/// (e.g. the `/` vs `/*path` duplicate fixed in commit 0969fb5d).
|
||||||
|
#[test]
|
||||||
|
fn gateway_route_tree_builds_without_panic() {
|
||||||
|
let state = make_test_state();
|
||||||
|
// build_gateway_route will panic if any route is registered more than once.
|
||||||
|
let _route = build_gateway_route(state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user