use std::path::Path;
use std::time::Duration;
use ratatui::buffer::Buffer;
use crate::lookout::app::{App, Body, Control, Effect, KeyPress, Msg, RevealedValue};
use crate::lookout::secrets::{SecretRow, SecretsModel, Source};
use crate::secret_readers::Reader;
use super::flock::full_app;
use super::render::render;
pub fn app_with_secrets() -> App {
let mut app = full_app();
app.update(Msg::Key(KeyPress::Secrets));
app.update(Msg::Secrets {
environment: "production".to_string(),
result: Ok(Box::new(SecretsModel {
environments: vec![
"all".to_string(),
"ci".to_string(),
"production".to_string(),
],
rows: vec![
SecretRow {
key: "DB_PASSWORD".to_string(),
source: Source::Operator,
in_force: Some("production".to_string()),
set_in: vec!["production".to_string()],
byte_len: Some(9),
readers: Vec::new(),
},
SecretRow {
key: "ELSEWHERE_ONLY".to_string(),
source: Source::Operator,
in_force: None,
set_in: vec!["ci".to_string()],
byte_len: None,
readers: Vec::new(),
},
SecretRow {
key: "SET_EVERYWHERE".to_string(),
source: Source::Operator,
in_force: Some("all".to_string()),
set_in: vec!["all".to_string()],
byte_len: Some(4),
readers: Vec::new(),
},
SecretRow {
key: "SET_IN_ALL_THREE".to_string(),
source: Source::Operator,
in_force: Some("all".to_string()),
set_in: vec![
"all".to_string(),
"ci".to_string(),
"production".to_string(),
],
byte_len: Some(4),
readers: Vec::new(),
},
],
..SecretsModel::default()
})),
});
app
}
pub fn app_with_a_maximum_length_secret() -> App {
let mut app = full_app();
app.update(Msg::Key(KeyPress::Secrets));
app.update(Msg::Secrets {
environment: "production".to_string(),
result: Ok(Box::new(SecretsModel {
environments: vec!["production".to_string()],
rows: vec![SecretRow {
key: "HUGE".to_string(),
source: Source::Operator,
in_force: Some("production".to_string()),
set_in: vec!["production".to_string()],
byte_len: Some(shep_core::secrets::MAX_VALUE_BYTES),
readers: Vec::new(),
}],
..SecretsModel::default()
})),
});
app
}
pub fn app_with_a_pushed_secret() -> App {
let mut app = full_app();
app.update(Msg::Key(KeyPress::Secrets));
app.update(Msg::Secrets {
environment: "production".to_string(),
result: Ok(Box::new(SecretsModel {
environments: vec!["production".to_string()],
rows: vec![SecretRow {
key: "vercel/API_TOKEN".to_string(),
source: Source::Namespace("vercel".to_string()),
in_force: Some("production".to_string()),
set_in: vec!["production".to_string()],
byte_len: Some(6),
readers: Vec::new(),
}],
..SecretsModel::default()
})),
});
app
}
pub fn app_with_secrets_and_a_provider_row() -> App {
let mut app = full_app();
app.update(Msg::Key(KeyPress::Secrets));
app.update(Msg::Secrets {
environment: "production".to_string(),
result: Ok(Box::new(SecretsModel {
environments: vec!["production".to_string()],
rows: vec![
SecretRow {
key: "DB_PASSWORD".to_string(),
source: Source::Operator,
in_force: Some("production".to_string()),
set_in: vec!["production".to_string()],
byte_len: Some(9),
readers: Vec::new(),
},
SecretRow {
key: "vercel/API_TOKEN".to_string(),
source: Source::Namespace("vercel".to_string()),
in_force: Some("production".to_string()),
set_in: vec!["production".to_string()],
byte_len: Some(6),
readers: Vec::new(),
},
],
..SecretsModel::default()
})),
});
app
}
pub fn app_with_interleaved_secret_sources() -> App {
let mut app = full_app();
app.update(Msg::Key(KeyPress::Secrets));
app.update(Msg::Secrets {
environment: "production".to_string(),
result: Ok(Box::new(SecretsModel {
environments: vec!["production".to_string()],
rows: vec![
SecretRow {
key: "FIRST_OPERATOR_KEY".to_string(),
source: Source::Operator,
in_force: Some("production".to_string()),
set_in: vec!["production".to_string()],
byte_len: Some(1),
readers: Vec::new(),
},
SecretRow {
key: "vercel/API_TOKEN".to_string(),
source: Source::Namespace("vercel".to_string()),
in_force: Some("production".to_string()),
set_in: vec!["production".to_string()],
byte_len: Some(6),
readers: Vec::new(),
},
SecretRow {
key: "SECOND_OPERATOR_KEY".to_string(),
source: Source::Operator,
in_force: Some("production".to_string()),
set_in: vec!["production".to_string()],
byte_len: Some(2),
readers: Vec::new(),
},
],
..SecretsModel::default()
})),
});
app
}
pub const REVEALED_VALUE: &str = "hunter2-not-really";
pub fn app_with_secrets_and_reads(home: &Path, allow_read: bool) -> App {
let mut app = full_app();
app.update(Msg::Key(KeyPress::Secrets));
app.update(Msg::Secrets {
environment: "production".to_string(),
result: Ok(Box::new(secrets_model(home, allow_read))),
});
app
}
pub fn secrets_model(home: &Path, allow_read: bool) -> SecretsModel {
let store = home.join("secrets.json");
shep_core::secrets::set(&store, "DB_PASSWORD", "production", REVEALED_VALUE).unwrap();
SecretsModel {
environments: vec!["all".to_string(), "production".to_string()],
rows: vec![
SecretRow {
key: "DB_PASSWORD".to_string(),
source: Source::Operator,
in_force: Some("production".to_string()),
set_in: vec!["production".to_string()],
byte_len: Some(REVEALED_VALUE.len()),
readers: Vec::new(),
},
SecretRow {
key: "OTHER_KEY".to_string(),
source: Source::Operator,
in_force: Some("production".to_string()),
set_in: vec!["production".to_string()],
byte_len: Some(3),
readers: Vec::new(),
},
],
allow_read,
store,
..SecretsModel::default()
}
}
fn secrets_write_model() -> SecretsModel {
SecretsModel {
environments: vec![
"all".to_string(),
"ci".to_string(),
"production".to_string(),
],
rows: vec![SecretRow {
key: "DB_PASSWORD".to_string(),
source: Source::Operator,
in_force: Some("all".to_string()),
set_in: vec!["all".to_string()],
byte_len: Some(9),
readers: Vec::new(),
}],
..SecretsModel::default()
}
}
pub fn app_with_secrets_read_only() -> App {
let mut app = full_app();
app.update(Msg::Key(KeyPress::Secrets));
app.update(Msg::Secrets {
environment: "all".to_string(),
result: Ok(Box::new(secrets_write_model())),
});
app
}
pub fn app_with_secrets_and_control() -> App {
let mut app = app_with_secrets_read_only();
app.set_control_for_tests(Control::Allowed);
app
}
pub fn app_with_secrets_on_a_named_tab_and_control() -> App {
let mut app = app_with_secrets();
app.set_control_for_tests(Control::Allowed);
app
}
pub fn app_typing_a_value() -> App {
let mut app = app_with_secrets_and_control();
app.update(Msg::Key(KeyPress::Confirm));
app
}
pub fn app_armed_to_delete_a_secret() -> App {
let mut app = app_with_secrets_and_control();
app.update(Msg::Key(KeyPress::SecretDelete));
app
}
pub fn app_typing_a_new_key() -> App {
let mut app = app_with_secrets_and_control();
app.update(Msg::Key(KeyPress::SelectLast));
app.update(Msg::Key(KeyPress::Confirm));
app
}
pub fn app_with_a_pushed_secret_selected_and_control() -> App {
let mut app = app_with_a_pushed_secret();
app.set_control_for_tests(Control::Allowed);
app
}
pub fn app_revealing_with_control() -> App {
let dir = tempfile::tempdir().unwrap();
let mut app = app_revealing(dir.path());
app.set_control_for_tests(Control::Allowed);
let _ = dir.keep();
app
}
#[track_caller]
pub fn ask_to_reveal(app: &mut App) -> Msg {
let Effect::RevealSecret {
store,
provider_cache,
row,
environment,
} = app.update(Msg::Key(KeyPress::Reveal))
else {
panic!("`v` over an open gate reads the store");
};
Msg::Revealed {
key: row.key.clone(),
environment,
value: crate::lookout::secrets::stored_value(&store, &provider_cache, &row)
.map(RevealedValue),
}
}
#[track_caller]
pub fn app_revealing(home: &Path) -> App {
let mut app = app_with_secrets_and_reads(home, true);
let answer = ask_to_reveal(&mut app);
app.update(answer);
assert!(
matches!(app.body(), Body::Secrets(pane) if pane.reveal.is_some()),
"the fixture starts with a value on screen"
);
app
}
fn app_with_one_row_and_readers(readers: Vec<Reader>) -> App {
let mut app = full_app();
app.update(Msg::Key(KeyPress::Secrets));
app.update(Msg::Secrets {
environment: "production".to_string(),
result: Ok(Box::new(SecretsModel {
environments: vec!["production".to_string()],
rows: vec![SecretRow {
key: "DB_PASSWORD".to_string(),
source: Source::Operator,
in_force: Some("production".to_string()),
set_in: vec!["production".to_string()],
byte_len: Some(9),
readers,
}],
roll_age: Some(Duration::from_secs(184)),
..SecretsModel::default()
})),
});
app
}
pub fn render_secrets_with_readers() -> Buffer {
let app = app_with_one_row_and_readers(vec![
Reader {
name: "catcher".to_string(),
environment: "production".to_string(),
online: true,
},
Reader {
name: "web".to_string(),
environment: "production".to_string(),
online: false,
},
]);
render(&app, 160, 48)
}
pub fn render_secrets_with_more_readers_than_fit() -> Buffer {
let app = app_with_one_row_and_readers(
["catcher", "web", "worker", "api", "scheduler"]
.into_iter()
.map(|name| Reader {
name: name.to_string(),
environment: "production".to_string(),
online: true,
})
.collect(),
);
render(&app, 160, 48)
}
pub fn render_secrets_gate_shut() -> Buffer {
render(&app_with_secrets(), 160, 48)
}
pub fn render_secrets_with_roll_age(age: Duration) -> Buffer {
let mut app = full_app();
app.update(Msg::Key(KeyPress::Secrets));
app.update(Msg::Secrets {
environment: "production".to_string(),
result: Ok(Box::new(SecretsModel {
environments: vec!["production".to_string()],
rows: vec![SecretRow {
key: "DB_PASSWORD".to_string(),
source: Source::Operator,
in_force: Some("production".to_string()),
set_in: vec!["production".to_string()],
byte_len: Some(9),
readers: Vec::new(),
}],
roll_age: Some(age),
..SecretsModel::default()
})),
});
render(&app, 160, 48)
}
pub fn render_secrets_with_no_roll() -> Buffer {
render_secrets_gate_shut()
}