| 1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
use serde::Serialize; |
| 8 |
|
| 9 |
|
| 10 |
#[derive(Clone)] |
| 11 |
pub struct WamClient { |
| 12 |
http: reqwest::Client, |
| 13 |
base_url: String, |
| 14 |
} |
| 15 |
|
| 16 |
#[derive(Serialize)] |
| 17 |
struct CreateTicketRequest<'a> { |
| 18 |
title: &'a str, |
| 19 |
#[serde(skip_serializing_if = "Option::is_none")] |
| 20 |
body: Option<&'a str>, |
| 21 |
priority: &'a str, |
| 22 |
source: &'a str, |
| 23 |
#[serde(skip_serializing_if = "Option::is_none")] |
| 24 |
source_ref: Option<&'a str>, |
| 25 |
} |
| 26 |
|
| 27 |
impl WamClient { |
| 28 |
pub fn new(base_url: String) -> Self { |
| 29 |
let http = reqwest::Client::builder() |
| 30 |
.timeout(std::time::Duration::from_secs(5)) |
| 31 |
.connect_timeout(std::time::Duration::from_secs(3)) |
| 32 |
.build() |
| 33 |
.expect("WAM HTTP client"); |
| 34 |
Self { http, base_url } |
| 35 |
} |
| 36 |
|
| 37 |
|
| 38 |
pub fn ticket_url(&self) -> String { |
| 39 |
format!("{}/tickets", self.base_url.trim_end_matches('/')) |
| 40 |
} |
| 41 |
|
| 42 |
|
| 43 |
|
| 44 |
pub async fn create_ticket( |
| 45 |
&self, |
| 46 |
title: &str, |
| 47 |
body: Option<&str>, |
| 48 |
priority: &str, |
| 49 |
source: &str, |
| 50 |
source_ref: Option<&str>, |
| 51 |
) { |
| 52 |
let url = self.ticket_url(); |
| 53 |
let req = CreateTicketRequest { |
| 54 |
title, |
| 55 |
body, |
| 56 |
priority, |
| 57 |
source, |
| 58 |
source_ref, |
| 59 |
}; |
| 60 |
|
| 61 |
match self.http.post(&url).json(&req).send().await { |
| 62 |
Ok(resp) if resp.status().is_success() => { |
| 63 |
tracing::info!(title, source, "WAM ticket created"); |
| 64 |
} |
| 65 |
Ok(resp) => { |
| 66 |
tracing::warn!( |
| 67 |
status = %resp.status(), title, source, |
| 68 |
"WAM ticket creation returned non-success" |
| 69 |
); |
| 70 |
} |
| 71 |
Err(e) => { |
| 72 |
tracing::warn!(error = %e, title, source, "WAM unreachable"); |
| 73 |
} |
| 74 |
} |
| 75 |
} |
| 76 |
} |
| 77 |
|
| 78 |
#[cfg(test)] |
| 79 |
mod tests { |
| 80 |
use super::*; |
| 81 |
|
| 82 |
#[test] |
| 83 |
fn ticket_url_construction() { |
| 84 |
let client = WamClient::new("http://100.120.174.96:7890".to_string()); |
| 85 |
assert_eq!(client.ticket_url(), "http://100.120.174.96:7890/tickets"); |
| 86 |
} |
| 87 |
|
| 88 |
#[test] |
| 89 |
fn ticket_url_strips_trailing_slash() { |
| 90 |
let client = WamClient::new("http://localhost:7890/".to_string()); |
| 91 |
assert_eq!(client.ticket_url(), "http://localhost:7890/tickets"); |
| 92 |
} |
| 93 |
|
| 94 |
#[test] |
| 95 |
fn request_serialization_full() { |
| 96 |
let req = CreateTicketRequest { |
| 97 |
title: "Test ticket", |
| 98 |
body: Some("Details here"), |
| 99 |
priority: "high", |
| 100 |
source: "test-source", |
| 101 |
source_ref: Some("ref-123"), |
| 102 |
}; |
| 103 |
let json = serde_json::to_value(&req).unwrap(); |
| 104 |
assert_eq!(json["title"], "Test ticket"); |
| 105 |
assert_eq!(json["body"], "Details here"); |
| 106 |
assert_eq!(json["priority"], "high"); |
| 107 |
assert_eq!(json["source"], "test-source"); |
| 108 |
assert_eq!(json["source_ref"], "ref-123"); |
| 109 |
} |
| 110 |
|
| 111 |
#[test] |
| 112 |
fn request_serialization_skips_none_fields() { |
| 113 |
let req = CreateTicketRequest { |
| 114 |
title: "Minimal", |
| 115 |
body: None, |
| 116 |
priority: "low", |
| 117 |
source: "test", |
| 118 |
source_ref: None, |
| 119 |
}; |
| 120 |
let json = serde_json::to_value(&req).unwrap(); |
| 121 |
assert_eq!(json["title"], "Minimal"); |
| 122 |
assert!(json.get("body").is_none()); |
| 123 |
assert!(json.get("source_ref").is_none()); |
| 124 |
} |
| 125 |
|
| 126 |
#[tokio::test] |
| 127 |
async fn create_ticket_unreachable_does_not_panic() { |
| 128 |
|
| 129 |
let client = WamClient::new("http://127.0.0.1:1".to_string()); |
| 130 |
client.create_ticket("test", None, "low", "test", None).await; |
| 131 |
|
| 132 |
} |
| 133 |
} |
| 134 |
|