use std::fmt; pub const DEFAULT_API_SERVER_HEALTHZ_URL: &str = "http://127.0.0.1:3100/healthz"; #[derive(Debug, Clone, PartialEq, Eq)] pub struct SmokeAssertionError { message: String, } impl SmokeAssertionError { /// 测试支撑 crate 只提供断言辅助,不承接业务错误分类。 pub fn new(message: impl Into) -> Self { Self { message: message.into(), } } pub fn message(&self) -> &str { &self.message } } impl fmt::Display for SmokeAssertionError { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str(&self.message) } } impl std::error::Error for SmokeAssertionError {} /// 归一化本地 smoke URL,供不同测试入口复用同一套空值与斜杠处理口径。 pub fn normalize_smoke_url(input: impl AsRef) -> String { let trimmed = input.as_ref().trim(); if trimmed.is_empty() { return DEFAULT_API_SERVER_HEALTHZ_URL.to_string(); } trimmed.trim_end_matches('/').to_string() } /// 断言 HTTP 状态码处于 2xx,避免 smoke 测试散落重复判断。 pub fn assert_success_status(status: u16) -> Result<(), SmokeAssertionError> { if (200..=299).contains(&status) { return Ok(()); } Err(SmokeAssertionError::new(format!( "期望 HTTP 2xx 状态码,实际为 {status}" ))) } /// 断言 healthz 响应体非空。具体 JSON 字段语义仍归 api-server 自己的 contract 测试负责。 pub fn assert_non_empty_healthz_body(body: impl AsRef) -> Result<(), SmokeAssertionError> { if body.as_ref().trim().is_empty() { return Err(SmokeAssertionError::new("healthz 响应体不能为空")); } Ok(()) } #[cfg(test)] mod tests { use super::*; #[test] fn normalize_smoke_url_uses_api_server_healthz_when_empty() { assert_eq!( normalize_smoke_url(" "), DEFAULT_API_SERVER_HEALTHZ_URL.to_string() ); } #[test] fn normalize_smoke_url_removes_trailing_slash() { assert_eq!( normalize_smoke_url(" http://127.0.0.1:3100/ "), "http://127.0.0.1:3100" ); } #[test] fn assert_success_status_rejects_non_2xx() { let error = assert_success_status(503).expect_err("非 2xx 状态码必须失败"); assert_eq!(error.message(), "期望 HTTP 2xx 状态码,实际为 503"); } #[test] fn assert_non_empty_healthz_body_rejects_blank_body() { let error = assert_non_empty_healthz_body(" \n ").expect_err("空响应体必须失败"); assert_eq!(error.message(), "healthz 响应体不能为空"); } }