use super::super::*;
#[allow(unused_imports)]
use super::support::*;
#[test]
fn app_rollup_worker_dlq_alert_counts() {
let envs = vec![crate::aws::Environment {
name: "worker-prod".into(),
application: "wapp".into(),
status: "Ready".into(),
health: "Green".into(),
platform: "Java 17".into(),
solution_stack: String::new(),
tier: "Worker".into(),
cname: String::new(),
version_label: String::new(),
arn: None,
updated: None,
id: None,
region: None,
}];
let mut dlq: HashMap<String, i64> = HashMap::new();
dlq.insert("worker-prod".into(), 7);
let r = crate::app::app_rollup(&envs, "wapp", &dlq);
assert_eq!(r.env_count, 1);
assert_eq!(r.red_count, 0, "EB health stays Green");
assert_eq!(
r.worker_dlq_alerts, 1,
"worker env with DLQ depth > 0 counts as alerting"
);
}
#[test]
fn compute_red_alerts_counts_eb_red_and_worker_dlq() {
use crate::aws::Environment;
let mk = |name: &str, tier: &str, health: &str| Environment {
name: name.into(),
application: "uflexi".into(),
status: "Ready".into(),
health: health.into(),
platform: "Java 17".into(),
solution_stack: String::new(),
tier: tier.into(),
cname: String::new(),
version_label: String::new(),
arn: None,
updated: None,
id: None,
region: None,
};
let envs = vec![
mk("web-prod", "Web", "Green"),
mk("web-red", "Web", "Red"),
mk("worker-green-dlq", "Worker", "Green"),
mk("worker-clean", "Worker", "Green"),
mk("worker-red", "Worker", "Severe"),
];
let mut dlq = std::collections::HashMap::new();
dlq.insert("worker-green-dlq".to_string(), 3);
dlq.insert("worker-clean".to_string(), 0);
assert_eq!(crate::app::compute_red_alerts(&envs, &dlq), 3);
}
#[test]
fn compute_red_alerts_ignores_dlq_for_web_tier() {
use crate::aws::Environment;
let env = Environment {
name: "web-prod".into(),
application: "uflexi".into(),
status: "Ready".into(),
health: "Green".into(),
platform: "Java 17".into(),
solution_stack: String::new(),
tier: "Web".into(),
cname: String::new(),
version_label: String::new(),
arn: None,
updated: None,
id: None,
region: None,
};
let mut dlq = std::collections::HashMap::new();
dlq.insert("web-prod".to_string(), 99);
assert_eq!(crate::app::compute_red_alerts(&[env], &dlq), 0);
}
#[test]
fn compute_red_alerts_zero_dlq_is_not_alert_worthy() {
use crate::aws::Environment;
let env = Environment {
name: "worker-clean".into(),
application: "uflexi".into(),
status: "Ready".into(),
health: "Green".into(),
platform: "Java 17".into(),
solution_stack: String::new(),
tier: "Worker".into(),
cname: String::new(),
version_label: String::new(),
arn: None,
updated: None,
id: None,
region: None,
};
let mut dlq = std::collections::HashMap::new();
dlq.insert("worker-clean".to_string(), 0);
assert_eq!(crate::app::compute_red_alerts(&[env], &dlq), 0);
}
#[tokio::test]
async fn worker_queue_fetch_error_keeps_previous_dlq_depth() {
let mut app = test_app();
app.handle_msg(AppMsg::WorkerQueueCheck {
gen: app.generation,
results: vec![("wk-prod".into(), Ok(Some(7)))],
});
assert_eq!(app.worker_dlq_depths.get("wk-prod"), Some(&7));
app.handle_msg(AppMsg::WorkerQueueCheck {
gen: app.generation,
results: vec![("wk-prod".into(), Err("AccessDenied".into()))],
});
assert_eq!(
app.worker_dlq_depths.get("wk-prod"),
Some(&7),
"error must not read as 'no DLQ'"
);
assert!(app.worker_dlq_stale.contains("wk-prod"));
app.handle_msg(AppMsg::WorkerQueueCheck {
gen: app.generation,
results: vec![("wk-prod".into(), Ok(Some(3)))],
});
assert!(!app.worker_dlq_stale.contains("wk-prod"));
assert_eq!(app.worker_dlq_depths.get("wk-prod"), Some(&3));
app.handle_msg(AppMsg::WorkerQueueCheck {
gen: app.generation,
results: vec![("wk-prod".into(), Ok(None))],
});
assert!(!app.worker_dlq_depths.contains_key("wk-prod"));
}
#[tokio::test]
async fn render_dlq_depth_tints_the_ready_pill_amber() {
let mut app = test_app();
app.environments = vec![mk_env("worker-x", "uflexi", "Worker", "Green")];
app.rebuild_view();
app.table_state.select(None);
let theme = app.theme.clone();
let clean = render_buf(&mut app, 140, 30);
let clean_amber = count_fg(&clean, theme.health_yellow);
app.worker_dlq_depths.insert("worker-x".into(), 12);
let backed_up = render_buf(&mut app, 140, 30);
let dlq_amber = count_fg(&backed_up, theme.health_yellow);
assert!(
dlq_amber > clean_amber,
"a non-empty DLQ should add amber (Ready-pill) cells \
(dlq={dlq_amber}, clean={clean_amber})"
);
}
#[tokio::test]
async fn dlq_destructive_operations_are_refused_in_read_only_mode() {
type DlqOp = fn(&mut App);
let cases: Vec<(&str, DlqOp)> = vec![
("purge", |app: &mut App| {
app.spawn_dlq_purge("api-prod".into(), "https://sqs/q-dlq".into())
}),
("replay", |app: &mut App| app.spawn_dlq_replay_batch(vec![])),
("resend", |app: &mut App| app.spawn_dlq_resend_selected()),
("delete", |app: &mut App| app.spawn_dlq_delete_one("m-1")),
];
for (name, op) in cases {
let mut app = read_only_app_with_env();
app.dlq = Some(open_dlq_state("api-prod"));
op(&mut app);
let err = app.error_message.as_deref().unwrap_or_default();
assert!(
err.contains("read-only mode"),
"DLQ {name} was not refused — got {err:?}"
);
}
}
#[tokio::test]
async fn dlq_destructive_operations_honour_a_per_env_pin() {
let mut app = read_only_app_with_env();
app.read_only = false;
app.cfg.safety_envs.insert("api-prod".into(), true);
app.spawn_dlq_purge("api-prod".into(), "https://sqs/q-dlq".into());
let err = app.error_message.as_deref().unwrap_or_default();
assert!(err.contains("safety.envs"), "got {err:?}");
}
#[tokio::test]
async fn detail_and_why_and_dlq_use_the_rows_own_region() {
let mut app = test_app();
let mut env = mk_env("api-prod", "uflexi", "Web", "Green");
env.region = Some("eu-west-2".into());
app.environments = vec![env.clone()];
app.rebuild_view();
app.table_state.select(Some(0));
assert_eq!(app.context.region, "us-east-1", "home region differs");
assert_eq!(app.region_for_name("api-prod"), "eu-west-2");
assert_eq!(app.region_for_name("not-in-the-table"), "us-east-1");
app.open_detail();
assert_eq!(
app.detail_client().region_for_tests(),
"eu-west-2",
"Detail must fetch from where the environment actually is"
);
app.dlq = Some(crate::app::DlqState {
env_name: "api-prod".into(),
main_queue_url: String::new(),
dlq_url: String::new(),
messages: Vec::new(),
list_state: Default::default(),
loading: false,
error: None,
confirm_purge: false,
purge_typed: tui_common::TextInput::new(),
viewing: crate::app::QueueView::Dlq,
confirm_delete_id: None,
replay_input: None,
});
assert_eq!(
app.dlq_client().region_for_tests(),
"eu-west-2",
"an SQS queue URL doesn't even exist in the home region"
);
}
#[tokio::test]
async fn the_dlq_viewer_renders_its_messages() {
let mut app = test_app();
app.environments = vec![mk_env("wk-prod", "uflexi", "Worker", "Green")];
app.rebuild_view();
app.table_state.select(Some(0));
app.dlq = Some(crate::app::DlqState {
env_name: "wk-prod".into(),
main_queue_url: "https://sqs.eu-west-2.amazonaws.com/1/awseb-main".into(),
dlq_url: "https://sqs.eu-west-2.amazonaws.com/1/awseb-main-dlq".into(),
messages: vec![crate::aws::QueueMessage {
id: "MSG-CANARY-1".into(),
receipt_handle: "rh".into(),
body: "poison pill payload".into(),
receive_count: 7,
sent_at: None,
}],
list_state: Default::default(),
loading: false,
error: None,
confirm_purge: false,
purge_typed: tui_common::TextInput::new(),
viewing: crate::app::QueueView::Dlq,
confirm_delete_id: None,
replay_input: None,
});
app.mode = crate::app::Mode::Dlq;
let out = render(&mut app, 150, 30);
assert!(
out.contains("MSG-CANARY-1"),
"the message id renders:\n{out}"
);
assert!(out.contains("wk-prod"), "and the env it belongs to:\n{out}");
}
#[tokio::test]
async fn the_embedded_shell_pane_renders_its_transcript() {
let mut app = test_app();
let shell = crate::shell::ShellSession::demo(
"i-0canary123".into(),
"$ systemctl status web\nactive (running)\n",
28,
140,
);
for _ in 0..500 {
shell.tick_demo_typer();
}
shell.resize(28, 140);
app.current_shell = Some(Box::new(shell));
app.mode = crate::app::Mode::Shell;
let out = render(&mut app, 150, 30);
assert!(
out.contains("i-0canary123"),
"the pane titles itself with the instance it is attached to:\n{out}"
);
assert!(
out.contains("F12 detach"),
"and keeps the detach hint visible — it is the way out:\n{out}"
);
assert!(
out.contains("active (running)"),
"vt100 screen contents reach the ratatui buffer:\n{out}"
);
}
fn msg(id: &str) -> crate::aws::QueueMessage {
crate::aws::QueueMessage {
id: id.into(),
receipt_handle: format!("rh-{id}"),
body: "{}".into(),
receive_count: 1,
sent_at: None,
}
}
#[tokio::test]
async fn dlq_messages_from_another_env_or_queue_are_dropped() {
use crate::app::AppMsg;
let armed = || {
let mut app = test_app();
app.dlq = Some(open_dlq_state("api-prod"));
app
};
let ids = |app: &App| -> Vec<String> {
app.dlq
.as_ref()
.unwrap()
.messages
.iter()
.map(|m| m.id.clone())
.collect()
};
let mut app = armed();
app.handle_msg(AppMsg::DlqMessages {
gen: app.generation,
env_name: "api-prod".into(),
queue_url: "https://sqs/q-dlq".into(),
result: Ok(vec![msg("new-1"), msg("new-2")]),
});
assert_eq!(
ids(&app),
vec!["new-1", "new-2"],
"the matching peek applies"
);
let mut app = armed();
app.handle_msg(AppMsg::DlqMessages {
gen: app.generation,
env_name: "worker-prod".into(),
queue_url: "https://sqs/q-dlq".into(),
result: Ok(vec![msg("wrong-env")]),
});
assert_eq!(ids(&app), vec!["m-1"], "another env's messages were shown");
let mut app = armed();
app.handle_msg(AppMsg::DlqMessages {
gen: app.generation,
env_name: "api-prod".into(),
queue_url: "https://sqs/q".into(),
result: Ok(vec![msg("main-queue")]),
});
assert_eq!(
ids(&app),
vec!["m-1"],
"the main queue's messages were shown in the DLQ view"
);
}
#[tokio::test]
async fn a_dlq_refetch_clamps_the_cursor_into_the_new_page() {
use crate::app::AppMsg;
let with_cursor = |at: Option<usize>| {
let mut app = test_app();
let mut dlq = open_dlq_state("api-prod");
dlq.list_state.select(at);
app.dlq = Some(dlq);
app
};
let sel = |app: &App| app.dlq.as_ref().unwrap().list_state.selected();
let fetch = |app: &mut App, n: usize| {
let gen = app.generation;
app.handle_msg(AppMsg::DlqMessages {
gen,
env_name: "api-prod".into(),
queue_url: "https://sqs/q-dlq".into(),
result: Ok((0..n).map(|i| msg(&format!("m{i}"))).collect()),
});
};
let mut app = with_cursor(Some(2));
fetch(&mut app, 2);
assert_eq!(sel(&app), Some(0), "a cursor at index == len must reset");
let mut app = with_cursor(Some(9));
fetch(&mut app, 2);
assert_eq!(sel(&app), Some(0));
let mut app = with_cursor(Some(1));
fetch(&mut app, 3);
assert_eq!(sel(&app), Some(1), "a valid cursor survives the refetch");
let mut app = with_cursor(None);
fetch(&mut app, 3);
assert_eq!(sel(&app), Some(0));
let mut app = with_cursor(Some(1));
fetch(&mut app, 0);
assert_eq!(sel(&app), None, "an empty queue selects nothing");
}
#[tokio::test]
async fn a_dlq_op_removes_only_the_message_it_names() {
use crate::app::{AppMsg, DlqOp};
let armed = || {
let mut app = test_app();
let mut dlq = open_dlq_state("api-prod");
dlq.messages = vec![msg("m-1"), msg("m-2"), msg("m-3")];
app.dlq = Some(dlq);
app
};
let ids = |app: &App| -> Vec<String> {
app.dlq
.as_ref()
.unwrap()
.messages
.iter()
.map(|m| m.id.clone())
.collect()
};
for op in [
DlqOp::Deleted {
message_id: "m-2".into(),
},
DlqOp::Resent {
message_id: "m-2".into(),
},
] {
let mut app = armed();
app.handle_msg(AppMsg::DlqActionResult {
gen: app.generation,
env_name: "api-prod".into(),
result: Ok(op.clone()),
});
assert_eq!(
ids(&app),
vec!["m-1", "m-3"],
"{op:?} must drop only its own message — inverting the retain \
predicate would leave only that one"
);
}
let mut app = armed();
app.handle_msg(AppMsg::DlqActionResult {
gen: app.generation,
env_name: "worker-prod".into(),
result: Ok(DlqOp::Purged),
});
assert_eq!(
ids(&app),
vec!["m-1", "m-2", "m-3"],
"wrong env purged the view"
);
let mut app = armed();
app.handle_msg(AppMsg::DlqActionResult {
gen: app.generation,
env_name: "api-prod".into(),
result: Ok(DlqOp::Purged),
});
assert!(ids(&app).is_empty());
}
#[tokio::test]
async fn a_replay_with_failures_reports_as_an_error() {
use crate::app::{AppMsg, DlqOp};
let replay = |count: usize, failures: usize| {
let mut app = test_app();
app.dlq = Some(open_dlq_state("api-prod"));
app.handle_msg(AppMsg::DlqActionResult {
gen: app.generation,
env_name: "api-prod".into(),
result: Ok(DlqOp::Replayed { count, failures }),
});
app
};
let clean = replay(5, 0);
assert!(
clean
.status_message
.as_deref()
.unwrap_or("")
.contains("replayed 5"),
"a clean replay is a status message: {:?}",
clean.status_message
);
assert!(clean.error_message.is_none(), "and not an error");
let partial = replay(5, 2);
assert!(
partial
.error_message
.as_deref()
.unwrap_or("")
.contains("2 failed"),
"a partial replay is an error: {:?}",
partial.error_message
);
}
#[tokio::test]
async fn a_purge_fires_only_when_the_typed_name_matches() {
let armed = |typed: &str| {
let mut app = test_app();
let mut dlq = open_dlq_state("api-prod");
dlq.confirm_purge = true;
for c in typed.chars() {
dlq.purge_typed.handle_key(crossterm::event::KeyEvent {
code: KeyCode::Char(c),
modifiers: KeyModifiers::NONE,
kind: crossterm::event::KeyEventKind::Press,
state: crossterm::event::KeyEventState::NONE,
});
}
app.dlq = Some(dlq);
app.mode = crate::app::Mode::Dlq;
app
};
let still_confirming = |app: &App| app.dlq.as_ref().unwrap().confirm_purge;
let mut app = armed("api-prod");
press(&mut app, KeyCode::Enter, KeyModifiers::NONE);
assert!(
!still_confirming(&app),
"typing the env name exactly must confirm the purge"
);
assert!(app.dlq.as_ref().unwrap().purge_typed.text().is_empty());
for wrong in ["api-prod ", "api-pro", "API-PROD", "", "worker-prod"] {
let mut app = armed(wrong);
press(&mut app, KeyCode::Enter, KeyModifiers::NONE);
assert!(
still_confirming(&app),
"{wrong:?} is not the env name and must NOT purge"
);
}
let mut app = armed("api-pro");
press(&mut app, KeyCode::Esc, KeyModifiers::NONE);
assert!(!still_confirming(&app), "Esc leaves the confirm");
assert!(app.dlq.as_ref().unwrap().purge_typed.text().is_empty());
}
#[tokio::test]
async fn a_confirmed_purge_reaches_the_write_gate() {
let mut app = read_only_app_with_env();
let mut dlq = open_dlq_state("api-prod");
dlq.confirm_purge = true;
for c in "api-prod".chars() {
dlq.purge_typed.handle_key(crossterm::event::KeyEvent {
code: KeyCode::Char(c),
modifiers: KeyModifiers::NONE,
kind: crossterm::event::KeyEventKind::Press,
state: crossterm::event::KeyEventState::NONE,
});
}
app.dlq = Some(dlq);
app.mode = crate::app::Mode::Dlq;
press(&mut app, KeyCode::Enter, KeyModifiers::NONE);
let msg = app.error_message.clone().unwrap_or_default();
assert!(
msg.contains("read-only") || msg.contains("refus"),
"a confirmed purge must reach the write gate — a read-only env \
should have refused it: {msg:?}"
);
}
#[tokio::test]
async fn a_delete_confirm_takes_yes_and_cancels_on_anything_else() {
let armed = || {
let mut app = read_only_app_with_env();
let mut dlq = open_dlq_state("api-prod");
dlq.confirm_delete_id = Some("m-1".into());
app.dlq = Some(dlq);
app.mode = crate::app::Mode::Dlq;
app
};
for confirm in [KeyCode::Char('y'), KeyCode::Char('Y'), KeyCode::Enter] {
let mut app = armed();
press(&mut app, confirm, KeyModifiers::NONE);
assert!(
app.dlq.as_ref().unwrap().confirm_delete_id.is_none(),
"{confirm:?} answers the confirm"
);
let msg = app.error_message.clone().unwrap_or_default();
assert!(
msg.contains("read-only") || msg.contains("refus"),
"{confirm:?} must reach the write gate: {msg:?}"
);
}
for other in [KeyCode::Char('n'), KeyCode::Esc, KeyCode::Char('z')] {
let mut app = armed();
press(&mut app, other, KeyModifiers::NONE);
assert!(
app.dlq.as_ref().unwrap().confirm_delete_id.is_none(),
"{other:?} clears the confirm"
);
assert!(
app.error_message.is_none(),
"{other:?} must not dispatch anything: {:?}",
app.error_message
);
}
}
#[tokio::test]
async fn the_dlq_cursor_wraps_at_both_ends() {
let mut app = test_app();
let mut dlq = open_dlq_state("api-prod");
dlq.messages = vec![msg("m-1"), msg("m-2"), msg("m-3")];
dlq.list_state.select(Some(0));
app.dlq = Some(dlq);
app.mode = crate::app::Mode::Dlq;
let sel = |app: &App| app.dlq.as_ref().unwrap().list_state.selected();
press(&mut app, KeyCode::Char('j'), KeyModifiers::NONE);
assert_eq!(sel(&app), Some(1));
press(&mut app, KeyCode::Char('j'), KeyModifiers::NONE);
assert_eq!(sel(&app), Some(2));
press(&mut app, KeyCode::Char('j'), KeyModifiers::NONE);
assert_eq!(sel(&app), Some(0), "j wraps past the last message");
press(&mut app, KeyCode::Char('k'), KeyModifiers::NONE);
assert_eq!(sel(&app), Some(2), "k wraps past the first");
press(&mut app, KeyCode::Char('k'), KeyModifiers::NONE);
assert_eq!(sel(&app), Some(1));
press(&mut app, KeyCode::Down, KeyModifiers::NONE);
assert_eq!(sel(&app), Some(2));
press(&mut app, KeyCode::Up, KeyModifiers::NONE);
assert_eq!(sel(&app), Some(1));
let mut app = test_app();
let mut dlq = open_dlq_state("api-prod");
dlq.messages.clear();
dlq.list_state.select(None);
app.dlq = Some(dlq);
app.mode = crate::app::Mode::Dlq;
press(&mut app, KeyCode::Char('j'), KeyModifiers::NONE);
press(&mut app, KeyCode::Char('k'), KeyModifiers::NONE);
assert_eq!(sel(&app), None, "an empty queue has nothing to select");
}
#[tokio::test]
async fn resend_is_refused_on_the_main_queue_and_refetch_is_not() {
let viewing = |view: crate::app::QueueView| {
let mut app = read_only_app_with_env();
let mut dlq = open_dlq_state("api-prod");
dlq.viewing = view;
app.dlq = Some(dlq);
app.mode = crate::app::Mode::Dlq;
app
};
let mut app = viewing(crate::app::QueueView::Main);
press(&mut app, KeyCode::Char('r'), KeyModifiers::NONE);
assert!(
app.error_message
.as_deref()
.unwrap_or("")
.contains("only available in DLQ view"),
"resend must be refused on a live main queue: {:?}",
app.error_message
);
let mut app = viewing(crate::app::QueueView::Dlq);
press(&mut app, KeyCode::Char('r'), KeyModifiers::NONE);
let msg = app.error_message.clone().unwrap_or_default();
assert!(
!msg.contains("only available in DLQ view"),
"resend is allowed in DLQ view: {msg:?}"
);
let mut app = viewing(crate::app::QueueView::Main);
press(&mut app, KeyCode::Char('r'), KeyModifiers::CONTROL);
assert!(
!app.error_message
.as_deref()
.unwrap_or("")
.contains("only available in DLQ view"),
"Ctrl-R refetches and is safe on either queue: {:?}",
app.error_message
);
}
#[tokio::test]
async fn replay_opens_only_when_there_is_something_to_replay() {
let armed = |view: crate::app::QueueView, empty: bool| {
let mut app = test_app();
let mut dlq = open_dlq_state("api-prod");
dlq.viewing = view;
if empty {
dlq.messages.clear();
}
app.dlq = Some(dlq);
app.mode = crate::app::Mode::Dlq;
app
};
let prompt_open = |app: &App| app.dlq.as_ref().unwrap().replay_input.is_some();
let mut app = armed(crate::app::QueueView::Dlq, false);
press(&mut app, KeyCode::Char('R'), KeyModifiers::NONE);
assert!(prompt_open(&app), "R opens the replay prompt");
press(&mut app, KeyCode::Esc, KeyModifiers::NONE);
assert!(!prompt_open(&app), "Esc abandons the replay prompt");
let mut app = armed(crate::app::QueueView::Main, false);
press(&mut app, KeyCode::Char('R'), KeyModifiers::NONE);
assert!(!prompt_open(&app));
assert!(app
.error_message
.as_deref()
.unwrap_or("")
.contains("only available in DLQ view"));
let mut app = armed(crate::app::QueueView::Dlq, true);
press(&mut app, KeyCode::Char('R'), KeyModifiers::NONE);
assert!(!prompt_open(&app), "an empty DLQ has nothing to replay");
assert!(app.error_message.as_deref().unwrap_or("").contains("empty"));
}
#[tokio::test]
async fn m_toggles_the_queue_and_clears_the_stale_page() {
let mut app = test_app();
let mut dlq = open_dlq_state("api-prod");
dlq.viewing = crate::app::QueueView::Dlq;
app.dlq = Some(dlq);
app.mode = crate::app::Mode::Dlq;
press(&mut app, KeyCode::Char('m'), KeyModifiers::NONE);
{
let d = app.dlq.as_ref().unwrap();
assert!(matches!(d.viewing, crate::app::QueueView::Main), "toggled");
assert!(
d.messages.is_empty(),
"the previous queue's messages must not survive the switch — \
`x` acts on the selection, and it would be the wrong queue's"
);
assert_eq!(d.list_state.selected(), None);
}
press(&mut app, KeyCode::Char('m'), KeyModifiers::NONE);
assert!(matches!(
app.dlq.as_ref().unwrap().viewing,
crate::app::QueueView::Dlq
));
let mut app = test_app();
let mut dlq = open_dlq_state("api-prod");
dlq.main_queue_url = String::new();
app.dlq = Some(dlq);
app.mode = crate::app::Mode::Dlq;
press(&mut app, KeyCode::Char('m'), KeyModifiers::NONE);
assert!(matches!(
app.dlq.as_ref().unwrap().viewing,
crate::app::QueueView::Dlq
));
assert!(app
.error_message
.as_deref()
.unwrap_or("")
.contains("no main queue"));
}
#[tokio::test]
async fn the_dlq_destructive_keys_arm_only_what_they_should() {
let open = |viewing: crate::app::QueueView| {
let mut app = test_app();
app.environments = vec![mk_env("api-prod", "uflexi", "Worker", "Green")];
app.rebuild_view();
app.table_state.select(Some(0));
let mut dlq = open_dlq_state("api-prod");
dlq.viewing = viewing;
app.dlq = Some(dlq);
app.mode = crate::app::Mode::Dlq;
app
};
let state = |app: &App| {
let d = app.dlq.as_ref().expect("dlq open");
(d.confirm_delete_id.clone(), d.confirm_purge)
};
let mut app = open(crate::app::QueueView::Dlq);
press(&mut app, KeyCode::Char('x'), KeyModifiers::NONE);
assert_eq!(
state(&app),
(Some("m-1".to_string()), false),
"`x` should arm the delete for the selected message and nothing else"
);
let mut app = open(crate::app::QueueView::Dlq);
press(&mut app, KeyCode::Char('p'), KeyModifiers::NONE);
assert_eq!(
state(&app),
(None, true),
"`p` in DLQ view should arm the purge"
);
let mut app = open(crate::app::QueueView::Main);
press(&mut app, KeyCode::Char('p'), KeyModifiers::NONE);
assert_eq!(
state(&app),
(None, false),
"`p` armed a purge from the MAIN queue view"
);
assert!(
app.error_message
.as_deref()
.is_some_and(|m| m.contains("only available in DLQ view")),
"refusing silently is worse than refusing loudly; got {:?}",
app.error_message
);
}