use std::os::unix::fs::PermissionsExt as _;
use std::time::Duration;
use serde_json::json;
use super::tests::{Place, harness, say};
use super::*;
use crate::provider::fake::{FakeServer, calls, says};
#[test]
fn a_call_as_it_streams_in_is_named_in_the_screens_language() {
let place = Place::new("view-live-verb");
let mut app = place.app(None);
app.phase = Phase::Replying;
app.live = vec![Live::Call("read_file".to_owned())];
let mut harness = harness(app);
harness.set_locale("tr");
let screen = harness.screen();
let verb = harness.env().i18n().translate("action.read", &[]);
assert_ne!(verb, "read_file", "Turkish has a word of its own for it");
assert!(screen.contains(&verb), "the verb in Turkish: {screen}");
assert!(!screen.contains("read_file"), "not the tool's name: {screen}");
}
#[test]
fn the_waiting_indicator_shows_only_for_a_wait_long_enough_to_notice() {
let place = Place::new("view-delayed");
let mut app = place.app(None);
app.phase = Phase::Replying;
let mut harness = harness(app);
assert!(!harness.screen().contains("waiting for the reply"), "not at once: {}", harness.screen());
harness.advance(Duration::from_millis(400));
assert!(harness.screen().contains("waiting for the reply"), "after a noticeable wait: {}", harness.screen());
}
#[test]
fn a_refusal_and_a_failed_action_carry_a_mark_and_not_only_a_colour() {
let place = Place::new("view-marks");
let server = FakeServer::start(vec![
(200, calls("c1", "read_file", &json!({"path": "missing.txt"}))),
(200, says("It is not there.")),
(401, json!({"error": {"message": "invalid api key"}}).to_string()),
]);
let mut harness = harness(place.app(Some(&server.address)));
let error = harness.env().icons().glyph("error").into_owned();
say(&mut harness, "read the missing file");
assert!(
harness.screen().contains(&format!("{error} `missing.txt` does not exist.")),
"the failed result has the error mark: {}",
harness.screen()
);
say(&mut harness, "again");
assert!(
harness.screen().contains(&format!("{error} The provider did not answer")),
"and so has the refusal: {}",
harness.screen()
);
}
#[test]
fn the_form_warns_of_a_key_sent_unencrypted_to_another_machine() {
let place = Place::new("view-plain-http");
let mut harness = harness(place.app(Some("http://models.example.test")));
harness.press("ctrl+o");
assert!(harness.screen().contains("sends the key unencrypted"), "{}", harness.screen());
for address in ["https://models.example.test", "http://127.0.0.1:8080", "http://localhost:8080"] {
let place = Place::new("view-plain-http-fine");
let mut fine = super::tests::harness(place.app(Some(address)));
fine.press("ctrl+o");
assert!(!fine.screen().contains("unencrypted"), "{address}: {}", fine.screen());
}
}
#[test]
fn the_form_warns_of_a_key_file_others_can_read_and_still_saves() {
let place = Place::new("view-open-key");
let key = place.base().join("key");
let mut harness = harness(place.app(Some("https://models.example.test")));
harness.press("ctrl+o");
assert!(!harness.screen().contains("Other users"), "a private key file is fine: {}", harness.screen());
harness.press("esc");
std::fs::set_permissions(&key, std::fs::Permissions::from_mode(0o644)).expect("readable by all");
harness.press("ctrl+o");
assert!(harness.screen().contains("Other users of this machine can read"), "{}", harness.screen());
harness.click_text("Save");
assert!(harness.app().form.is_none(), "a warning does not keep the form from saving");
}
#[test]
fn the_key_file_is_made_private_from_the_form() {
let place = Place::new("view-make-private");
let key = place.base().join("key");
std::fs::set_permissions(&key, std::fs::Permissions::from_mode(0o644)).expect("readable by all");
let mut harness = harness(place.app(Some("https://models.example.test")));
harness.press("ctrl+o");
harness.click_text("Make it private");
let mode = std::fs::metadata(&key).expect("the key file").permissions().mode() & 0o777;
assert_eq!(mode, 0o600, "the file is its owner's alone");
assert!(!harness.screen().contains("Other users"), "and the warning is gone: {}", harness.screen());
assert!(!harness.screen().contains("Make it private"), "with its button: {}", harness.screen());
}
#[test]
fn the_answers_of_a_long_conversation_are_parsed_once_and_let_go_with_it() {
let place = Place::new("view-answers");
let server = FakeServer::start(vec![]);
let mut app = place.app(Some(&server.address));
for i in 0..12 {
app.conversation.say(&format!("question {i}"));
app.conversation.reply(vec![json!({"type": "text", "text": format!("## Answer {i}\n\n- one\n- two")})]);
}
let mut harness = harness(app);
harness.render().render();
assert_eq!(harness.app().answers.held(), 12, "every answer drawn is kept");
harness.press("ctrl+n");
assert_eq!(harness.app().answers.held(), 0, "a new conversation lets them go");
}
#[test]
fn a_running_commands_last_lines_show_under_its_indicator_and_a_stopped_rounds_do_not() {
let place = Place::new("view-tail");
let server = FakeServer::start(vec![]);
let mut app = place.app(Some(&server.address));
let action = Action::Run { command: "cargo test".to_owned() };
let call = crate::conversation::Call { id: "r1".to_owned(), name: "run_command".to_owned(), input: json!({}) };
app.phase = Phase::Acting { call, action };
let round = app.round;
let mut harness = harness(app);
let lines: String = (1..=8).map(|n| format!("test number {n} ... ok\n")).collect();
harness.send(Msg::Tail(round, lines));
let screen = harness.screen();
assert!(screen.contains("test number 8 ... ok"), "the newest line is shown: {screen}");
assert!(screen.contains("test number 4 ... ok"), "and the four before it: {screen}");
assert!(!screen.contains("test number 3 ... ok"), "but no more than five: {screen}");
harness.send(Msg::Tail(round + 1, "from a stopped round".to_owned()));
assert!(!harness.screen().contains("from a stopped round"), "a round that is over is not shown");
}