huskies: merge 1203 bug Gateway status gives no response — 1187 made it proxy-only with no local resolution or error surfacing
This commit is contained in:
@@ -125,6 +125,51 @@ pub fn format_overview_compact(items_by_project: &BTreeMap<String, Value>) -> St
|
||||
format!("**Overview: Active Work**\n\n{}", sections.join("\n\n"))
|
||||
}
|
||||
|
||||
/// Find the registered project whose pipeline contains a story with the
|
||||
/// given numeric ID prefix, searching `active`, `backlog`, and `archived`
|
||||
/// alike so gateway `status <n>` resolves regardless of which project is
|
||||
/// currently "active" (story 1203).
|
||||
///
|
||||
/// `items_by_project` is the output of `fetch_all_project_pipeline_items`.
|
||||
/// Projects whose entry carries an `error` key (unreachable, no URL) are
|
||||
/// skipped rather than matched. Returns the owning project's name, or `None`
|
||||
/// if no registered, reachable project has a matching story.
|
||||
pub fn find_project_containing_story(
|
||||
items_by_project: &BTreeMap<String, Value>,
|
||||
num_str: &str,
|
||||
) -> Option<String> {
|
||||
for (name, status) in items_by_project {
|
||||
if status.get("error").is_some() {
|
||||
continue;
|
||||
}
|
||||
for key in ["active", "backlog", "archived"] {
|
||||
let Some(items) = status.get(key).and_then(|a| a.as_array()) else {
|
||||
continue;
|
||||
};
|
||||
let found = items.iter().any(|item| {
|
||||
item.get("story_id")
|
||||
.and_then(|s| s.as_str())
|
||||
.map(|story_id| story_number_prefix(story_id) == num_str)
|
||||
.unwrap_or(false)
|
||||
});
|
||||
if found {
|
||||
return Some(name.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Extract the leading numeric prefix from a `story_id` like `"42_story_x"`,
|
||||
/// or `""` if the ID has no purely-digit prefix before the first `_`.
|
||||
fn story_number_prefix(story_id: &str) -> &str {
|
||||
story_id
|
||||
.split('_')
|
||||
.next()
|
||||
.filter(|s| !s.is_empty() && s.chars().all(|c| c.is_ascii_digit()))
|
||||
.unwrap_or("")
|
||||
}
|
||||
|
||||
// ── Tests ────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -267,4 +312,84 @@ mod tests {
|
||||
// alpha's section should come before beta's (BTreeMap sorted order).
|
||||
assert!(output.find("alpha").unwrap() < output.find("beta").unwrap());
|
||||
}
|
||||
|
||||
// ── find_project_containing_story ──────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn find_story_matches_active_array() {
|
||||
let mut items = BTreeMap::new();
|
||||
items.insert(
|
||||
"huskies".to_string(),
|
||||
json!({ "active": [{ "story_id": "42_story_x", "name": "X", "stage": "current" }], "backlog": [], "archived": [] }),
|
||||
);
|
||||
assert_eq!(
|
||||
find_project_containing_story(&items, "42"),
|
||||
Some("huskies".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_story_matches_backlog_array() {
|
||||
let mut items = BTreeMap::new();
|
||||
items.insert(
|
||||
"huskies".to_string(),
|
||||
json!({ "active": [], "backlog": [{ "story_id": "77_story_y", "name": "Y" }], "archived": [] }),
|
||||
);
|
||||
assert_eq!(
|
||||
find_project_containing_story(&items, "77"),
|
||||
Some("huskies".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_story_matches_archived_array() {
|
||||
let mut items = BTreeMap::new();
|
||||
items.insert(
|
||||
"huskies".to_string(),
|
||||
json!({ "active": [], "backlog": [], "archived": [{ "story_id": "13_story_z", "name": "Z" }] }),
|
||||
);
|
||||
assert_eq!(
|
||||
find_project_containing_story(&items, "13"),
|
||||
Some("huskies".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_story_returns_none_when_not_present_anywhere() {
|
||||
let mut items = BTreeMap::new();
|
||||
items.insert(
|
||||
"huskies".to_string(),
|
||||
json!({ "active": [{ "story_id": "1_story_a", "name": "A" }], "backlog": [], "archived": [] }),
|
||||
);
|
||||
assert_eq!(find_project_containing_story(&items, "999"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_story_skips_unreachable_projects() {
|
||||
let mut items = BTreeMap::new();
|
||||
items.insert(
|
||||
"broken".to_string(),
|
||||
json!({ "error": "connection refused" }),
|
||||
);
|
||||
items.insert(
|
||||
"huskies".to_string(),
|
||||
json!({ "active": [{ "story_id": "5_story_b", "name": "B" }], "backlog": [], "archived": [] }),
|
||||
);
|
||||
assert_eq!(
|
||||
find_project_containing_story(&items, "5"),
|
||||
Some("huskies".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
/// A project literally named after the digits (e.g. "42") must not
|
||||
/// spuriously match — only `story_id` prefixes are compared.
|
||||
#[test]
|
||||
fn find_story_does_not_match_on_project_name() {
|
||||
let mut items = BTreeMap::new();
|
||||
items.insert(
|
||||
"42".to_string(),
|
||||
json!({ "active": [{ "story_id": "1_story_a", "name": "A" }], "backlog": [], "archived": [] }),
|
||||
);
|
||||
assert_eq!(find_project_containing_story(&items, "42"), None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,9 @@ pub mod polling;
|
||||
/// Pure signed release-manifest verification (signature, sha256, rollback) — no I/O.
|
||||
pub mod release_manifest;
|
||||
|
||||
pub use aggregation::{format_aggregate_status_compact, format_overview_compact};
|
||||
pub use aggregation::{
|
||||
find_project_containing_story, format_aggregate_status_compact, format_overview_compact,
|
||||
};
|
||||
pub use config::{GatewayConfig, ProjectEntry};
|
||||
pub use identity::{IdentityCheck, check_identity};
|
||||
pub use io::{
|
||||
|
||||
Reference in New Issue
Block a user