huskies: merge 940
This commit is contained in:
@@ -195,7 +195,7 @@ async fn gateway_notification_poller_continues_when_one_project_unreachable() {
|
|||||||
);
|
);
|
||||||
let has_good = messages
|
let has_good = messages
|
||||||
.iter()
|
.iter()
|
||||||
.any(|m| m.contains("[good-project]") && m.contains("10_story_ok"));
|
.any(|m| m.contains("[good-project]") && m.contains("#10"));
|
||||||
assert!(
|
assert!(
|
||||||
has_good,
|
has_good,
|
||||||
"Expected a notification from [good-project]; got: {messages:?}"
|
"Expected a notification from [good-project]; got: {messages:?}"
|
||||||
@@ -792,8 +792,8 @@ async fn broadcaster_forwarder_forwards_events_with_project_tag() {
|
|||||||
"Expected [my-project] prefix; got: {plain}"
|
"Expected [my-project] prefix; got: {plain}"
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
plain.contains("7_story_x"),
|
plain.contains("#7"),
|
||||||
"Expected story ID; got: {plain}"
|
"Expected story number #7; got: {plain}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,12 +33,16 @@ pub fn format_stage_notification(
|
|||||||
to_stage: &str,
|
to_stage: &str,
|
||||||
) -> (String, String) {
|
) -> (String, String) {
|
||||||
let number = extract_item_number(item_id).unwrap_or(item_id);
|
let number = extract_item_number(item_id).unwrap_or(item_id);
|
||||||
let name = story_name.unwrap_or(item_id);
|
let effective_name = story_name.filter(|n| !n.is_empty());
|
||||||
|
let name_plain = effective_name.map(|n| format!("{n} ")).unwrap_or_default();
|
||||||
|
let name_html = effective_name
|
||||||
|
.map(|n| format!("<em>{n}</em> "))
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
let prefix = if to_stage == "Done" { "\u{1f389} " } else { "" };
|
let prefix = if to_stage == "Done" { "\u{1f389} " } else { "" };
|
||||||
let plain = format!("{prefix}#{number} {name} \u{2014} {from_stage} \u{2192} {to_stage}");
|
let plain = format!("{prefix}#{number} {name_plain}\u{2014} {from_stage} \u{2192} {to_stage}");
|
||||||
let html = format!(
|
let html = format!(
|
||||||
"{prefix}<strong>#{number}</strong> <em>{name}</em> \u{2014} {from_stage} \u{2192} {to_stage}"
|
"{prefix}<strong>#{number}</strong> {name_html}\u{2014} {from_stage} \u{2192} {to_stage}"
|
||||||
);
|
);
|
||||||
(plain, html)
|
(plain, html)
|
||||||
}
|
}
|
||||||
@@ -52,10 +56,14 @@ pub fn format_error_notification(
|
|||||||
reason: &str,
|
reason: &str,
|
||||||
) -> (String, String) {
|
) -> (String, String) {
|
||||||
let number = extract_item_number(item_id).unwrap_or(item_id);
|
let number = extract_item_number(item_id).unwrap_or(item_id);
|
||||||
let name = story_name.unwrap_or(item_id);
|
let effective_name = story_name.filter(|n| !n.is_empty());
|
||||||
|
let name_plain = effective_name.map(|n| format!("{n} ")).unwrap_or_default();
|
||||||
|
let name_html = effective_name
|
||||||
|
.map(|n| format!("<em>{n}</em> "))
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
let plain = format!("\u{274c} #{number} {name} \u{2014} {reason}");
|
let plain = format!("\u{274c} #{number} {name_plain}\u{2014} {reason}");
|
||||||
let html = format!("\u{274c} <strong>#{number}</strong> <em>{name}</em> \u{2014} {reason}");
|
let html = format!("\u{274c} <strong>#{number}</strong> {name_html}\u{2014} {reason}");
|
||||||
(plain, html)
|
(plain, html)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,11 +76,15 @@ pub fn format_blocked_notification(
|
|||||||
reason: &str,
|
reason: &str,
|
||||||
) -> (String, String) {
|
) -> (String, String) {
|
||||||
let number = extract_item_number(item_id).unwrap_or(item_id);
|
let number = extract_item_number(item_id).unwrap_or(item_id);
|
||||||
let name = story_name.unwrap_or(item_id);
|
let effective_name = story_name.filter(|n| !n.is_empty());
|
||||||
|
let name_plain = effective_name.map(|n| format!("{n} ")).unwrap_or_default();
|
||||||
|
let name_html = effective_name
|
||||||
|
.map(|n| format!("<em>{n}</em> "))
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
let plain = format!("\u{1f6ab} #{number} {name} \u{2014} BLOCKED: {reason}");
|
let plain = format!("\u{1f6ab} #{number} {name_plain}\u{2014} BLOCKED: {reason}");
|
||||||
let html =
|
let html =
|
||||||
format!("\u{1f6ab} <strong>#{number}</strong> <em>{name}</em> \u{2014} BLOCKED: {reason}");
|
format!("\u{1f6ab} <strong>#{number}</strong> {name_html}\u{2014} BLOCKED: {reason}");
|
||||||
(plain, html)
|
(plain, html)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,12 +97,17 @@ pub fn format_rate_limit_notification(
|
|||||||
agent_name: &str,
|
agent_name: &str,
|
||||||
) -> (String, String) {
|
) -> (String, String) {
|
||||||
let number = extract_item_number(item_id).unwrap_or(item_id);
|
let number = extract_item_number(item_id).unwrap_or(item_id);
|
||||||
let name = story_name.unwrap_or(item_id);
|
let effective_name = story_name.filter(|n| !n.is_empty());
|
||||||
|
let name_plain = effective_name.map(|n| format!("{n} ")).unwrap_or_default();
|
||||||
|
let name_html = effective_name
|
||||||
|
.map(|n| format!("<em>{n}</em> "))
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
let plain =
|
let plain = format!(
|
||||||
format!("\u{26a0}\u{fe0f} #{number} {name} \u{2014} {agent_name} hit an API rate limit");
|
"\u{26a0}\u{fe0f} #{number} {name_plain}\u{2014} {agent_name} hit an API rate limit"
|
||||||
|
);
|
||||||
let html = format!(
|
let html = format!(
|
||||||
"\u{26a0}\u{fe0f} <strong>#{number}</strong> <em>{name}</em> \u{2014} \
|
"\u{26a0}\u{fe0f} <strong>#{number}</strong> {name_html}\u{2014} \
|
||||||
{agent_name} hit an API rate limit"
|
{agent_name} hit an API rate limit"
|
||||||
);
|
);
|
||||||
(plain, html)
|
(plain, html)
|
||||||
@@ -127,11 +144,15 @@ pub fn format_agent_started_notification(
|
|||||||
agent_name: &str,
|
agent_name: &str,
|
||||||
) -> (String, String) {
|
) -> (String, String) {
|
||||||
let number = extract_item_number(item_id).unwrap_or(item_id);
|
let number = extract_item_number(item_id).unwrap_or(item_id);
|
||||||
let name = story_name.unwrap_or(item_id);
|
let effective_name = story_name.filter(|n| !n.is_empty());
|
||||||
let plain = format!("\u{1F916} #{number} {name} \u{2014} {agent_name} started");
|
let name_plain = effective_name.map(|n| format!("{n} ")).unwrap_or_default();
|
||||||
let html = format!(
|
let name_html = effective_name
|
||||||
"\u{1F916} <strong>#{number}</strong> <em>{name}</em> \u{2014} {agent_name} started"
|
.map(|n| format!("<em>{n}</em> "))
|
||||||
);
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let plain = format!("\u{1F916} #{number} {name_plain}\u{2014} {agent_name} started");
|
||||||
|
let html =
|
||||||
|
format!("\u{1F916} <strong>#{number}</strong> {name_html}\u{2014} {agent_name} started");
|
||||||
(plain, html)
|
(plain, html)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,16 +167,20 @@ pub fn format_agent_completed_notification(
|
|||||||
success: bool,
|
success: bool,
|
||||||
) -> (String, String) {
|
) -> (String, String) {
|
||||||
let number = extract_item_number(item_id).unwrap_or(item_id);
|
let number = extract_item_number(item_id).unwrap_or(item_id);
|
||||||
let name = story_name.unwrap_or(item_id);
|
let effective_name = story_name.filter(|n| !n.is_empty());
|
||||||
|
let name_plain = effective_name.map(|n| format!("{n} ")).unwrap_or_default();
|
||||||
|
let name_html = effective_name
|
||||||
|
.map(|n| format!("<em>{n}</em> "))
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
let (emoji, result) = if success {
|
let (emoji, result) = if success {
|
||||||
("\u{2705}", "completed") // ✅
|
("\u{2705}", "completed") // ✅
|
||||||
} else {
|
} else {
|
||||||
("\u{274C}", "failed") // ❌
|
("\u{274C}", "failed") // ❌
|
||||||
};
|
};
|
||||||
let plain = format!("{emoji} #{number} {name} \u{2014} {agent_name} {result}");
|
let plain = format!("{emoji} #{number} {name_plain}\u{2014} {agent_name} {result}");
|
||||||
let html = format!(
|
let html =
|
||||||
"{emoji} <strong>#{number}</strong> <em>{name}</em> \u{2014} {agent_name} {result}"
|
format!("{emoji} <strong>#{number}</strong> {name_html}\u{2014} {agent_name} {result}");
|
||||||
);
|
|
||||||
(plain, html)
|
(plain, html)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,9 +266,10 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn format_notification_without_story_name_falls_back_to_item_id() {
|
fn format_stage_notification_without_story_name_falls_back_to_number() {
|
||||||
let (plain, _html) = format_stage_notification("42_bug_fix_thing", None, "Current", "QA");
|
let (plain, html) = format_stage_notification("42_bug_fix_thing", None, "Current", "QA");
|
||||||
assert_eq!(plain, "#42 42_bug_fix_thing \u{2014} Current \u{2192} QA");
|
assert_eq!(plain, "#42 \u{2014} Current \u{2192} QA");
|
||||||
|
assert_eq!(html, "<strong>#42</strong> \u{2014} Current \u{2192} QA");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -265,12 +291,10 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn format_stage_notification_empty_story_name_falls_back_to_id() {
|
fn format_stage_notification_empty_story_name_falls_back_to_number() {
|
||||||
// Some("") is a valid Some but empty — treat as missing? Currently we use it as-is.
|
let (plain, html) = format_stage_notification("42_story_empty", Some(""), "Current", "QA");
|
||||||
let (plain, _html) = format_stage_notification("42_story_empty", Some(""), "Current", "QA");
|
assert_eq!(plain, "#42 \u{2014} Current \u{2192} QA");
|
||||||
// The name slot is empty but the structure is still correct.
|
assert_eq!(html, "<strong>#42</strong> \u{2014} Current \u{2192} QA");
|
||||||
assert!(plain.contains("#42"));
|
|
||||||
assert!(plain.contains("Current \u{2192} QA"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -301,9 +325,10 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn format_error_notification_without_story_name_falls_back_to_item_id() {
|
fn format_error_notification_without_story_name_falls_back_to_number() {
|
||||||
let (plain, _html) = format_error_notification("42_bug_fix_thing", None, "tests failed");
|
let (plain, html) = format_error_notification("42_bug_fix_thing", None, "tests failed");
|
||||||
assert_eq!(plain, "\u{274c} #42 42_bug_fix_thing \u{2014} tests failed");
|
assert_eq!(plain, "\u{274c} #42 \u{2014} tests failed");
|
||||||
|
assert_eq!(html, "\u{274c} <strong>#42</strong> \u{2014} tests failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -330,6 +355,13 @@ mod tests {
|
|||||||
assert!(plain.contains("错误:合并冲突"));
|
assert!(plain.contains("错误:合并冲突"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_error_notification_empty_story_name_falls_back_to_number() {
|
||||||
|
let (plain, _html) =
|
||||||
|
format_error_notification("42_bug_fix_thing", Some(""), "tests failed");
|
||||||
|
assert_eq!(plain, "\u{274c} #42 \u{2014} tests failed");
|
||||||
|
}
|
||||||
|
|
||||||
// ── format_blocked_notification ───────────────────────────────────────────
|
// ── format_blocked_notification ───────────────────────────────────────────
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -350,14 +382,21 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn format_blocked_notification_falls_back_to_item_id() {
|
fn format_blocked_notification_falls_back_to_number() {
|
||||||
let (plain, _html) = format_blocked_notification("42_story_thing", None, "empty diff");
|
let (plain, html) = format_blocked_notification("42_story_thing", None, "empty diff");
|
||||||
|
assert_eq!(plain, "\u{1f6ab} #42 \u{2014} BLOCKED: empty diff");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
plain,
|
html,
|
||||||
"\u{1f6ab} #42 42_story_thing \u{2014} BLOCKED: empty diff"
|
"\u{1f6ab} <strong>#42</strong> \u{2014} BLOCKED: empty diff"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_blocked_notification_empty_story_name_falls_back_to_number() {
|
||||||
|
let (plain, _html) = format_blocked_notification("42_story_thing", Some(""), "empty diff");
|
||||||
|
assert_eq!(plain, "\u{1f6ab} #42 \u{2014} BLOCKED: empty diff");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn format_blocked_notification_unicode_reason() {
|
fn format_blocked_notification_unicode_reason() {
|
||||||
let (plain, _html) = format_blocked_notification("3_story_x", Some("X"), "理由:空の差分");
|
let (plain, _html) = format_blocked_notification("3_story_x", Some("X"), "理由:空の差分");
|
||||||
@@ -381,11 +420,24 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn format_rate_limit_notification_falls_back_to_item_id() {
|
fn format_rate_limit_notification_falls_back_to_number() {
|
||||||
let (plain, _html) = format_rate_limit_notification("42_story_thing", None, "coder-1");
|
let (plain, html) = format_rate_limit_notification("42_story_thing", None, "coder-1");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
plain,
|
plain,
|
||||||
"\u{26a0}\u{fe0f} #42 42_story_thing \u{2014} coder-1 hit an API rate limit"
|
"\u{26a0}\u{fe0f} #42 \u{2014} coder-1 hit an API rate limit"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
html,
|
||||||
|
"\u{26a0}\u{fe0f} <strong>#42</strong> \u{2014} coder-1 hit an API rate limit"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_rate_limit_notification_empty_story_name_falls_back_to_number() {
|
||||||
|
let (plain, _html) = format_rate_limit_notification("42_story_thing", Some(""), "coder-1");
|
||||||
|
assert_eq!(
|
||||||
|
plain,
|
||||||
|
"\u{26a0}\u{fe0f} #42 \u{2014} coder-1 hit an API rate limit"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,4 +447,79 @@ mod tests {
|
|||||||
assert!(plain.contains("агент-1"));
|
assert!(plain.contains("агент-1"));
|
||||||
assert!(plain.contains("hit an API rate limit"));
|
assert!(plain.contains("hit an API rate limit"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── format_agent_started_notification ─────────────────────────────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_agent_started_notification_with_story_name() {
|
||||||
|
let (plain, html) =
|
||||||
|
format_agent_started_notification("42_story_foo", Some("My Feature"), "coder-1");
|
||||||
|
assert_eq!(plain, "\u{1F916} #42 My Feature \u{2014} coder-1 started");
|
||||||
|
assert_eq!(
|
||||||
|
html,
|
||||||
|
"\u{1F916} <strong>#42</strong> <em>My Feature</em> \u{2014} coder-1 started"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_agent_started_notification_falls_back_to_number() {
|
||||||
|
let (plain, html) = format_agent_started_notification("42_story_foo", None, "coder-1");
|
||||||
|
assert_eq!(plain, "\u{1F916} #42 \u{2014} coder-1 started");
|
||||||
|
assert_eq!(
|
||||||
|
html,
|
||||||
|
"\u{1F916} <strong>#42</strong> \u{2014} coder-1 started"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_agent_started_notification_empty_name_falls_back_to_number() {
|
||||||
|
let (plain, _html) = format_agent_started_notification("42_story_foo", Some(""), "coder-1");
|
||||||
|
assert_eq!(plain, "\u{1F916} #42 \u{2014} coder-1 started");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── format_agent_completed_notification ───────────────────────────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_agent_completed_notification_success_with_story_name() {
|
||||||
|
let (plain, html) = format_agent_completed_notification(
|
||||||
|
"42_story_foo",
|
||||||
|
Some("My Feature"),
|
||||||
|
"coder-1",
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
assert_eq!(plain, "\u{2705} #42 My Feature \u{2014} coder-1 completed");
|
||||||
|
assert_eq!(
|
||||||
|
html,
|
||||||
|
"\u{2705} <strong>#42</strong> <em>My Feature</em> \u{2014} coder-1 completed"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_agent_completed_notification_failure_with_story_name() {
|
||||||
|
let (plain, _html) = format_agent_completed_notification(
|
||||||
|
"42_story_foo",
|
||||||
|
Some("My Feature"),
|
||||||
|
"coder-1",
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
assert_eq!(plain, "\u{274C} #42 My Feature \u{2014} coder-1 failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_agent_completed_notification_falls_back_to_number() {
|
||||||
|
let (plain, html) =
|
||||||
|
format_agent_completed_notification("42_story_foo", None, "coder-1", true);
|
||||||
|
assert_eq!(plain, "\u{2705} #42 \u{2014} coder-1 completed");
|
||||||
|
assert_eq!(
|
||||||
|
html,
|
||||||
|
"\u{2705} <strong>#42</strong> \u{2014} coder-1 completed"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_agent_completed_notification_empty_name_falls_back_to_number() {
|
||||||
|
let (plain, _html) =
|
||||||
|
format_agent_completed_notification("42_story_foo", Some(""), "coder-1", false);
|
||||||
|
assert_eq!(plain, "\u{274C} #42 \u{2014} coder-1 failed");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user