Files
agent-manager/tests/i18n_test.rs
T

133 lines
4.9 KiB
Rust

//! i18n (issue #74): locale selection, English output, stable JSON contracts.
//!
//! The tests spawn the real am binary (CARGO_BIN_EXE_am) so the assertions
//! cover the actual command output, not just the library layer.
mod common;
use agent_manager::i18n::{self, Lang};
use std::path::PathBuf;
use std::process::Command;
/// Run the real am binary with args and a temp config; returns
/// (stdout, stderr, exit code).
fn am(args: &[&str], config: &PathBuf) -> (String, String, i32) {
let mut cmd = Command::new(env!("CARGO_BIN_EXE_am"));
cmd.arg("--config").arg(config).args(args);
let out = cmd.output().expect("am binary should run");
(
String::from_utf8_lossy(&out.stdout).to_string(),
String::from_utf8_lossy(&out.stderr).to_string(),
out.status.code().unwrap_or(-1),
)
}
/// A minimal valid config in a fresh temp dir (no user config leakage).
fn temp_config(tag: &str) -> (PathBuf, PathBuf) {
let dir = std::env::temp_dir().join(format!(
"am-i18n-{tag}-{}-{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.subsec_nanos()
));
std::fs::create_dir_all(&dir).unwrap();
let cfg = dir.join("config.yaml");
std::fs::write(
&cfg,
"version: \"1.0\"\nsettings:\n auto_install_deps: false\n confirm_before_run: false\nagents: []\n",
)
.unwrap();
(cfg, dir)
}
#[test]
fn lang_flag_selects_english_for_tip_content() {
let (cfg, _dir) = temp_config("flag");
// Explicit French: the tip page is French.
let (fr, _, _) = am(&["--lang", "fr", "tip"], &cfg);
assert!(fr.contains("Lancer & piloter"), "fr tip missing: {fr}");
// --lang en: the same page is translated.
let (en, _, code) = am(&["--lang", "en", "tip"], &cfg);
assert_eq!(code, 0);
assert!(en.contains("Run & control"), "en tip missing: {en}");
assert!(!en.contains("Lancer & piloter"), "untranslated section: {en}");
// The flag works after the subcommand too (global clap flag).
let (en2, _, _) = am(&["tip", "--lang", "en"], &cfg);
assert!(en2.contains("Run & control"), "en tip (post) missing: {en2}");
}
#[test]
fn am_lang_env_selects_english() {
let (cfg, _dir) = temp_config("env");
let mut cmd = Command::new(env!("CARGO_BIN_EXE_am"));
cmd.arg("--config").arg(&cfg).arg("tip");
cmd.env("AM_LANG", "en");
let out = cmd.output().unwrap();
let text = String::from_utf8_lossy(&out.stdout).to_string();
assert!(text.contains("Run & control"), "AM_LANG=en ignored: {text}");
}
#[test]
fn lang_env_values_are_parsed_leniently() {
// The host machine may export LANG/AM_LANG (git-bash sets LANG=en_GB):
// clear them so the default resolution is exercised deterministically.
std::env::remove_var("AM_LANG");
std::env::remove_var("LANG");
std::env::remove_var("LC_ALL");
std::env::remove_var("LC_MESSAGES");
assert_eq!(i18n::resolve(None), Lang::Fr); // no env -> default French
std::env::set_var("AM_LANG", "en_US.UTF-8");
assert_eq!(i18n::resolve(None), Lang::En);
std::env::remove_var("AM_LANG");
// The default follows the host LANG when set (spec: locale selon env).
std::env::set_var("LANG", "fr_FR.UTF-8");
assert_eq!(i18n::resolve(None), Lang::Fr);
}
#[test]
fn app_remembers_the_selected_language() {
let app = common::test_app("applang", &["--lang", "en", "list"]);
assert_eq!(app.lang(), Lang::En);
assert!(i18n::active().is_en());
let app_fr = common::test_app("applangfr", &["--lang", "fr", "list"]);
assert_eq!(app_fr.lang(), Lang::Fr);
assert!(!i18n::active().is_en());
}
#[test]
fn json_contract_is_identical_across_languages() {
// Issue #74 acceptance: --json field names and values are untouched by
// the language switch — only display strings are translated.
let (cfg, _dir) = temp_config("json");
let (fr, _, code_fr) = am(&["list", "--json"], &cfg);
let (en, _, code_en) = am(&["list", "--json", "--lang", "en"], &cfg);
assert_eq!(code_fr, 0);
assert_eq!(code_en, 0);
assert_eq!(fr, en, "list --json differs across languages");
// Same for status --json (structured detail).
let (fr_s, _, _) = am(&["status", "--json"], &cfg);
let (en_s, _, _) = am(&["status", "--json", "--lang", "en"], &cfg);
assert_eq!(fr_s, en_s, "status --json differs across languages");
}
#[test]
fn init_reports_stack_in_english() {
let (cfg, dir) = temp_config("init");
let work = dir.join("proj");
std::fs::create_dir_all(&work).unwrap();
let out = Command::new(env!("CARGO_BIN_EXE_am"))
.arg("--config")
.arg(&cfg)
.args(["--lang", "en", "init", "--force"])
.current_dir(&work)
.output()
.unwrap();
let text = String::from_utf8_lossy(&out.stderr).to_string();
assert!(
text.contains("none detected"),
"EN init message missing: {text}"
);
}