use crate::iq::spec::IqSpec;
use crate::iq::tctoken::build_tc_token_node;
use crate::request::InfoQuery;
use crate::types::spam_report::{SpamReportRequest, SpamReportResult, build_spam_list_node};
use wacore_binary::{Jid, Server};
use wacore_binary::{Node, NodeContent, NodeContentRef, NodeRef};
pub use crate::types::spam_report::{
SpamFlow, SpamReportRequest as Request, SpamReportResult as Response,
};
#[derive(Debug, Clone)]
pub struct SpamReportSpec {
pub request: SpamReportRequest,
pub tc_token: Option<Vec<u8>>,
}
impl SpamReportSpec {
pub fn new(request: SpamReportRequest) -> Self {
Self {
request,
tc_token: None,
}
}
pub fn with_tc_token(mut self, token: Vec<u8>) -> Self {
self.tc_token = Some(token);
self
}
}
impl IqSpec for SpamReportSpec {
type Response = SpamReportResult;
fn build_iq(&self) -> InfoQuery<'static> {
let mut children: Vec<Node> = vec![build_spam_list_node(&self.request)];
if let Some(token) = &self.tc_token {
children.push(build_tc_token_node(token));
}
InfoQuery::set(
"spam",
Jid::new("", Server::Pn),
Some(NodeContent::Nodes(children)),
)
}
fn parse_response(&self, response: &NodeRef<'_>) -> Result<Self::Response, anyhow::Error> {
let report_id = response
.get_optional_child_by_tag(&["report_id"])
.and_then(|n| match n.content.as_ref() {
Some(NodeContentRef::String(s)) => Some(s.to_string()),
_ => None,
});
Ok(SpamReportResult { report_id })
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::spam_report::SpamFlow;
use wacore_binary::builder::NodeBuilder;
#[test]
fn test_spam_report_spec_build_iq() {
let request = SpamReportRequest {
message_id: "TEST123".to_string(),
message_timestamp: 1234567890,
spam_flow: SpamFlow::MessageMenu,
..Default::default()
};
let spec = SpamReportSpec::new(request);
let iq = spec.build_iq();
assert_eq!(iq.namespace, "spam");
assert_eq!(iq.query_type, crate::request::InfoQueryType::Set);
if let Some(NodeContent::Nodes(nodes)) = &iq.content {
assert_eq!(nodes.len(), 1);
assert_eq!(nodes[0].tag, "spam_list");
assert!(
nodes[0]
.attrs
.get("spam_flow")
.is_some_and(|s| s == "MessageMenu")
);
} else {
panic!("Expected NodeContent::Nodes");
}
}
#[test]
fn test_spam_report_spec_build_iq_with_tctoken() {
let request = SpamReportRequest {
message_id: "TEST123".to_string(),
message_timestamp: 1234567890,
spam_flow: SpamFlow::MessageMenu,
..Default::default()
};
let iq = SpamReportSpec::new(request)
.with_tc_token(vec![0xCA, 0xFE])
.build_iq();
let Some(NodeContent::Nodes(nodes)) = &iq.content else {
panic!("Expected NodeContent::Nodes");
};
assert!(nodes.iter().any(|n| n.tag == "spam_list"));
let tctoken = nodes
.iter()
.find(|n| n.tag == "tctoken")
.expect("spam report should carry a tctoken when set");
match &tctoken.content {
Some(NodeContent::Bytes(b)) => assert_eq!(b, &[0xCA, 0xFE]),
_ => panic!("tctoken should carry bytes"),
}
}
#[test]
fn test_spam_report_spec_parse_response_with_report_id() {
let request = SpamReportRequest {
message_id: "TEST123".to_string(),
message_timestamp: 1234567890,
spam_flow: SpamFlow::MessageMenu,
..Default::default()
};
let spec = SpamReportSpec::new(request);
let response = NodeBuilder::new("iq")
.attr("type", "result")
.children([NodeBuilder::new("report_id")
.string_content("REPORT_ABC123")
.build()])
.build();
let result = spec.parse_response(&response.as_node_ref()).unwrap();
assert_eq!(result.report_id, Some("REPORT_ABC123".to_string()));
}
#[test]
fn test_spam_report_spec_parse_response_without_report_id() {
let request = SpamReportRequest {
message_id: "TEST123".to_string(),
message_timestamp: 1234567890,
spam_flow: SpamFlow::MessageMenu,
..Default::default()
};
let spec = SpamReportSpec::new(request);
let response = NodeBuilder::new("iq").attr("type", "result").build();
let result = spec.parse_response(&response.as_node_ref()).unwrap();
assert_eq!(result.report_id, None);
}
}