use std::path::{Path, PathBuf};
use std::time::Instant;
use shep_core::protocol::{DogSource, ProcessInfo, Response};
use shep_core::status::ProcStatus;
use crate::commands::settings::{DogView, SettingEdit, SettingsSnapshot, Shape};
use crate::lookout::app::{App, Control, Effect, KeyPress, Msg, Sent, Settings, SettingsRow};
use crate::lookout::field::shep_toml_shape;
use crate::lookout::pane::PaneRow;
use crate::style::{StyleLevel, StyleSource};
use super::dog_pane::dog_section;
use super::flock::{app_with, flock_of};
use super::palette::plain;
pub fn settings_snapshot() -> SettingsSnapshot {
let document = serde_json::json!({
"daemon": {
"log_level": "warn",
"log_json": false,
"socket": "/home/ada/.shep/run/shep.sock",
"max_cron_sleep": "30s",
"environment": "production",
},
"whistle": { "allow_control": false },
"secrets": { "allow_read": false },
"style": { "level": "full" },
"interpreters": { "js": "node", "py": "python3" },
"hosts": ["ada@box"],
});
SettingsSnapshot {
document: document.as_object().cloned().unwrap_or_default(),
socket_default: PathBuf::from("/home/ada/.shep/run/shep.sock"),
style: (StyleLevel::Full, StyleSource::Config),
dogs: vec![
DogView::built_in("bark", false),
DogView::built_in("metrics", true),
],
}
}
pub fn app_in_settings() -> App {
let mut app = app_with(flock_of(3, 0), plain());
app.update(Msg::Key(KeyPress::Settings));
app.update(Msg::Settings {
result: Ok(settings_snapshot()),
});
app
}
fn key_of(settings: &Settings, row: SettingsRow) -> Option<String> {
let shape = shep_toml_shape();
let fields = shape.fields.fields();
Some(match row {
SettingsRow::Table(index) => shape.outline.tables[index].path.clone(),
SettingsRow::Field(index) => fields[index].key.clone(),
SettingsRow::Entry { map, index } => {
let key = &fields[map].key;
let (name, _) = settings.snapshot().entries(key).into_iter().nth(index)?;
format!("{key}.{name}")
}
SettingsRow::AddEntry(map) => format!("{}+", fields[map].key),
SettingsRow::Dog(index) => format!("dog.{}", settings.dog(index)?.name),
SettingsRow::DogKey { dog, key } => {
format!("dog.{}.{}", settings.dog(dog)?.name, key.label())
}
SettingsRow::DogGrant { dog, index } => {
format!("dog.{}.grant.{}", settings.dog(dog)?.name, index)
}
SettingsRow::DogEnv(dog) => format!("dog.{}.env", settings.dog(dog)?.name),
SettingsRow::DogEnvKey { dog, index } => {
let dog = settings.dog(dog)?;
format!("dog.{}.env.{}", dog.name, dog.env_keys.get(index)?)
}
SettingsRow::DogEnvNew(dog) => format!("dog.{}.env+", settings.dog(dog)?.name),
SettingsRow::DogField { dog, row } => {
let pane = settings.dog_tree(dog)?.pane()?;
let PaneRow::Field(field) = pane.rows().get(row)?.to_owned() else {
return None;
};
format!(
"dog.{}.{}",
settings.dog(dog)?.name,
pane.fields().fields().get(field)?.key
)
}
})
}
#[track_caller]
pub fn select_setting(app: &mut App, key: &str) {
app.update(Msg::Key(KeyPress::SelectFirst));
let settings = app.settings().expect("the settings screen is open");
let target = settings
.rows()
.into_iter()
.position(|row| key_of(settings, row).as_deref() == Some(key))
.unwrap_or_else(|| panic!("{key} is a row of the settings tree"));
for _ in 0..target {
app.update(Msg::Key(KeyPress::SelectDown));
}
}
pub fn app_in_settings_on(key: &str) -> App {
let mut app = app_in_settings_with_control();
select_setting(&mut app, key);
app
}
pub fn app_in_settings_at() -> (App, Instant) {
let t0 = Instant::now();
let mut app = App::new(plain(), Control::Allowed, "/home/ada/.shep".to_string(), t0);
app.update(Msg::Key(KeyPress::Settings));
app.update(Msg::Settings {
result: Ok(settings_snapshot()),
});
(app, t0)
}
pub fn app_in_settings_with_shadowed_style(source: StyleSource) -> App {
let mut app = app_in_settings_on("style.level");
let mut snapshot = settings_snapshot();
snapshot.style = (StyleLevel::Bare, source);
app.update(Msg::Settings {
result: Ok(snapshot),
});
app
}
pub fn app_in_settings_with_silent_style() -> App {
let mut app = app_in_settings_on("style.level");
let mut snapshot = settings_snapshot();
snapshot.document.remove("style");
snapshot.style = (StyleLevel::Full, StyleSource::Default);
app.update(Msg::Settings {
result: Ok(snapshot),
});
app
}
pub fn app_in_settings_with_control() -> App {
let mut app = app_in_settings();
app.set_control_for_tests(Control::Allowed);
app
}
pub fn app_in_settings_with_a_dog_open() -> App {
let mut app = app_in_settings_with_control();
open_dog_row(&mut app, "bark");
app
}
#[track_caller]
pub fn open_adopted_dog_row(app: &mut App, name: &str, path: &Path) {
open_adopted_dog_row_with(
app,
name,
path,
serde_json::json!({
"type": "object",
"properties": { "ratio": { "type": "number" } },
}),
"ratio = 1.5\n",
);
}
#[track_caller]
pub fn open_adopted_dog_row_with(
app: &mut App,
name: &str,
path: &Path,
schema: serde_json::Value,
toml: &str,
) {
select_setting(app, &format!("dog.{name}"));
let effect = app.update(Msg::Key(KeyPress::Edit));
if !matches!(effect, Effect::LoadDogPane { .. }) {
panic!("opening a dog row asks for its schema, got {effect:?}");
}
app.update(Msg::DogPane {
name: name.to_owned(),
adopted_path: Some(path.to_path_buf()),
env_keys: Vec::new(),
result: Ok(schema),
});
app.update(Msg::Replied {
sent: Sent::DogSection {
name: name.to_owned(),
},
result: Ok(Response::DogSection {
toml: toml.to_owned().into(),
}),
});
}
#[track_caller]
pub fn open_dog_row(app: &mut App, name: &str) {
select_setting(app, &format!("dog.{name}"));
let effect = app.update(Msg::Key(KeyPress::Edit));
if !matches!(effect, Effect::LoadDogPane { .. }) {
panic!("opening a dog row asks for its schema, got {effect:?}");
}
app.update(Msg::DogPane {
name: name.to_owned(),
adopted_path: None,
env_keys: Vec::new(),
result: Ok(crate::dog::builtin_schema(name).expect("the fixture's dog is a built-in")),
});
app.update(Msg::Replied {
sent: Sent::DogSection {
name: name.to_owned(),
},
result: Ok(Response::DogSection {
toml: dog_section().into(),
}),
});
}
pub const BARK_RECORDED: &str = "[daemon]\nenabled_dogs = [\"bark\"]\n\n[daemon.dog_grants.bark]\ngranted = [\"flock.read\"]\nwaiting = [\"events:log.\"]\nreviewed = true\n";
pub const OTEL_WITH_ENV: &str = "[daemon]\nenabled_dogs = [\"otel\"]\n\n[daemon.adopted_dogs.otel]\npath = \"/usr/local/bin/shep-otel\"\nenv = { PADDOCK_KEY = \"hunter2\" }\n";
pub const OTEL_BARE: &str = "# which dogs run\n[daemon]\nenabled_dogs = [\"otel\"]\n\n[daemon.adopted_dogs]\n# the exporter\notel = \"/usr/local/bin/shep-otel\" # built from main\nzulu = \"/opt/zulu\"\n";
pub fn home_with(text: &str) -> (tempfile::TempDir, shep_core::paths::ShepPaths) {
let dir = tempfile::tempdir().expect("a temp shep home");
let paths = shep_core::paths::ShepPaths::resolve(&|_| None, dir.path());
std::fs::create_dir_all(&paths.home).expect("the home exists");
std::fs::write(&paths.daemon_config, text).expect("shep.toml is written");
(dir, paths)
}
const FAKE_SOCKET: &str = "/home/ada/.shep/run/shep.sock";
pub fn settings_over(text: &str) -> (tempfile::TempDir, shep_core::paths::ShepPaths, App) {
let (dir, paths) = home_with(text);
let snapshot = crate::commands::settings::load_settings(
&paths.daemon_config,
Path::new(FAKE_SOCKET),
(StyleLevel::Full, StyleSource::Default),
)
.expect("the fixture's shep.toml parses");
let mut app = app_with(flock_of(3, 0), plain());
app.set_control_for_tests(Control::Allowed);
app.update(Msg::Key(KeyPress::Settings));
app.update(Msg::Settings {
result: Ok(snapshot),
});
(dir, paths, app)
}
fn settings_snapshot_with_dogs(dogs: Vec<DogView>) -> SettingsSnapshot {
SettingsSnapshot {
dogs,
..settings_snapshot()
}
}
pub fn app_in_settings_with_dog_drift() -> App {
let flock = vec![
ProcessInfo::builder(90, "otel", ProcStatus::Online)
.pid(Some(90_000))
.dog(Some(DogSource::BuiltIn { grants: None }))
.build(),
];
let mut app = app_with(flock, plain());
app.update(Msg::Key(KeyPress::Settings));
app.update(Msg::Settings {
result: Ok(settings_snapshot_with_dogs(vec![
DogView::adopted("otel", false, Path::new("/usr/local/bin/shep-otel")),
DogView::adopted("ledger", true, Path::new("/opt/ledger/bin/dog")),
])),
});
app
}
pub fn app_in_settings_with_silent_dog() -> App {
let flock = vec![
ProcessInfo::builder(91, "bark", ProcStatus::Online)
.pid(Some(91_000))
.dog(Some(DogSource::BuiltIn { grants: None }))
.handshook(Some(false))
.build(),
];
let mut app = app_with(flock, plain());
app.update(Msg::Key(KeyPress::Settings));
app.update(Msg::Settings {
result: Ok(settings_snapshot_with_dogs(vec![DogView::built_in(
"bark", true,
)])),
});
app
}
fn settings_snapshot_for_toggle_tests() -> SettingsSnapshot {
settings_snapshot_with_dogs(vec![
DogView::built_in("metrics", false),
DogView::adopted("otel", true, Path::new("/usr/local/bin/shep-otel")),
])
}
pub fn app_in_settings_on_dog(name: &str) -> App {
let mut app = app_with(flock_of(3, 0), plain());
app.set_control_for_tests(Control::Allowed);
app.update(Msg::Key(KeyPress::Settings));
app.update(Msg::Settings {
result: Ok(settings_snapshot_for_toggle_tests()),
});
let dog_index = settings_snapshot_for_toggle_tests()
.dogs
.iter()
.position(|dog| dog.name == name)
.expect("name is one of settings_snapshot_for_toggle_tests's own dogs");
let target = app
.settings()
.unwrap()
.rows()
.into_iter()
.position(|row| row == SettingsRow::Dog(dog_index))
.expect("the dog is a row");
for _ in 0..target {
app.update(Msg::Key(KeyPress::SelectDown));
}
app
}
pub fn app_in_settings_on_enabled_dog(name: &str) -> App {
app_in_settings_on_dog(name)
}
#[track_caller]
pub fn setting_set(key: &str, value: &str) -> SettingEdit {
let shape = shep_toml_shape();
let field = shape.fields.by_key(key).expect("key is a row");
SettingEdit::Set {
path: shape.paths[key].clone(),
shape: Shape::of(&field.kind).expect("the key is typed in place"),
value: value.to_string(),
}
}
#[track_caller]
pub fn setting_unset(key: &str) -> SettingEdit {
SettingEdit::Unset {
path: shep_toml_shape().paths[key].clone(),
}
}