Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions tests/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ The suite is hermetic by construction (`tests/conftest.py` + `pyproject.toml` `[

Lessons that cost iterations getting the patch-coverage and mutation tail gates green:

- **Control a command's output shape with the real `--json` flag, never by patching
`output.resolve_json`.** `resolve_json` is now just `return explicit` (it no longer
auto-enables JSON off a tty), so a test wanting human output simply omits `--json`
(the suite's default) and one wanting machine output passes `--json` to `runner.invoke`.
A `monkeypatch.setattr("aai_cli.output.resolve_json", …)` is therefore a no-op that
bypasses the real argscan→`json_option`→`resolve_json` path — don't add one.
- **A boolean literal/default survives the mutation gate unless a test asserts the
difference between its two values**, not just that the line ran. `json_mode=False` passed
to `output.emit`, or `quiet=False` on `output.status`, get mutated to `True` — kill them by
Expand Down
34 changes: 10 additions & 24 deletions tests/test_account_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,8 @@ def _login_result(*, json_mode=False):
)


def _human(monkeypatch):
monkeypatch.setattr("aai_cli.output.resolve_json", lambda *, explicit: explicit)


def test_balance_formats_dollars(monkeypatch, mocker):
def test_balance_formats_dollars(mocker):
_auth()
_human(monkeypatch)
mocker.patch(
"aai_cli.commands.account.ams.get_balance",
autospec=True,
Expand Down Expand Up @@ -89,9 +84,8 @@ def fake_usage(jwt, start, end, window):
assert data["usage_items"][0]["line_items"][0]["price"] == 1250.0


def test_usage_renders_table_human(monkeypatch, mocker):
def test_usage_renders_table_human(mocker):
_auth()
_human(monkeypatch)
payload = {
"usage_items": [
{
Expand Down Expand Up @@ -194,9 +188,8 @@ def test_usage_models_format_windows_and_line_items():
assert _window({"line_items": [{"name": "free", "price": 0.0}]}).breakdown == ""


def test_usage_human_renders_breakdown(monkeypatch, mocker):
def test_usage_human_renders_breakdown(mocker):
_auth()
_human(monkeypatch)
payload = {
"usage_items": [
{
Expand All @@ -216,9 +209,8 @@ def test_usage_human_renders_breakdown(monkeypatch, mocker):
assert "minutes: $10.00" in result.output


def test_usage_human_summarizes_empty_range(monkeypatch, mocker):
def test_usage_human_summarizes_empty_range(mocker):
_auth()
_human(monkeypatch)
mocker.patch(
"aai_cli.commands.account.ams.get_usage", autospec=True, return_value={"usage_items": []}
)
Expand All @@ -227,9 +219,8 @@ def test_usage_human_summarizes_empty_range(monkeypatch, mocker):
assert "No usage windows returned" in result.output


def test_usage_human_hides_zero_windows_by_default(monkeypatch, mocker):
def test_usage_human_hides_zero_windows_by_default(mocker):
_auth()
_human(monkeypatch)
payload = {
"usage_items": [
{
Expand Down Expand Up @@ -257,9 +248,8 @@ def test_usage_human_hides_zero_windows_by_default(monkeypatch, mocker):
assert "Use --include-zero to show them." in result.output


def test_usage_human_can_include_zero_windows(monkeypatch, mocker):
def test_usage_human_can_include_zero_windows(mocker):
_auth()
_human(monkeypatch)
payload = {
"usage_items": [
{
Expand All @@ -278,9 +268,8 @@ def test_usage_human_can_include_zero_windows(monkeypatch, mocker):
assert "No usage in this range" not in result.output


def test_usage_all_is_a_back_compat_alias_for_include_zero(monkeypatch, mocker):
def test_usage_all_is_a_back_compat_alias_for_include_zero(mocker):
_auth()
_human(monkeypatch)
payload = {
"usage_items": [
{
Expand All @@ -297,9 +286,8 @@ def test_usage_all_is_a_back_compat_alias_for_include_zero(monkeypatch, mocker):
assert "2026-01-01" in result.output


def test_usage_human_summarizes_all_zero_range(monkeypatch, mocker):
def test_usage_human_summarizes_all_zero_range(mocker):
_auth()
_human(monkeypatch)
payload = {
"usage_items": [
{
Expand Down Expand Up @@ -355,9 +343,8 @@ def _no_login(**_kwargs):
get_usage.assert_not_called()


def test_limits_renders_services(monkeypatch, mocker):
def test_limits_renders_services(mocker):
_auth()
_human(monkeypatch)
mocker.patch(
"aai_cli.commands.account.ams.get_rate_limits",
autospec=True,
Expand All @@ -368,9 +355,8 @@ def test_limits_renders_services(monkeypatch, mocker):
assert "transcript" in result.output and "200" in result.output


def test_limits_human_summarizes_empty(monkeypatch, mocker):
def test_limits_human_summarizes_empty(mocker):
_auth()
_human(monkeypatch)
# The AMS endpoint returns an empty array when no custom rate limits are
# configured; show a clear message instead of a bare header-only table.
mocker.patch(
Expand Down
5 changes: 0 additions & 5 deletions tests/test_agent_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@ def fake_run_session(api_key, *, renderer, player, mic, config):

def test_agent_headphones_notice_in_human_mode(monkeypatch):
config.set_api_key("default", "sk_live")
monkeypatch.setattr("aai_cli.output.resolve_json", lambda *, explicit: False)
monkeypatch.setattr("aai_cli.commands.agent._exec.run_session", lambda *a, **k: None)
result = runner.invoke(app, ["agent"])
assert result.exit_code == 0
Expand Down Expand Up @@ -212,7 +211,6 @@ def test_agent_file_source_with_device_exits_2(monkeypatch, tmp_path):

def test_agent_file_source_no_headphones_notice(monkeypatch, tmp_path):
config.set_api_key("default", "sk_live")
monkeypatch.setattr("aai_cli.output.resolve_json", lambda *, explicit: False)
monkeypatch.setattr("aai_cli.commands.agent._exec.FileSource", lambda src: "filesrc")
monkeypatch.setattr("aai_cli.commands.agent._exec.run_session", lambda *a, **k: None)
wav = tmp_path / "say.wav"
Expand All @@ -224,7 +222,6 @@ def test_agent_file_source_no_headphones_notice(monkeypatch, tmp_path):

def test_agent_file_source_no_start_talking_notice(monkeypatch, tmp_path):
config.set_api_key("default", "sk_live")
monkeypatch.setattr("aai_cli.output.resolve_json", lambda *, explicit: False)
monkeypatch.setattr("aai_cli.commands.agent._exec.FileSource", lambda src: "filesrc")

def fake_run_session(api_key, *, renderer, player, mic, config):
Expand All @@ -241,7 +238,6 @@ def fake_run_session(api_key, *, renderer, player, mic, config):

def test_agent_mic_shows_start_talking_notice(monkeypatch):
config.set_api_key("default", "sk_live")
monkeypatch.setattr("aai_cli.output.resolve_json", lambda *, explicit: False)

# Avoid opening real audio hardware; the renderer is what we're testing.
class FakeDuplex:
Expand Down Expand Up @@ -320,7 +316,6 @@ def test_agent_headphones_notice_routes_to_stderr(monkeypatch):
# `assembly agent | head` must not eat the advisory as transcript data: in the
# default human mode the notice goes to stderr, stdout stays transcript-only.
config.set_api_key("default", "sk_live")
monkeypatch.setattr("aai_cli.output.resolve_json", lambda *, explicit: False)
monkeypatch.setattr("aai_cli.commands.agent._exec.run_session", lambda *a, **k: None)
result = _invoke_split(["agent"])
assert result.exit_code == 0
Expand Down
16 changes: 4 additions & 12 deletions tests/test_audit_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,6 @@ def _login_result(*, json_mode=False):
)


def _human(monkeypatch):
monkeypatch.setattr("aai_cli.output.resolve_json", lambda *, explicit: explicit)


def test_audit_renders_rows(mocker):
_auth()
payload = {
Expand Down Expand Up @@ -67,9 +63,8 @@ def test_audit_passes_filters(mocker):
)


def test_audit_human_mode_renders_table(monkeypatch, mocker):
def test_audit_human_mode_renders_table(mocker):
_auth()
_human(monkeypatch)
payload = {
"data": [
{
Expand Down Expand Up @@ -117,9 +112,8 @@ def test_audit_helpers_format_edge_cases():
assert audit._audit_rows({"data": "bad"}) == []


def test_audit_human_empty_result(monkeypatch, mocker):
def test_audit_human_empty_result(mocker):
_auth()
_human(monkeypatch)
mocker.patch(
"aai_cli.commands.audit.ams.list_audit_logs", autospec=True, return_value={"data": []}
)
Expand All @@ -128,9 +122,8 @@ def test_audit_human_empty_result(monkeypatch, mocker):
assert "No audit events found" in result.output


def test_audit_can_include_login_events(monkeypatch, mocker):
def test_audit_can_include_login_events(mocker):
_auth()
_human(monkeypatch)
payload = {
"data": [
{
Expand All @@ -151,9 +144,8 @@ def test_audit_can_include_login_events(monkeypatch, mocker):
assert "Hidden:" not in result.output


def test_audit_summarizes_all_login_rows(monkeypatch, mocker):
def test_audit_summarizes_all_login_rows(mocker):
_auth()
_human(monkeypatch)
payload = {
"data": [
{
Expand Down
3 changes: 1 addition & 2 deletions tests/test_config_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,7 @@ def test_config_list_json_is_the_full_settings_object():
}


def test_config_list_human_render_shows_rows_and_hint(monkeypatch):
monkeypatch.setattr("aai_cli.output.resolve_json", lambda *, explicit: explicit)
def test_config_list_human_render_shows_rows_and_hint():
config.set_api_key("staging", "sk_2")
config.set_profile_env("staging", "sandbox000")
result = runner.invoke(app, ["config", "list"])
Expand Down
6 changes: 2 additions & 4 deletions tests/test_doctor.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,7 @@ def test_doctor_no_keyring_recommends_env_var(healthy, monkeypatch):
assert "no usable OS keyring" in api["detail"]


def test_doctor_success_suggests_trying_transcribe(healthy, monkeypatch):
monkeypatch.setattr("aai_cli.output.resolve_json", lambda *, explicit: False)
def test_doctor_success_suggests_trying_transcribe(healthy):
result = runner.invoke(app, ["doctor"])
assert result.exit_code == 0, result.output
assert "assembly transcribe --sample" in result.output
Expand Down Expand Up @@ -315,8 +314,7 @@ def test_render_omits_profile_line_when_only_one_key_is_present() -> None:
assert "profile:" not in text


def test_doctor_human_output_shows_profile_and_environment(healthy, monkeypatch):
monkeypatch.setattr("aai_cli.output.resolve_json", lambda *, explicit: False)
def test_doctor_human_output_shows_profile_and_environment(healthy):
result = runner.invoke(app, ["doctor"])
assert result.exit_code == 0, result.output
assert "profile: default" in result.output
Expand Down
6 changes: 0 additions & 6 deletions tests/test_init_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,6 @@ def test_init_appears_in_help():
def test_init_prints_cli_banner_in_human_mode(tmp_path, monkeypatch):
# Vercel-style header at the top of an interactive run (human output only).
monkeypatch.chdir(tmp_path)
monkeypatch.setattr("aai_cli.output.resolve_json", lambda *, explicit: False)
result = runner.invoke(app, ["init", TEMPLATE, "x", "--no-install"])
assert result.exit_code == 0, result.output
# Decoration goes to stderr (data → stdout), so a piped stdout never sees it.
Expand All @@ -155,7 +154,6 @@ def test_init_banner_skipped_on_error_only_runs(tmp_path, monkeypatch):
# The banner prints only after validation passes: a pure error run (unknown
# template) stays undecorated like the sibling commands, and stdout stays empty.
monkeypatch.chdir(tmp_path)
monkeypatch.setattr("aai_cli.output.resolve_json", lambda *, explicit: False)
result = runner.invoke(app, ["init", "nope", "x", "--no-install"])
assert result.exit_code == 1
assert "AssemblyAI CLI" not in result.stderr
Expand All @@ -165,7 +163,6 @@ def test_init_banner_skipped_on_error_only_runs(tmp_path, monkeypatch):
def test_init_banner_skipped_on_target_conflict_error(tmp_path, monkeypatch):
# Target validation failures are error-only runs too: no banner.
monkeypatch.chdir(tmp_path)
monkeypatch.setattr("aai_cli.output.resolve_json", lambda *, explicit: False)
assert runner.invoke(app, ["init", TEMPLATE, "myapp", "--no-install"]).exit_code == 0
result = runner.invoke(app, ["init", TEMPLATE, "myapp", "--no-install"])
assert result.exit_code == 1
Expand Down Expand Up @@ -363,9 +360,6 @@ def test_init_launches_when_key_present(tmp_path, monkeypatch):
# Key present + install succeeds -> the server is launched and the browser opens.
monkeypatch.chdir(tmp_path)
monkeypatch.setenv("ASSEMBLYAI_API_KEY", "sk-real-key")
monkeypatch.setattr(
"aai_cli.output.resolve_json", lambda *, explicit: False
) # exercise human banner
monkeypatch.setattr(
"aai_cli.init.runner.run_setup",
lambda *a, **k: subprocess.CompletedProcess([], 0, "ok", ""),
Expand Down
1 change: 0 additions & 1 deletion tests/test_init_force_and_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ def test_init_force_warns_existing_files_are_overwritten(tmp_path, monkeypatch):
# --force overlays the template onto a non-empty target; the run must say so
# (on stderr) instead of silently clobbering files.
monkeypatch.chdir(tmp_path)
monkeypatch.setattr("aai_cli.output.resolve_json", lambda *, explicit: False)
assert runner.invoke(app, ["init", TEMPLATE, "myapp", "--no-install"]).exit_code == 0
result = runner.invoke(app, ["init", TEMPLATE, "myapp", "--no-install", "--force"])
assert result.exit_code == 0
Expand Down
7 changes: 2 additions & 5 deletions tests/test_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,8 @@ def test_keys_list_flattens_tokens(mocker):
assert "sk_abcdef1234" not in result.output # api key is masked


def test_keys_list_renders_table_human(monkeypatch, mocker):
def test_keys_list_renders_table_human(mocker):
_auth()
monkeypatch.setattr("aai_cli.output.resolve_json", lambda *, explicit: explicit)
projects = [
{
"project": {"id": 1, "name": "Default"},
Expand Down Expand Up @@ -161,7 +160,6 @@ def test_keys_list_renders_human_table(mocker):
],
}
]
mocker.patch("aai_cli.output.resolve_json", autospec=True, return_value=False)
mocker.patch("aai_cli.commands.keys.ams.list_projects", autospec=True, return_value=projects)
result = runner.invoke(app, ["keys", "list"])
assert result.exit_code == 0
Expand All @@ -184,9 +182,8 @@ def test_keys_create_rejects_empty_project_list(mocker):
assert "dashboard" in result.output


def test_keys_list_empty_shows_human_empty_state(monkeypatch, mocker):
def test_keys_list_empty_shows_human_empty_state(mocker):
_auth()
monkeypatch.setattr("aai_cli.output.resolve_json", lambda *, explicit: explicit)
# A project with no tokens yields no rows -> a friendly empty state, not a bare header.
projects = [{"project": {"id": 1, "name": "Default"}, "tokens": []}]
mocker.patch("aai_cli.commands.keys.ams.list_projects", autospec=True, return_value=projects)
Expand Down
5 changes: 1 addition & 4 deletions tests/test_login.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,8 @@ def test_whoami_reports_authenticated(mocker):
assert data["api_key"].startswith("sk_") and "…" in data["api_key"]


def test_whoami_human_render_shows_detail_rows(monkeypatch, mocker):
def test_whoami_human_render_shows_detail_rows(mocker):
config.set_api_key("default", "sk_1234567890")
monkeypatch.setattr("aai_cli.output.resolve_json", lambda *, explicit: explicit)
mocker.patch("aai_cli.commands.login.client.validate_key", autospec=True, return_value=True)
result = runner.invoke(app, ["whoami"])
assert result.exit_code == 0
Expand Down Expand Up @@ -355,7 +354,6 @@ def test_whoami_renders_human_table_reachable(mocker):
# a reachable status, and the account/session rows.
config.set_api_key("default", "sk_1234567890")
config.set_session("default", session_jwt="j", session_token="t", account_id=77)
mocker.patch("aai_cli.output.resolve_json", autospec=True, return_value=False)
mocker.patch("aai_cli.commands.login.client.validate_key", autospec=True, return_value=True)
result = runner.invoke(app, ["whoami"])
assert result.exit_code == 0
Expand All @@ -372,7 +370,6 @@ def test_whoami_renders_human_table_rejected_key(mocker):
# account/session "none" fallbacks (the em-dash placeholder). A rejected key
# is a failed preflight: the status still renders, but the exit code is 4.
config.set_api_key("default", "sk_1234567890")
mocker.patch("aai_cli.output.resolve_json", autospec=True, return_value=False)
mocker.patch("aai_cli.commands.login.client.validate_key", autospec=True, return_value=False)
result = runner.invoke(app, ["whoami"])
assert result.exit_code == 4
Expand Down
1 change: 0 additions & 1 deletion tests/test_login_guards.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@ def test_whoami_network_failure_still_renders_table(mocker):

config.set_api_key("default", "sk_1234567890")
config.set_session("default", session_jwt="j", session_token="t", account_id=77)
mocker.patch("aai_cli.output.resolve_json", autospec=True, return_value=False)
mocker.patch(
"aai_cli.commands.login.client.validate_key",
autospec=True,
Expand Down
6 changes: 2 additions & 4 deletions tests/test_login_with_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ def test_with_api_key_reads_stdin_and_stores(mocker):
assert payload["api_key_only"] is True # no AMS session from a key-only login


def test_with_api_key_human_mode_mentions_account_command_limit(mocker, monkeypatch):
monkeypatch.setattr("aai_cli.output.resolve_json", lambda *, explicit: explicit)
def test_with_api_key_human_mode_mentions_account_command_limit(mocker):
mocker.patch("aai_cli.commands.login.client.validate_key", autospec=True, return_value=True)
result = runner.invoke(app, ["login", "--with-api-key"], input="sk_piped\n")
assert result.exit_code == 0
Expand Down Expand Up @@ -71,8 +70,7 @@ def test_api_key_and_with_api_key_conflict(mocker):
assert config.get_api_key("default") is None


def test_api_key_flag_warns_toward_stdin_form(mocker, monkeypatch):
monkeypatch.setattr("aai_cli.output.resolve_json", lambda *, explicit: explicit)
def test_api_key_flag_warns_toward_stdin_form(mocker):
mocker.patch("aai_cli.commands.login.client.validate_key", autospec=True, return_value=True)
result = runner.invoke(app, ["login", "--api-key", "sk_flag"])
assert result.exit_code == 0
Expand Down
Loading
Loading