use shep_core::protocol::Response;
use crate::lookout::app::{App, Control, KeyPress, Msg, Sent};
use crate::lookout::pane::{EntryBody, PaneRow};
use super::flock::{app_with, flock_of};
use super::palette::plain;
use super::settings::settings_snapshot;
use super::sheep_pane::select_field;
pub fn dog_section() -> String {
"# how often\npoll = \"60s\"\nhistory_bytes = 4096\n\n[sinks.ops]\nkind = \"slack\"\nurl = \"https://hooks.example/x\"\n"
.to_string()
}
pub fn app_in_dog_pane() -> 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 _ in 0..6 {
app.update(Msg::Key(KeyPress::SelectDown));
}
app.update(Msg::Key(KeyPress::Edit));
app.update(Msg::DogPane {
name: "bark".to_string(),
adopted_path: None,
result: Ok(crate::dog::builtin_schema("bark").expect("bark is a built-in")),
});
app.update(Msg::Replied {
sent: Sent::DogSection {
name: "bark".to_string(),
},
result: Ok(Response::DogSection {
toml: dog_section().into(),
}),
});
app
}
pub fn nested_dog_schema() -> serde_json::Value {
serde_json::json!({
"$ref": "#/$defs/Settings",
"$defs": {
"Settings": {
"type": "object",
"properties": {
"name": { "type": "string" },
"models": { "$ref": "#/$defs/Models" },
},
},
"Models": {
"type": "object",
"properties": { "worker": { "$ref": "#/$defs/Worker" } },
},
"Worker": {
"type": "object",
"properties": {
"model": { "type": "string" },
"token": { "type": "string", "x-shep-secret": true },
},
},
},
})
}
pub fn nested_dog_section() -> String {
"# which model to run\nname = \"small\"\n\n[models.worker]\n# the credential\ntoken = \"sk-live-51Hx9Qa\"\nmodel = \"gpt\"\n"
.to_string()
}
pub fn app_in_dog_pane_with_a_nested_table() -> App {
let mut app = app_with(flock_of(3, 0), plain());
app.set_control_for_tests(Control::Allowed);
app.update(Msg::DogPane {
name: "jobs".to_string(),
adopted_path: None,
result: Ok(nested_dog_schema()),
});
app.update(Msg::Replied {
sent: Sent::DogSection {
name: "jobs".to_string(),
},
result: Ok(Response::DogSection {
toml: nested_dog_section().into(),
}),
});
app
}
pub fn entries_dog_schema() -> serde_json::Value {
serde_json::json!({
"type": "object",
"properties": {
"clients": { "type": "array", "items": { "$ref": "#/$defs/Client" } },
"models": {
"type": "object",
"additionalProperties": { "$ref": "#/$defs/Model" },
},
"retries": { "type": "integer" },
},
"$defs": {
"Client": {
"type": "object",
"properties": {
"id": { "type": "string" },
"key": { "type": "string", "x-shep-secret": true },
"name": { "type": "string" },
"peers": { "type": "array", "items": { "$ref": "#/$defs/Client" } },
"timeout": { "type": "integer" },
},
},
"Model": {
"type": "object",
"properties": {
"context": { "type": "integer" },
"provider": { "type": "string" },
},
},
},
})
}
pub fn entries_dog_defaults_schema() -> serde_json::Value {
let mut schema = entries_dog_schema();
let defs = &mut schema["$defs"];
defs["Client"]["properties"]["enabled"] =
serde_json::json!({ "type": "boolean", "default": true });
defs["Client"]["properties"]["key"]["default"] = serde_json::json!("change-me");
defs["Model"]["properties"]["context"]["default"] = serde_json::json!(4096);
schema
}
pub fn entries_dog_section() -> String {
concat!(
"retries = 3\n\n",
"[[clients]]\nname = \"alpha\"\nkey = \"hunter2\"\ntimeout = 30\n\n",
"[[clients]]\nid = \"b-2\"\nkey = \"hunter3\"\n\n",
"[[clients]]\nkey = \"hunter4\"\n\n",
"[models.large]\nprovider = \"remote\"\n\n",
"[models.small]\nprovider = \"local\"\ncontext = 4096\n",
)
.to_string()
}
pub fn app_in_dog_pane_with_entries() -> App {
let mut app = app_in_read_only_dog_pane_with_entries();
app.set_control_for_tests(Control::Allowed);
app
}
pub fn app_in_dog_pane_with_entry_defaults() -> App {
let mut app = app_with(flock_of(3, 0), plain());
app.set_control_for_tests(Control::Allowed);
app.update(Msg::DogPane {
name: "paddock".to_string(),
adopted_path: None,
result: Ok(entries_dog_defaults_schema()),
});
app.update(Msg::Replied {
sent: Sent::DogSection {
name: "paddock".to_string(),
},
result: Ok(Response::DogSection {
toml: entries_dog_section().into(),
}),
});
app
}
pub fn app_in_read_only_dog_pane_with_entries() -> App {
let mut app = app_with(flock_of(3, 0), plain());
app.update(Msg::DogPane {
name: "paddock".to_string(),
adopted_path: None,
result: Ok(entries_dog_schema()),
});
app.update(Msg::Replied {
sent: Sent::DogSection {
name: "paddock".to_string(),
},
result: Ok(Response::DogSection {
toml: entries_dog_section().into(),
}),
});
app
}
#[track_caller]
pub fn select_node(app: &mut App, path: &str) {
let pane = app.config_pane().expect("the pane is open");
let index = pane
.rows()
.iter()
.position(|row| match *row {
PaneRow::Field(index) => pane.fields().fields()[index].key == path,
PaneRow::Table(index) => pane.table_path(index) == Some(path),
PaneRow::Entry(index) => pane
.entry_row(index)
.is_some_and(|entry| match &entry.body {
EntryBody::Node { path: at, .. } => at == path,
EntryBody::Leaf { field, .. } => field.key == path,
EntryBody::New { field } => {
format!("{}[+]", pane.fields().fields()[*field].key) == path
}
}),
PaneRow::Choice { .. } | PaneRow::Arm { .. } | PaneRow::Env(_) | PaneRow::AddEnv => {
false
}
})
.unwrap_or_else(|| panic!("no node {path} is drawn"));
app.update(Msg::Key(KeyPress::SelectFirst));
for _ in 0..index {
app.update(Msg::Key(KeyPress::SelectDown));
}
}
pub fn list_dog_schema() -> serde_json::Value {
serde_json::json!({
"type": "object",
"properties": {
"hosts": {
"type": "array",
"items": { "type": "string", "minLength": 3, "pattern": "^[a-z.]+$" },
},
"keys": {
"type": "array",
"items": { "type": "string" },
"x-shep-secret": true,
},
"labels": { "type": "object", "additionalProperties": { "type": "string" } },
"ports": { "type": "array", "items": { "type": "integer" } },
},
})
}
pub fn list_dog_section() -> String {
"# who to ping\nhosts = [\"a.example\", \"b.example\"]\n# the api keys\nkeys = [\"sk-live-51Hx9Qa\"]\nports = [80, 443]\n"
.to_string()
}
pub fn app_in_dog_pane_with_lists() -> App {
app_in_dog_pane_over_lists(list_dog_section())
}
pub fn app_in_dog_pane_over_lists(section: String) -> App {
let mut app = app_with(flock_of(3, 0), plain());
app.set_control_for_tests(Control::Allowed);
app.update(Msg::DogPane {
name: "watch".to_string(),
adopted_path: None,
result: Ok(list_dog_schema()),
});
app.update(Msg::Replied {
sent: Sent::DogSection {
name: "watch".to_string(),
},
result: Ok(Response::DogSection {
toml: section.into(),
}),
});
app
}
pub fn app_in_dog_pane_with_two_edits() -> App {
let mut app = app_in_dog_pane();
for (key, typed) in [("poll", "45s"), ("history_bytes", "8192")] {
select_field(&mut app, key);
app.update(Msg::Key(KeyPress::Confirm));
for _ in 0..64 {
app.update(Msg::Key(KeyPress::TextBackspace));
}
for character in typed.chars() {
app.update(Msg::Key(KeyPress::TextChar(character)));
}
app.update(Msg::Key(KeyPress::TextApply));
}
assert_eq!(
app.config_pane().expect("the pane is open").edits().len(),
2,
"the fixture files two edits"
);
app
}