1use crate::report::{ReportBuilder, ReportHeader};
2
3#[derive(Debug, Clone, PartialEq, Eq)]
4pub struct RestReportAssertion {
5 pub label: String,
6 pub state: String,
7}
8
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct RestReport {
11 pub report_date: String,
12 pub case_name: String,
13 pub generated_at: String,
14 pub endpoint_note: String,
15 pub result_note: String,
16 pub command_snippet: Option<String>,
17 pub assertions: Vec<RestReportAssertion>,
18 pub request_json: String,
19 pub response_lang: String,
20 pub response_body: String,
21 pub stderr_note: Option<String>,
22}
23
24pub fn render_rest_report_markdown(report: &RestReport) -> String {
25 let header = ReportHeader {
26 report_date: &report.report_date,
27 case_name: &report.case_name,
28 generated_at: &report.generated_at,
29 endpoint_note: &report.endpoint_note,
30 result_note: &report.result_note,
31 command_snippet: report.command_snippet.as_deref(),
32 };
33 let mut builder = ReportBuilder::new(header);
34
35 if !report.assertions.is_empty() {
36 builder.push_section_heading("Assertions");
37 for a in &report.assertions {
38 builder.push_list_item(&format!("{} ({})", a.label, a.state));
39 }
40 builder.push_blank_line();
41 }
42
43 builder.push_code_section("Request", "json", &report.request_json, None, true);
44 builder.push_code_section(
45 "Response",
46 &report.response_lang,
47 &report.response_body,
48 None,
49 true,
50 );
51
52 if let Some(stderr_note) = &report.stderr_note
53 && !stderr_note.is_empty()
54 {
55 builder.push_code_section("stderr", "text", stderr_note, None, false);
56 }
57
58 builder.finish()
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64 use pretty_assertions::assert_eq;
65
66 #[test]
67 fn rest_report_renders_markdown_with_optional_sections() {
68 let report = RestReport {
69 report_date: "2026-02-01".to_string(),
70 case_name: "Health".to_string(),
71 generated_at: "2026-02-01T00:00:00Z".to_string(),
72 endpoint_note: "Endpoint: http://localhost:8080/health".to_string(),
73 result_note: "Result: OK".to_string(),
74 command_snippet: Some("curl -sS http://localhost:8080/health".to_string()),
75 assertions: vec![RestReportAssertion {
76 label: "status".to_string(),
77 state: "pass".to_string(),
78 }],
79 request_json: r#"{"method":"GET","path":"/health"}"#.to_string(),
80 response_lang: "json".to_string(),
81 response_body: r#"{"ok":true}"#.to_string(),
82 stderr_note: Some("warning: retrying".to_string()),
83 };
84
85 let got = render_rest_report_markdown(&report);
86 let expected = concat!(
87 "# API Test Report (2026-02-01)\n",
88 "\n",
89 "## Test Case: Health\n",
90 "\n",
91 "## Command\n",
92 "\n",
93 "```bash\n",
94 "curl -sS http://localhost:8080/health\n",
95 "```\n",
96 "\n",
97 "Generated at: 2026-02-01T00:00:00Z\n",
98 "\n",
99 "Endpoint: http://localhost:8080/health\n",
100 "\n",
101 "Result: OK\n",
102 "\n",
103 "### Assertions\n",
104 "\n",
105 "- status (pass)\n",
106 "\n",
107 "### Request\n",
108 "\n",
109 "```json\n",
110 "{\"method\":\"GET\",\"path\":\"/health\"}\n",
111 "```\n",
112 "\n",
113 "### Response\n",
114 "\n",
115 "```json\n",
116 "{\"ok\":true}\n",
117 "```\n",
118 "\n",
119 "### stderr\n",
120 "\n",
121 "```text\n",
122 "warning: retrying\n",
123 "```\n"
124 );
125 assert_eq!(got, expected);
126 }
127
128 #[test]
129 fn rest_report_omits_command_assertions_and_empty_stderr() {
130 let report = RestReport {
131 report_date: "2026-02-01".to_string(),
132 case_name: "No-Options".to_string(),
133 generated_at: "2026-02-01T00:00:00Z".to_string(),
134 endpoint_note: "Endpoint: x".to_string(),
135 result_note: "Result: y".to_string(),
136 command_snippet: None,
137 assertions: vec![],
138 request_json: "{}".to_string(),
139 response_lang: "text".to_string(),
140 response_body: "ok".to_string(),
141 stderr_note: Some("".to_string()),
142 };
143
144 let got = render_rest_report_markdown(&report);
145 assert!(!got.contains("## Command\n"));
146 assert!(!got.contains("### Assertions\n"));
147 assert!(!got.contains("### stderr\n"));
148 }
149}