tidaldb/tidal/tests/metrics_integration.rs
jx12n 3bcfb3c576 feat: Bazel build, crate docs/ai-lookup, docker images, and engine hardening
- Add BUILD.bazel across tidal, tidal-net, tidal-server, tidalctl for bzlmod build
- Add tidal/ crate docs (README, CHANGELOG, CONTRIBUTING, AGENTS, CLAUDE, API, ARCHITECTURE) and ai-lookup reference
- Add docker standalone/cluster/deploy images, compose, and prometheus config
- Harden WAL (batch format, writer, dedup, diagnostics), text syncer/collectors, and vector registry
- Expand tidalctl CLI and tests; restructure WAL/visibility integration test suites
- Refine tidal-net transport/client/server and tidal-server cluster/scatter-gather
2026-06-07 18:29:38 -06:00

170 lines
4.5 KiB
Rust

//! Integration tests for the metrics HTTP server.
//!
//! These tests require `--features metrics`.
#![allow(clippy::unwrap_used)]
use std::{
io::{Read, Write},
net::TcpStream,
time::Duration,
};
use tidaldb::TidalDb;
/// Make an HTTP GET request to the given address and path.
/// Returns (`status_code`, body).
fn http_get(addr: std::net::SocketAddr, path: &str) -> (u16, String) {
let mut stream = TcpStream::connect(addr).expect("connect");
write!(
stream,
"GET {path} HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n"
)
.unwrap();
stream.flush().unwrap();
let mut response = String::new();
stream.read_to_string(&mut response).unwrap();
// Parse status line: "HTTP/1.1 200 OK"
let status: u16 = response
.lines()
.next()
.and_then(|l| l.split_whitespace().nth(1))
.and_then(|s| s.parse().ok())
.unwrap_or(0);
let body = response
.split_once("\r\n\r\n")
.map_or("", |x| x.1)
.to_string();
(status, body)
}
fn open_with_metrics() -> (std::net::SocketAddr, TidalDb) {
let db = TidalDb::builder()
.ephemeral()
.enable_metrics("127.0.0.1:0")
.open()
.expect("open should succeed");
let addr = db
.metrics_addr()
.expect("metrics_addr should be Some when metrics enabled");
(addr, db)
}
#[test]
fn metrics_server_starts_on_port_zero() {
let (addr, db) = open_with_metrics();
assert_ne!(addr.port(), 0, "OS should have assigned a real port");
db.close().expect("close should succeed");
}
#[test]
fn healthz_returns_200_json() {
let (addr, _db) = open_with_metrics();
let (status, body) = http_get(addr, "/healthz");
assert_eq!(status, 200);
assert!(body.contains("\"status\":\"ok\""), "body: {body}");
assert!(
body.contains("\"uptime_seconds\":"),
"body should have uptime: {body}"
);
assert!(
body.contains("\"version\":"),
"body should have version: {body}"
);
assert!(
body.contains("\"build_hash\":"),
"body should have build_hash: {body}"
);
}
#[test]
fn metrics_returns_200_prometheus() {
let (addr, _db) = open_with_metrics();
let (status, body) = http_get(addr, "/metrics");
assert_eq!(status, 200);
assert!(
body.contains("tidaldb_uptime_seconds"),
"missing uptime metric in body: {body}"
);
assert!(
body.contains("tidaldb_health_ok"),
"missing health_ok metric in body: {body}"
);
assert!(
body.contains("tidaldb_info"),
"missing info metric in body: {body}"
);
}
#[test]
fn uptime_increases_over_time() {
let (addr, _db) = open_with_metrics();
let (_, body1) = http_get(addr, "/metrics");
std::thread::sleep(Duration::from_millis(150));
let (_, body2) = http_get(addr, "/metrics");
let uptime1 = extract_uptime(&body1);
let uptime2 = extract_uptime(&body2);
assert!(
uptime2 > uptime1,
"uptime should increase: {uptime1} -> {uptime2}"
);
}
#[test]
fn health_ok_value_is_one() {
let (addr, _db) = open_with_metrics();
let (_, body) = http_get(addr, "/metrics");
let health_line = body
.lines()
.find(|l| l.starts_with("tidaldb_health_ok{"))
.expect("should have health_ok metric");
assert!(
health_line.ends_with(" 1"),
"health should be 1: {health_line}"
);
}
#[test]
fn not_found_returns_404() {
let (addr, _db) = open_with_metrics();
let (status, _) = http_get(addr, "/nonexistent");
assert_eq!(status, 404);
}
#[test]
fn close_stops_server() {
let (addr, db) = open_with_metrics();
// Server should respond before close
let (status, _) = http_get(addr, "/healthz");
assert_eq!(status, 200);
db.close().expect("close should succeed");
// Give the thread a moment to shut down
std::thread::sleep(Duration::from_millis(200));
// After close, connection should be refused
let result = TcpStream::connect_timeout(&addr, Duration::from_millis(200));
assert!(result.is_err(), "connection should be refused after close");
}
// -----------------------------------------------------------------------
// Helpers
// -----------------------------------------------------------------------
fn extract_uptime(prometheus_body: &str) -> f64 {
for line in prometheus_body.lines() {
if line.starts_with("tidaldb_uptime_seconds{") {
let val = line.split_whitespace().last().unwrap_or("0");
return val.parse().unwrap_or(0.0);
}
}
0.0
}