"""Tests for the unified persona system prompt. The persona should match the user's configured wake word so renaming the wake word to e.g. "Friday" produces a butler named Friday, not one still hardcoded to Jarvis. """ from jarvis.system_prompt import ( build_system_prompt, load_agent_instructions, output_language_directive, reply_language_directive, ENGLISH_ONLY_DIRECTIVE, ) class TestBuildSystemPrompt: def test_default_name_is_jarvis(self): prompt = build_system_prompt() assert "named Jarvis" in prompt def test_custom_name_replaces_jarvis(self): prompt = build_system_prompt("Friday") assert "named Friday" in prompt assert "named Jarvis" not in prompt def test_lowercase_wake_word_is_capitalised(self): prompt = build_system_prompt("friday".capitalize()) assert "named Friday" in prompt def test_blank_name_falls_back_to_jarvis(self): assert "named Jarvis" in build_system_prompt("") assert "named Jarvis" in build_system_prompt(" ") assert "named Jarvis" in build_system_prompt(None) # type: ignore[arg-type] def test_default_keeps_user_language_clause(self): # Without a lock, the persona still mirrors the user's language. assert "in the user's language" in build_system_prompt("Jarvis") def test_language_lock_rewrites_user_language_clause(self): # With a lock, the contradicting "user's language" clause is rewritten # so the persona does not fight the OUTPUT_LANGUAGE directive. prompt = build_system_prompt("Jarvis", "Korean") assert "in the user's language" not in prompt assert "in Korean" in prompt class TestOutputLanguageDirective: """A deployment may lock replies to a single language via OUTPUT_LANGUAGE. Unset (the default) must keep the assistant's multilingual behaviour of replying in the user's own language, so the helper returns None and no directive is injected. """ def test_unset_returns_none(self): assert output_language_directive(None) is None assert output_language_directive("") is None assert output_language_directive(" ") is None def test_set_language_is_named_and_exclusive(self): directive = output_language_directive("Korean") assert directive is not None assert "Korean" in directive # Must force exclusivity, not merely prefer the language. assert "only" in directive.lower() def test_language_agnostic(self): # The helper takes any language string — no hardcoded single language. assert "French" in (output_language_directive("French") or "") assert "日本語" in (output_language_directive("日本語") or "") def test_strips_surrounding_whitespace(self): directive = output_language_directive(" Korean ") assert directive is not None assert "Korean" in directive assert " Korean" not in directive class TestReplyLanguageDirective: """Precedence: explicit OUTPUT_LANGUAGE lock > English-only TTS > free. The lock must override the Piper/Chatterbox English fallback, because a deployment that sets OUTPUT_LANGUAGE (e.g. Korean) also runs a TTS voice that can speak it. Without this, the English lock and the Korean lock contradict each other and the model reverts to English. """ def test_lock_overrides_english_only_tts(self): directive = reply_language_directive("Korean", "piper") assert directive is not None assert "Korean" in directive assert directive != ENGLISH_ONLY_DIRECTIVE def test_english_only_tts_forces_english_without_lock(self): assert reply_language_directive(None, "piper") == ENGLISH_ONLY_DIRECTIVE assert reply_language_directive("", "chatterbox") == ENGLISH_ONLY_DIRECTIVE def test_no_lock_multilingual_tts_is_free(self): # A non-English-only engine (e.g. melo) with no lock → reply in the # user's own language, so no directive. assert reply_language_directive(None, "melo") is None def test_unknown_tts_defaults_to_english_only(self): # Preserves the original getattr(cfg, 'tts_engine', 'piper') default: # an unknown/missing engine is treated conservatively as English-only. assert reply_language_directive(None, None) == ENGLISH_ONLY_DIRECTIVE def test_lock_wins_even_with_multilingual_tts(self): directive = reply_language_directive("Korean", "melo") assert directive is not None and "Korean" in directive def test_edge_is_multilingual(self): # Edge TTS (the default Korean voice) is not English-only: no lock → the # user's own language, and a lock is honoured (not forced to English). assert reply_language_directive(None, "edge") is None directive = reply_language_directive("Korean", "edge") assert directive is not None and "Korean" in directive assert directive != ENGLISH_ONLY_DIRECTIVE class TestLoadAgentInstructions: """Operator can extend the reply LLM's system prompt by dropping *.md files into an agents/ folder. The loader concatenates them in filename order and fails open so a missing/empty/broken folder never breaks a reply.""" def test_missing_dir_returns_empty(self, tmp_path): assert load_agent_instructions(str(tmp_path / "does-not-exist")) == "" def test_empty_dir_returns_empty(self, tmp_path): assert load_agent_instructions(str(tmp_path)) == "" def test_reads_and_concatenates_single_file(self, tmp_path): (tmp_path / "rules.md").write_text("Always be brief.", encoding="utf-8") assert load_agent_instructions(str(tmp_path)) == "Always be brief." def test_files_are_ordered_by_filename(self, tmp_path): # Filename prefixes let the operator control ordering. (tmp_path / "10-second.md").write_text("SECOND", encoding="utf-8") (tmp_path / "00-first.md").write_text("FIRST", encoding="utf-8") result = load_agent_instructions(str(tmp_path)) assert result.index("FIRST") < result.index("SECOND") def test_only_md_files_are_read(self, tmp_path): (tmp_path / "note.txt").write_text("IGNORE ME", encoding="utf-8") (tmp_path / "use.md").write_text("USE ME", encoding="utf-8") result = load_agent_instructions(str(tmp_path)) assert "USE ME" in result assert "IGNORE ME" not in result def test_blank_files_are_skipped(self, tmp_path): (tmp_path / "blank.md").write_text(" \n ", encoding="utf-8") (tmp_path / "real.md").write_text("Real instruction.", encoding="utf-8") assert load_agent_instructions(str(tmp_path)) == "Real instruction." def test_env_var_is_used_when_no_arg(self, tmp_path, monkeypatch): (tmp_path / "a.md").write_text("FROM ENV", encoding="utf-8") monkeypatch.setenv("AGENTS_DIR", str(tmp_path)) assert load_agent_instructions() == "FROM ENV" def test_explicit_arg_overrides_env(self, tmp_path, monkeypatch): (tmp_path / "env.md").write_text("ENV", encoding="utf-8") other = tmp_path / "other" other.mkdir() (other / "arg.md").write_text("ARG", encoding="utf-8") monkeypatch.setenv("AGENTS_DIR", str(tmp_path)) assert load_agent_instructions(str(other)) == "ARG" def test_a_file_path_instead_of_dir_returns_empty(self, tmp_path): f = tmp_path / "file.md" f.write_text("x", encoding="utf-8") # Pointed at a file, not a directory → fail-open. assert load_agent_instructions(str(f)) == ""