use reinhardt_core::messages::{Level, Message};
#[derive(Debug, thiserror::Error)]
pub enum MessageAssertionError {
#[error("Message count mismatch: expected {expected}, got {actual}")]
CountMismatch {
expected: usize,
actual: usize,
},
#[error("Message not found: {message}")]
MessageNotFound {
message: String,
},
#[error("Message level mismatch: expected {expected:?}, got {actual:?}")]
LevelMismatch {
expected: Level,
actual: Level,
},
#[error("Message tags mismatch: expected {expected:?}, got {actual:?}")]
TagsMismatch {
expected: Vec<String>,
actual: Vec<String>,
},
#[error("Order mismatch: expected {expected:?}, got {actual:?}")]
OrderMismatch {
expected: Vec<String>,
actual: Vec<String>,
},
}
pub type MessageAssertionResult<T> = Result<T, MessageAssertionError>;
pub fn assert_message_count(
messages: &[Message],
expected_count: usize,
) -> MessageAssertionResult<()> {
let actual_count = messages.len();
if actual_count != expected_count {
return Err(MessageAssertionError::CountMismatch {
expected: expected_count,
actual: actual_count,
});
}
Ok(())
}
pub fn assert_message_exists(
messages: &[Message],
level: Level,
text: &str,
) -> MessageAssertionResult<()> {
let found = messages
.iter()
.any(|msg| msg.level == level && msg.text == text);
if !found {
return Err(MessageAssertionError::MessageNotFound {
message: format!(
"Message with level {:?} and text '{}' not found",
level, text
),
});
}
Ok(())
}
pub fn assert_message_level(
message: &Message,
expected_level: Level,
) -> MessageAssertionResult<()> {
if message.level != expected_level {
return Err(MessageAssertionError::LevelMismatch {
expected: expected_level,
actual: message.level,
});
}
Ok(())
}
pub fn assert_message_tags(
message: &Message,
expected_tags: Vec<String>,
) -> MessageAssertionResult<()> {
if message.extra_tags != expected_tags {
return Err(MessageAssertionError::TagsMismatch {
expected: expected_tags,
actual: message.extra_tags.clone(),
});
}
Ok(())
}
pub fn assert_messages(
messages: &[Message],
expected: &[(Level, String)],
ordered: bool,
) -> MessageAssertionResult<()> {
assert_message_count(messages, expected.len())?;
if ordered {
for (i, (expected_level, expected_text)) in expected.iter().enumerate() {
if i >= messages.len() {
return Err(MessageAssertionError::MessageNotFound {
message: format!("Expected message at index {} not found", i),
});
}
let actual_message = &messages[i];
if actual_message.level != *expected_level || actual_message.text != *expected_text {
return Err(MessageAssertionError::OrderMismatch {
expected: expected.iter().map(|(_, text)| text.clone()).collect(),
actual: messages.iter().map(|msg| msg.text.clone()).collect(),
});
}
}
} else {
for (expected_level, expected_text) in expected {
assert_message_exists(messages, *expected_level, expected_text)?;
}
}
Ok(())
}
#[derive(Debug, Default)]
pub struct MessagesTestMixin {
pub ignore_method_frames: bool,
}
impl MessagesTestMixin {
pub fn new() -> Self {
Self::default()
}
pub fn with_settings(ignore_method_frames: bool) -> Self {
Self {
ignore_method_frames,
}
}
pub fn assert_message_count(
&self,
messages: &[Message],
expected_count: usize,
) -> MessageAssertionResult<()> {
assert_message_count(messages, expected_count)
}
pub fn assert_message_exists(
&self,
messages: &[Message],
level: Level,
text: &str,
) -> MessageAssertionResult<()> {
assert_message_exists(messages, level, text)
}
pub fn assert_messages(
&self,
messages: &[Message],
expected: &[(Level, String)],
ordered: bool,
) -> MessageAssertionResult<()> {
assert_messages(messages, expected, ordered)
}
pub fn assert_messages_with_tags(
&self,
messages: &[Message],
expected: &[(Level, String, Vec<String>)],
) -> MessageAssertionResult<()> {
assert_message_count(messages, expected.len())?;
for (expected_level, expected_text, expected_tags) in expected {
let found = messages.iter().find(|msg| {
msg.level == *expected_level
&& msg.text == *expected_text
&& msg.extra_tags == *expected_tags
});
if found.is_none() {
return Err(MessageAssertionError::MessageNotFound {
message: format!(
"Message with level {:?}, text '{}', and tags {:?} not found",
expected_level, expected_text, expected_tags
),
});
}
}
Ok(())
}
pub fn filter_stack_trace(&self, stack_trace: &str) -> String {
if !self.ignore_method_frames {
return stack_trace.to_string();
}
let lines: Vec<&str> = stack_trace.lines().collect();
let filtered_lines: Vec<&str> = lines
.into_iter()
.filter(|line| {
!line.contains("::assert_")
&& !line.contains("::test_")
&& !line.contains("unittest::")
&& !line.contains("testing::")
})
.collect();
filtered_lines.join("\n")
}
}
#[cfg(test)]
mod tests {
use super::*;
use reinhardt_core::messages::{Level, Message};
#[test]
fn test_assert_message_count_success() {
let messages = vec![Message::new(Level::Info, "Test message".to_string())];
assert!(assert_message_count(&messages, 1).is_ok());
}
#[test]
fn test_assert_message_count_failure() {
let messages = vec![Message::new(Level::Info, "Test message".to_string())];
assert!(assert_message_count(&messages, 2).is_err());
}
#[test]
fn test_assert_message_exists_success() {
let messages = vec![Message::new(Level::Info, "Test message".to_string())];
assert!(assert_message_exists(&messages, Level::Info, "Test message").is_ok());
}
#[test]
fn test_assert_message_exists_failure() {
let messages = vec![Message::new(Level::Info, "Test message".to_string())];
assert!(assert_message_exists(&messages, Level::Warning, "Test message").is_err());
}
#[test]
fn test_assert_message_level_success() {
let message = Message::new(Level::Info, "Test message".to_string());
assert!(assert_message_level(&message, Level::Info).is_ok());
}
#[test]
fn test_assert_message_level_failure() {
let message = Message::new(Level::Info, "Test message".to_string());
assert!(assert_message_level(&message, Level::Warning).is_err());
}
#[test]
fn test_assert_message_tags_success() {
let mut message = Message::new(Level::Info, "Test message".to_string());
message.extra_tags = vec!["tag1".to_string(), "tag2".to_string()];
assert!(
assert_message_tags(&message, vec!["tag1".to_string(), "tag2".to_string()]).is_ok()
);
}
#[test]
fn test_assert_message_tags_failure() {
let mut message = Message::new(Level::Info, "Test message".to_string());
message.extra_tags = vec!["tag1".to_string()];
assert!(assert_message_tags(&message, vec!["tag2".to_string()]).is_err());
}
#[test]
fn test_assert_messages_ordered_success() {
let messages = vec![
Message::new(Level::Info, "First message".to_string()),
Message::new(Level::Warning, "Second message".to_string()),
];
let expected = vec![
(Level::Info, "First message".to_string()),
(Level::Warning, "Second message".to_string()),
];
assert!(assert_messages(&messages, &expected, true).is_ok());
}
#[test]
fn test_assert_messages_unordered_success() {
let messages = vec![
Message::new(Level::Warning, "Second message".to_string()),
Message::new(Level::Info, "First message".to_string()),
];
let expected = vec![
(Level::Info, "First message".to_string()),
(Level::Warning, "Second message".to_string()),
];
assert!(assert_messages(&messages, &expected, false).is_ok());
}
#[test]
fn test_messages_test_mixin() {
let mixin = MessagesTestMixin::new();
let messages = vec![Message::new(Level::Info, "Test message".to_string())];
assert!(mixin.assert_message_count(&messages, 1).is_ok());
}
#[test]
fn test_filter_stack_trace() {
let mixin = MessagesTestMixin::with_settings(true);
let stack_trace = "at testing::assert_message_count\nat unittest::test_case\nat main";
let filtered = mixin.filter_stack_trace(stack_trace);
assert!(!filtered.contains("testing::"));
assert!(!filtered.contains("unittest::"));
}
}