#![cfg(feature = "http-transport")]
use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
use std::process::{Command, Stdio};
use std::time::{Duration, Instant};
fn pick_free_port() -> u16 {
let listener = TcpListener::bind("127.0.0.1:0").expect("bind ephemeral");
listener.local_addr().expect("local_addr").port()
}
fn http_get_dashboard(port: u16) -> std::io::Result<String> {
let addr = format!("127.0.0.1:{port}").parse().unwrap();
let mut stream = TcpStream::connect_timeout(&addr, Duration::from_millis(500))?;
stream.set_read_timeout(Some(Duration::from_secs(2)))?;
stream.set_write_timeout(Some(Duration::from_secs(2)))?;
stream.write_all(b"GET /dashboard HTTP/1.1\r\nHost: 127.0.0.1\r\nConnection: close\r\n\r\n")?;
let mut buf = Vec::new();
stream.read_to_end(&mut buf)?;
Ok(String::from_utf8_lossy(&buf).to_string())
}
#[test]
fn cold_window_help_dispatches() {
let bin = env!("CARGO_BIN_EXE_skrills");
let output = Command::new(bin)
.args(["cold-window", "--help"])
.output()
.expect("spawn skrills cold-window --help");
assert!(
output.status.success(),
"`skrills cold-window --help` exited non-zero — \
likely a missing Commands::ColdWindow arm in app/mod.rs::run() \
(T031b).\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("--browser"),
"help output missing `--browser` flag:\n{stdout}"
);
assert!(
stdout.contains("--alert-budget"),
"help output missing `--alert-budget` flag:\n{stdout}"
);
assert!(
stdout.contains("--research-rate"),
"help output missing `--research-rate` flag:\n{stdout}"
);
}
#[test]
fn cold_window_browser_surface_serves_dashboard() {
let bin = env!("CARGO_BIN_EXE_skrills");
let port = pick_free_port();
let mut child = Command::new(bin)
.args(["cold-window", "--browser", "--port", &port.to_string()])
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.expect("spawn skrills cold-window");
let response = (|| -> Option<String> {
let deadline = Instant::now() + Duration::from_secs(5);
loop {
if let Ok(s) = http_get_dashboard(port) {
return Some(s);
}
if Instant::now() >= deadline {
return None;
}
std::thread::sleep(Duration::from_millis(100));
}
})();
let _ = child.kill();
let _ = child.wait();
let body = response.expect(
"dashboard did not respond within 5 s — \
either the binary failed to bind or the dispatch arm regressed",
);
assert!(
body.contains("HTTP/1.1 200"),
"dashboard did not return 200:\n{body}"
);
assert!(
body.contains("EventSource"),
"dashboard body missing EventSource script:\n{body}"
);
assert!(
body.contains("/dashboard.sse"),
"dashboard body missing SSE endpoint reference:\n{body}"
);
}