use super::super::*;
pub(super) use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
pub(super) fn fake_env_with(
name: &str,
status: &str,
health: &str,
updated_minutes_ago: Option<i64>,
) -> Environment {
let updated = updated_minutes_ago.map(|m| chrono::Utc::now() - chrono::Duration::minutes(m));
Environment {
name: name.into(),
application: "app".into(),
status: status.into(),
health: health.into(),
platform: "Java 17".into(),
solution_stack: String::new(),
tier: "Web".into(),
cname: "x.elb".into(),
version_label: "v1".into(),
arn: None,
updated,
id: None,
region: None,
}
}
pub(super) fn opt(
ns: &str,
name: &str,
value: Option<&str>,
default: Option<&str>,
) -> crate::aws::ConfigOption {
crate::aws::ConfigOption {
namespace: ns.into(),
name: name.into(),
value: value.map(String::from),
default_value: default.map(String::from),
value_type: "Scalar".into(),
value_options: vec![],
change_severity: None,
user_defined: Some(true),
min_value: None,
max_value: None,
max_length: None,
}
}
pub(super) fn empty_resources() -> crate::aws::EnvResources {
crate::aws::EnvResources::default()
}
pub(super) fn make_event(msg: &str) -> crate::aws::Event {
crate::aws::Event {
at: Some(chrono::Utc::now()),
env: "uflexi-prod".into(),
application: "uflexi".into(),
message: msg.into(),
severity: "INFO".into(),
version_label: None,
}
}
pub(super) fn fake_env(name: &str, status: &str, health: &str, version: &str) -> Environment {
Environment {
name: name.into(),
application: "my-app".into(),
status: status.into(),
health: health.into(),
platform: "Java 17".into(),
solution_stack: String::new(),
tier: "Web".into(),
cname: format!("{name}.elb.amazonaws.com"),
version_label: version.into(),
arn: None,
updated: None,
id: None,
region: None,
}
}
pub(super) fn test_app() -> App {
let cfg = crate::config::Config {
theme: "dark".into(),
icons: "unicode".into(),
..crate::config::Config::default()
};
App::for_tests(crate::aws::AwsClient::stub(), cfg)
}
pub(super) fn press(app: &mut App, code: KeyCode, mods: KeyModifiers) {
app.handle_event(Event::Key(KeyEvent {
code,
modifiers: mods,
kind: KeyEventKind::Press,
state: crossterm::event::KeyEventState::NONE,
}));
}
pub(super) fn render(app: &mut App, w: u16, h: u16) -> String {
use ratatui::backend::TestBackend;
use ratatui::Terminal;
let backend = TestBackend::new(w, h);
let mut terminal = Terminal::new(backend).expect("terminal");
terminal.draw(|f| crate::ui::draw(f, app)).expect("draw");
let buf = terminal.backend().buffer();
let mut out = String::new();
for y in 0..buf.area.height {
for x in 0..buf.area.width {
out.push_str(buf[(x, y)].symbol());
}
out.push('\n');
}
out
}
pub(super) fn render_buf(app: &mut App, w: u16, h: u16) -> ratatui::buffer::Buffer {
use ratatui::backend::TestBackend;
use ratatui::Terminal;
let backend = TestBackend::new(w, h);
let mut terminal = Terminal::new(backend).expect("terminal");
terminal.draw(|f| crate::ui::draw(f, app)).expect("draw");
terminal.backend().buffer().clone()
}
pub(super) fn find_row(buf: &ratatui::buffer::Buffer, needle: &str) -> Option<u16> {
(0..buf.area.height).find(|&y| {
let mut row = String::new();
for x in 0..buf.area.width {
row.push_str(buf[(x, y)].symbol());
}
row.contains(needle)
})
}
pub(super) fn row_has_fg(
buf: &ratatui::buffer::Buffer,
y: u16,
color: ratatui::style::Color,
) -> bool {
(0..buf.area.width).any(|x| buf[(x, y)].fg == color)
}
pub(super) fn count_symbol_fg(
buf: &ratatui::buffer::Buffer,
y: u16,
sym: &str,
fg: ratatui::style::Color,
) -> usize {
(0..buf.area.width)
.filter(|&x| buf[(x, y)].symbol() == sym && buf[(x, y)].fg == fg)
.count()
}
pub(super) fn count_fg(buf: &ratatui::buffer::Buffer, color: ratatui::style::Color) -> usize {
(0..buf.area.height)
.map(|y| {
(0..buf.area.width)
.filter(|&x| buf[(x, y)].fg == color)
.count()
})
.sum()
}
pub(super) fn mk_env(name: &str, app: &str, tier: &str, health: &str) -> crate::aws::Environment {
crate::aws::Environment {
name: name.into(),
application: app.into(),
status: "Ready".into(),
health: health.into(),
platform: "Java 17".into(),
solution_stack: String::new(),
tier: tier.into(),
cname: format!("{name}.example.com"),
version_label: "build-1".into(),
arn: Some(format!("arn:aws:eb:us-east-1:0:env/{name}")),
updated: None,
id: None,
region: None,
}
}
pub(super) fn mk_modal(action: Action, env: &str) -> ConfirmModal {
ConfirmModal {
action,
target_env: env.into(),
params: ParameterisedAction::default(),
typed: TextInput::new(),
kind: ConfirmKind::YesNo,
dryrun: None,
loading_dryrun: false,
recent_events: None,
loading_events: false,
traffic_warning: None,
version_preview: None,
loading_version_preview: false,
health_check_probe: None,
loading_health_check: false,
unavailability_line: None,
loading_unavailability: false,
lint_issues: None,
loading_lint: false,
}
}
pub(super) fn mk_fleet_event(
env: &str,
severity: &str,
message: &str,
at_ms: i64,
) -> crate::aws::Event {
crate::aws::Event {
at: chrono::DateTime::from_timestamp_millis(at_ms),
env: env.into(),
application: "shop".into(),
message: message.into(),
severity: severity.into(),
version_label: None,
}
}
#[track_caller]
pub(super) fn assert_no_run_on_spaces(msg: &str) {
assert!(
!msg.contains(" "),
"message contains a double space (missing a `\\` continuation, or a \
stray space before a `{{}}` placeholder?): {msg:?}"
);
}
pub(super) const WRITE_COMMANDS: &[&str] = &[
"deployment-policy Rolling",
"rolling-update on",
"health-check-url /healthz",
"keypair my-key",
"service-role arn:aws:iam::1:role/svc",
"instance-profile arn:aws:iam::1:instance-profile/eb",
"public-ip on",
"elb-scheme internal",
"set-option aws:autoscaling:asg MinSize 2",
"tag Owner platform",
"untag Owner",
"logs-stream on",
"notify ops@example.com",
"rds-detach api-prod",
"alarm-create my-alarm 5xx 20",
"alarm-delete ebman-api-prod-5xx",
"config-save my-template",
"config-apply my-template",
];
pub(super) const APPLICATION_SCOPED_WRITES: &[&str] = &["config-delete uflexi my-template"];
pub(super) fn read_only_app_with_env() -> App {
let mut app = test_app();
app.environments = vec![mk_env("api-prod", "uflexi", "Web", "Green")];
app.rebuild_view();
app.table_state.select(Some(0));
app.read_only = true;
app
}
pub(super) const BATCH_WRITE_COMMANDS: &[&str] = &[
"batch-rebuild",
"batch-restart",
"batch-deploy build-900",
"batch-tag Owner platform",
"batch-untag Owner",
"batch-set-option aws:autoscaling:asg MinSize 2",
];
pub(super) fn open_dlq_state(env: &str) -> crate::app::DlqState {
let mut list_state = ratatui::widgets::ListState::default();
list_state.select(Some(0));
crate::app::DlqState {
env_name: env.into(),
main_queue_url: "https://sqs/q".into(),
dlq_url: "https://sqs/q-dlq".into(),
messages: vec![crate::aws::QueueMessage {
id: "m-1".into(),
receipt_handle: "rh-1".into(),
body: "{}".into(),
receive_count: 1,
sent_at: None,
}],
list_state,
loading: false,
error: None,
confirm_purge: false,
purge_typed: Default::default(),
viewing: crate::app::QueueView::Dlq,
confirm_delete_id: None,
replay_input: None,
}
}
pub(super) fn is_test_source(path: &std::path::Path) -> bool {
path.file_name().and_then(|f| f.to_str()) == Some("tests.rs")
|| path.components().any(|c| c.as_os_str() == "tests")
}
#[test]
fn is_test_source_excludes_test_trees_and_nothing_else() {
use std::path::Path;
assert!(is_test_source(Path::new("src/aws/tests.rs")));
assert!(is_test_source(Path::new("src/ui/tests.rs")));
assert!(is_test_source(Path::new("src/app/tests/refresh.rs")));
assert!(is_test_source(Path::new("src/app/tests/support.rs")));
assert!(!is_test_source(Path::new("src/app.rs")));
assert!(!is_test_source(Path::new("src/app/cmd_misc.rs")));
assert!(!is_test_source(Path::new("src/app/spawn_refresh.rs")));
assert!(!is_test_source(Path::new("src/ui/detail.rs")));
assert!(!is_test_source(Path::new("src/app/latest_stacks.rs")));
}
pub(super) const GATED_COMMANDS: &[&str] = &[
"restart",
"rebuild",
"terminate",
"stop",
"start",
"abort",
"scale 3",
"deploy build-900",
"delete-version build-900",
"custom-platform-delete arn:aws:elasticbeanstalk:eu-west-2:1:platform/my/1.0.0",
"unset-option aws:autoscaling:asg MinSize",
"env-edit",
"rds-attach",
"rollout build-900 --regions eu-west-2,us-east-1",
"capacity",
"scaling-triggers",
"listener-edit 443",
"subnets",
"elb-subnets",
"security-groups",
];