120 lines
3.9 KiB
Rust
120 lines
3.9 KiB
Rust
|
|
//! Event-to-notification mapping.
|
||
|
|
//!
|
||
|
|
//! Pure functions that classify [`WatcherEvent`] variants into notification
|
||
|
|
//! actions, deciding which events produce user-visible messages and which
|
||
|
|
//! are suppressed or logged server-side only.
|
||
|
|
|
||
|
|
use crate::io::watcher::WatcherEvent;
|
||
|
|
|
||
|
|
/// The notification action to take in response to a [`WatcherEvent`].
|
||
|
|
#[derive(Debug, PartialEq)]
|
||
|
|
pub enum EventAction {
|
||
|
|
/// Post a stage-transition notification; the event carries a known source stage.
|
||
|
|
StageTransition,
|
||
|
|
/// Post a merge-failure error notification.
|
||
|
|
MergeFailure,
|
||
|
|
/// Post a rate-limit warning (subject to config/debounce suppression).
|
||
|
|
RateLimitWarning,
|
||
|
|
/// Post a story-blocked notification.
|
||
|
|
StoryBlocked,
|
||
|
|
/// Log server-side only; do not post to chat (e.g. hard rate-limit blocks).
|
||
|
|
LogOnly,
|
||
|
|
/// Reload the project configuration.
|
||
|
|
ReloadConfig,
|
||
|
|
/// Skip silently (synthetic events, unknown variants).
|
||
|
|
Skip,
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Classify a [`WatcherEvent`] into the action the notification listener should take.
|
||
|
|
pub fn classify(event: &WatcherEvent) -> EventAction {
|
||
|
|
match event {
|
||
|
|
WatcherEvent::WorkItem { from_stage, .. } => {
|
||
|
|
if from_stage.is_some() {
|
||
|
|
EventAction::StageTransition
|
||
|
|
} else {
|
||
|
|
// Synthetic events (creation, reassign) have no from_stage.
|
||
|
|
// Posting a notification for these would produce incorrect messages.
|
||
|
|
EventAction::Skip
|
||
|
|
}
|
||
|
|
}
|
||
|
|
WatcherEvent::MergeFailure { .. } => EventAction::MergeFailure,
|
||
|
|
WatcherEvent::RateLimitWarning { .. } => EventAction::RateLimitWarning,
|
||
|
|
WatcherEvent::StoryBlocked { .. } => EventAction::StoryBlocked,
|
||
|
|
WatcherEvent::RateLimitHardBlock { .. } => EventAction::LogOnly,
|
||
|
|
WatcherEvent::ConfigChanged => EventAction::ReloadConfig,
|
||
|
|
_ => EventAction::Skip,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[cfg(test)]
|
||
|
|
mod tests {
|
||
|
|
use super::*;
|
||
|
|
|
||
|
|
fn work_item(from_stage: Option<&str>) -> WatcherEvent {
|
||
|
|
WatcherEvent::WorkItem {
|
||
|
|
stage: "3_qa".to_string(),
|
||
|
|
item_id: "1_story_foo".to_string(),
|
||
|
|
action: "qa".to_string(),
|
||
|
|
commit_msg: String::new(),
|
||
|
|
from_stage: from_stage.map(str::to_string),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn work_item_with_from_stage_is_stage_transition() {
|
||
|
|
let event = work_item(Some("2_current"));
|
||
|
|
assert_eq!(classify(&event), EventAction::StageTransition);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn work_item_without_from_stage_is_skip() {
|
||
|
|
let event = work_item(None);
|
||
|
|
assert_eq!(classify(&event), EventAction::Skip);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn merge_failure_is_classified_correctly() {
|
||
|
|
let event = WatcherEvent::MergeFailure {
|
||
|
|
story_id: "1_story_foo".to_string(),
|
||
|
|
reason: "conflict".to_string(),
|
||
|
|
};
|
||
|
|
assert_eq!(classify(&event), EventAction::MergeFailure);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn rate_limit_warning_is_classified_correctly() {
|
||
|
|
let event = WatcherEvent::RateLimitWarning {
|
||
|
|
story_id: "1_story_foo".to_string(),
|
||
|
|
agent_name: "coder-1".to_string(),
|
||
|
|
};
|
||
|
|
assert_eq!(classify(&event), EventAction::RateLimitWarning);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn story_blocked_is_classified_correctly() {
|
||
|
|
let event = WatcherEvent::StoryBlocked {
|
||
|
|
story_id: "1_story_foo".to_string(),
|
||
|
|
reason: "empty diff".to_string(),
|
||
|
|
};
|
||
|
|
assert_eq!(classify(&event), EventAction::StoryBlocked);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn rate_limit_hard_block_is_log_only() {
|
||
|
|
let event = WatcherEvent::RateLimitHardBlock {
|
||
|
|
story_id: "1_story_foo".to_string(),
|
||
|
|
agent_name: "coder-1".to_string(),
|
||
|
|
reset_at: chrono::Utc::now(),
|
||
|
|
};
|
||
|
|
assert_eq!(classify(&event), EventAction::LogOnly);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn config_changed_triggers_reload() {
|
||
|
|
assert_eq!(
|
||
|
|
classify(&WatcherEvent::ConfigChanged),
|
||
|
|
EventAction::ReloadConfig
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|