use crate::TestSummary;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PostDeployReport {
pub command: TestCommand,
pub url: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mode: Option<String>,
pub outcome: TestOutcome,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub summary: Option<TestSummary>,
#[serde(default)]
pub failures: Vec<FailureDetail>,
pub duration_ms: u64,
pub schema_version: String,
}
impl Default for PostDeployReport {
fn default() -> Self {
Self {
command: TestCommand::Check,
url: String::new(),
mode: None,
outcome: TestOutcome::Passed,
summary: None,
failures: Vec::new(),
duration_ms: 0,
schema_version: "1".to_string(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum TestCommand {
Check,
Conformance,
Apps,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum TestOutcome {
Passed,
TestFailed,
InfraError,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FailureDetail {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tool: Option<String>,
pub message: String,
pub reproduce: String,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::TestSummary;
fn sample_summary() -> TestSummary {
TestSummary {
total: 9,
passed: 7,
failed: 1,
warnings: 1,
skipped: 0,
}
}
#[test]
fn post_deploy_report_round_trips_via_serde_json() {
let original = PostDeployReport {
command: TestCommand::Check,
url: "http://x".to_string(),
mode: None,
outcome: TestOutcome::Passed,
summary: None,
failures: vec![],
duration_ms: 200,
schema_version: "1".to_string(),
};
let json = serde_json::to_string_pretty(&original)
.expect("PostDeployReport must serialize cleanly");
let round_tripped: PostDeployReport =
serde_json::from_str(&json).expect("PostDeployReport must deserialize cleanly");
assert_eq!(original, round_tripped);
}
#[test]
fn test_command_serializes_kebab_case() {
assert_eq!(
serde_json::to_string(&TestCommand::Check).unwrap(),
"\"check\""
);
assert_eq!(
serde_json::to_string(&TestCommand::Conformance).unwrap(),
"\"conformance\""
);
assert_eq!(
serde_json::to_string(&TestCommand::Apps).unwrap(),
"\"apps\""
);
}
#[test]
fn test_outcome_serializes_kebab_case() {
assert_eq!(
serde_json::to_string(&TestOutcome::Passed).unwrap(),
"\"passed\""
);
assert_eq!(
serde_json::to_string(&TestOutcome::TestFailed).unwrap(),
"\"test-failed\""
);
assert_eq!(
serde_json::to_string(&TestOutcome::InfraError).unwrap(),
"\"infra-error\""
);
}
#[test]
fn failure_detail_construction() {
let detail = FailureDetail {
tool: Some("get_spend_summary".to_string()),
message: "widget cost-summary.html missing onteardown handler".to_string(),
reproduce:
"cargo pmcp test apps --url http://x --mode claude-desktop --tool get_spend_summary"
.to_string(),
};
let json = serde_json::to_string(&detail).unwrap();
let round_tripped: FailureDetail = serde_json::from_str(&json).unwrap();
assert_eq!(detail, round_tripped);
assert!(detail.reproduce.contains("--mode"));
assert!(detail.reproduce.contains("--tool"));
}
#[test]
fn schema_version_field_is_serialized() {
let report = PostDeployReport::default();
let json = serde_json::to_string(&report).unwrap();
assert!(
json.contains("\"schema_version\":\"1\""),
"schema_version field missing or wrong; got: {json}"
);
}
#[test]
fn test_summary_reuses_mcp_tester_test_summary() {
let report = PostDeployReport {
command: TestCommand::Conformance,
url: "http://x".to_string(),
mode: None,
outcome: TestOutcome::TestFailed,
summary: Some(sample_summary()),
failures: vec![],
duration_ms: 50,
schema_version: "1".to_string(),
};
let json = serde_json::to_string(&report).unwrap();
let round_tripped: PostDeployReport = serde_json::from_str(&json).unwrap();
let summary = round_tripped.summary.expect("summary preserved");
assert_eq!(summary.total, 9);
assert_eq!(summary.passed, 7);
assert_eq!(summary.failed, 1);
assert_eq!(summary.warnings, 1);
assert_eq!(summary.skipped, 0);
}
#[test]
fn default_schema_version_is_1() {
let report = PostDeployReport::default();
assert_eq!(report.schema_version, "1");
}
}