133 lines
4.9 KiB
Rust
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}"
|
|
);
|
|
}
|