From e7c170a5b3abeead0494fff5b4b747877fbed5e3 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 13 Jun 2026 17:36:37 +0000 Subject: [PATCH] test: drop no-op resolve_json monkeypatching resolve_json is now just `return explicit` (it no longer auto-enables JSON off a tty), so the monkeypatches of aai_cli.output.resolve_json across the suite were either dead no-ops (passthrough / forced-False on invocations that never pass --json) or stood in for the real flag. - Delete the redundant patches and the _human() helpers that only wrapped them. - Convert test_setup / test_setup_install's _force_json fixtures to pass the real --json flag on the structured-output invocations, exercising the genuine argscan -> json_option -> resolve_json path instead of patching the resolver. - Remove the now-unused monkeypatch fixture params left behind. - Record the convention in tests/AGENTS.md. Test-only; full gate green. --- tests/AGENTS.md | 6 +++++ tests/test_account_command.py | 34 ++++++++------------------ tests/test_agent_command.py | 5 ---- tests/test_audit_command.py | 16 +++--------- tests/test_config_command.py | 3 +-- tests/test_doctor.py | 6 ++--- tests/test_init_command.py | 6 ----- tests/test_init_force_and_report.py | 1 - tests/test_keys.py | 7 ++---- tests/test_login.py | 5 +--- tests/test_login_guards.py | 1 - tests/test_login_with_key.py | 6 ++--- tests/test_replay_e2e.py | 26 ++++++-------------- tests/test_sessions_command.py | 16 +++--------- tests/test_setup.py | 20 +++++---------- tests/test_setup_install.py | 38 ++++++++++++----------------- tests/test_stream_command.py | 3 --- tests/test_stream_session.py | 1 - tests/test_telemetry_command.py | 12 ++------- tests/test_transcribe.py | 5 +--- tests/test_transcripts.py | 7 ++---- 21 files changed, 65 insertions(+), 159 deletions(-) diff --git a/tests/AGENTS.md b/tests/AGENTS.md index 0d28760a..8c2c0661 100644 --- a/tests/AGENTS.md +++ b/tests/AGENTS.md @@ -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 diff --git a/tests/test_account_command.py b/tests/test_account_command.py index 4b0c8067..f7d8cbca 100644 --- a/tests/test_account_command.py +++ b/tests/test_account_command.py @@ -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, @@ -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": [ { @@ -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": [ { @@ -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": []} ) @@ -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": [ { @@ -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": [ { @@ -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": [ { @@ -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": [ { @@ -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, @@ -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( diff --git a/tests/test_agent_command.py b/tests/test_agent_command.py index 3348c90b..d6c84f08 100644 --- a/tests/test_agent_command.py +++ b/tests/test_agent_command.py @@ -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 @@ -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" @@ -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): @@ -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: @@ -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 diff --git a/tests/test_audit_command.py b/tests/test_audit_command.py index 5cb77974..5bb94dd2 100644 --- a/tests/test_audit_command.py +++ b/tests/test_audit_command.py @@ -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 = { @@ -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": [ { @@ -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": []} ) @@ -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": [ { @@ -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": [ { diff --git a/tests/test_config_command.py b/tests/test_config_command.py index ad5989f8..17ad4908 100644 --- a/tests/test_config_command.py +++ b/tests/test_config_command.py @@ -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"]) diff --git a/tests/test_doctor.py b/tests/test_doctor.py index e30bdb97..d985f4a2 100644 --- a/tests/test_doctor.py +++ b/tests/test_doctor.py @@ -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 @@ -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 diff --git a/tests/test_init_command.py b/tests/test_init_command.py index 460701f5..d7d700e9 100644 --- a/tests/test_init_command.py +++ b/tests/test_init_command.py @@ -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. @@ -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 @@ -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 @@ -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", ""), diff --git a/tests/test_init_force_and_report.py b/tests/test_init_force_and_report.py index c3d0c5b6..77a69a63 100644 --- a/tests/test_init_force_and_report.py +++ b/tests/test_init_force_and_report.py @@ -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 diff --git a/tests/test_keys.py b/tests/test_keys.py index 9bd40524..ae19ad9e 100644 --- a/tests/test_keys.py +++ b/tests/test_keys.py @@ -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"}, @@ -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 @@ -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) diff --git a/tests/test_login.py b/tests/test_login.py index a5eb2ada..e1b61f9a 100644 --- a/tests/test_login.py +++ b/tests/test_login.py @@ -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 @@ -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 @@ -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 diff --git a/tests/test_login_guards.py b/tests/test_login_guards.py index c8d4c031..1f1045b1 100644 --- a/tests/test_login_guards.py +++ b/tests/test_login_guards.py @@ -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, diff --git a/tests/test_login_with_key.py b/tests/test_login_with_key.py index f9f4b3d9..f4d08b2f 100644 --- a/tests/test_login_with_key.py +++ b/tests/test_login_with_key.py @@ -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 @@ -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 diff --git a/tests/test_replay_e2e.py b/tests/test_replay_e2e.py index f1ef49c5..06374530 100644 --- a/tests/test_replay_e2e.py +++ b/tests/test_replay_e2e.py @@ -18,11 +18,6 @@ runner = CliRunner() -def _human(monkeypatch): - """Pin human output (the real default) so output assertions don't depend on a tty.""" - monkeypatch.setattr("aai_cli.output.resolve_json", lambda *, explicit: explicit) - - def _with_api_key(): config.set_api_key("default", "sk_live") @@ -31,9 +26,8 @@ def _with_session(): config.set_session("default", session_jwt="jwt", session_token="tok", account_id=12345) -def test_transcribe_sample_renders_real_transcript(monkeypatch, mocker): +def test_transcribe_sample_renders_real_transcript(mocker): _with_api_key() - _human(monkeypatch) mocker.patch( "aai_cli.transcribe_exec.client.transcribe", autospec=True, @@ -44,9 +38,8 @@ def test_transcribe_sample_renders_real_transcript(monkeypatch, mocker): assert "Smoke from hundreds of wildfires" in result.output -def test_transcripts_get_renders_real_text(monkeypatch, mocker): +def test_transcripts_get_renders_real_text(mocker): _with_api_key() - _human(monkeypatch) mocker.patch( "aai_cli.commands.transcripts.client.get_transcript", autospec=True, @@ -57,9 +50,8 @@ def test_transcripts_get_renders_real_text(monkeypatch, mocker): assert "Smoke from hundreds of wildfires" in result.output -def test_transcripts_list_renders_real_rows(monkeypatch, mocker): +def test_transcripts_list_renders_real_rows(mocker): _with_api_key() - _human(monkeypatch) mocker.patch( "aai_cli.commands.transcripts.client.list_transcripts", autospec=True, @@ -74,9 +66,8 @@ def test_transcripts_list_renders_real_rows(monkeypatch, mocker): assert "status" in result.output -def test_llm_renders_real_completion(monkeypatch, mocker): +def test_llm_renders_real_completion(mocker): _with_api_key() - _human(monkeypatch) mocker.patch( "aai_cli.commands.llm.gateway.complete", autospec=True, @@ -87,9 +78,8 @@ def test_llm_renders_real_completion(monkeypatch, mocker): assert "PONG" in result.output -def test_balance_renders_real_dollars(monkeypatch, mocker): +def test_balance_renders_real_dollars(mocker): _with_session() - _human(monkeypatch) mocker.patch( "aai_cli.commands.account.ams.get_balance", autospec=True, @@ -100,9 +90,8 @@ def test_balance_renders_real_dollars(monkeypatch, mocker): assert "$879.58" in result.output -def test_usage_renders_real_breakdown(monkeypatch, mocker): +def test_usage_renders_real_breakdown(mocker): _with_session() - _human(monkeypatch) mocker.patch( "aai_cli.commands.account.ams.get_usage", autospec=True, @@ -116,9 +105,8 @@ def test_usage_renders_real_breakdown(monkeypatch, mocker): assert "$" in result.output -def test_limits_renders_no_custom_limits(monkeypatch, mocker): +def test_limits_renders_no_custom_limits(mocker): _with_session() - _human(monkeypatch) mocker.patch( "aai_cli.commands.account.ams.get_rate_limits", autospec=True, diff --git a/tests/test_sessions_command.py b/tests/test_sessions_command.py index a75f9e75..fd1c8bf6 100644 --- a/tests/test_sessions_command.py +++ b/tests/test_sessions_command.py @@ -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_sessions_help_lists_list_before_get(): # Pins the list-then-get subcommand order `transcripts --help` mirrors. result = runner.invoke(app, ["sessions", "--help"]) @@ -63,9 +59,8 @@ def test_session_rows_filter_invalid_items(): assert sessions._session_rows([{"session_id": "s_1"}, "bad"]) == [{"session_id": "s_1"}] -def test_sessions_list_renders_table_human(monkeypatch, mocker): +def test_sessions_list_renders_table_human(mocker): _auth() - _human(monkeypatch) payload = { "data": [ { @@ -89,9 +84,8 @@ def test_sessions_list_renders_table_human(monkeypatch, mocker): assert "12.0" in result.output -def test_sessions_list_renders_zero_duration_as_zero(monkeypatch, mocker): +def test_sessions_list_renders_zero_duration_as_zero(mocker): _auth() - _human(monkeypatch) # 0 is a legitimate duration (a session that connected but streamed no audio): # it must render as "0", not be coerced to a blank cell like a missing value. # Neither row carries created_at, so the duration is the only digit in the table. @@ -118,9 +112,8 @@ def test_sessions_list_renders_zero_duration_as_zero(monkeypatch, mocker): assert "0" in result.output -def test_sessions_list_empty_shows_human_empty_state(monkeypatch, mocker): +def test_sessions_list_empty_shows_human_empty_state(mocker): _auth() - _human(monkeypatch) mocker.patch( "aai_cli.commands.sessions.ams.list_streaming", autospec=True, @@ -185,9 +178,8 @@ def test_sessions_list_without_status_passes_none(mocker): list_streaming.assert_called_once_with("jwt", limit=10, status=None) -def test_sessions_get_renders_detail(monkeypatch, mocker): +def test_sessions_get_renders_detail(mocker): _auth() - _human(monkeypatch) detail = { "session_id": "s_1", "status": "completed", diff --git a/tests/test_setup.py b/tests/test_setup.py index 0b3080b1..4c160dc2 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -17,14 +17,6 @@ def _isolate_home(tmp_path, monkeypatch): monkeypatch.delenv("CLAUDE_CONFIG_DIR", raising=False) -@pytest.fixture(autouse=True) -def _force_json(monkeypatch): - """These tests pin the structured step/status JSON. The CLI now defaults to human - text everywhere (JSON is opt-in), so force the machine output the assertions parse — - the equivalent of invoking each command with --json.""" - monkeypatch.setattr("aai_cli.output.resolve_json", lambda *, explicit: True) - - def test_proc_detail_prefers_stderr_then_falls_back_to_stdout(): from aai_cli import setup_exec as setup @@ -51,7 +43,7 @@ def test_remove_skill_failure_reports_failed(monkeypatch): FakeRun({("claude", "mcp", "get"): 1}, removes_skill=False), ) - result = runner.invoke(app, ["setup", "remove"]) + result = runner.invoke(app, ["setup", "remove", "--json"]) assert result.exit_code == 1 assert _statuses(result)["skill"] == "failed" # The failure detail surfaces the subprocess's stderr ("boom"), preferring it over @@ -77,7 +69,7 @@ def test_remove_skill_skipped_when_npx_missing(monkeypatch): FakeRun({("claude", "mcp", "get"): 1}), ) - result = runner.invoke(app, ["setup", "remove"]) + result = runner.invoke(app, ["setup", "remove", "--json"]) assert result.exit_code == 0 assert _statuses(result)["skill"] == "skipped" @@ -94,7 +86,7 @@ def test_remove_unwinds_all(monkeypatch, tmp_path): fake = FakeRun({("claude", "mcp", "get"): 0}) # present -> removable monkeypatch.setattr("aai_cli.setup_exec.subprocess.run", fake) - result = runner.invoke(app, ["setup", "remove"]) + result = runner.invoke(app, ["setup", "remove", "--json"]) assert result.exit_code == 0 assert _statuses(result) == {"mcp": "removed", "skill": "removed", "aai-cli skill": "removed"} assert ["claude", "mcp", "remove", "assemblyai-docs"] in fake.calls @@ -111,7 +103,7 @@ def test_remove_when_absent_is_not_an_error(monkeypatch): fake = FakeRun({("claude", "mcp", "get"): 1}) # absent monkeypatch.setattr("aai_cli.setup_exec.subprocess.run", fake) - result = runner.invoke(app, ["setup", "remove"]) + result = runner.invoke(app, ["setup", "remove", "--json"]) assert result.exit_code == 0 assert _statuses(result) == { "mcp": "not_installed", @@ -146,7 +138,7 @@ def test_remove_skips_mcp_when_claude_missing(monkeypatch): fake = FakeRun() monkeypatch.setattr("aai_cli.setup_exec.subprocess.run", fake) - result = runner.invoke(app, ["setup", "remove"]) + result = runner.invoke(app, ["setup", "remove", "--json"]) assert result.exit_code == 0 assert _statuses(result)["mcp"] == "skipped" assert not any(c[0] == "claude" for c in fake.calls) @@ -158,7 +150,7 @@ def test_remove_mcp_failure_reports_failed(monkeypatch): fake = FakeRun({("claude", "mcp", "get"): 0, ("claude", "mcp", "remove"): 1}) monkeypatch.setattr("aai_cli.setup_exec.subprocess.run", fake) - result = runner.invoke(app, ["setup", "remove"]) + result = runner.invoke(app, ["setup", "remove", "--json"]) assert result.exit_code == 1 assert _statuses(result)["mcp"] == "failed" diff --git a/tests/test_setup_install.py b/tests/test_setup_install.py index 184aeac7..016571f1 100644 --- a/tests/test_setup_install.py +++ b/tests/test_setup_install.py @@ -23,14 +23,6 @@ def _isolate_home(tmp_path, monkeypatch): monkeypatch.delenv("CLAUDE_CONFIG_DIR", raising=False) -@pytest.fixture(autouse=True) -def _force_json(monkeypatch): - """These tests pin the structured step/status JSON. The CLI now defaults to human - text everywhere (JSON is opt-in), so force the machine output the assertions parse — - the equivalent of invoking each command with --json.""" - monkeypatch.setattr("aai_cli.output.resolve_json", lambda *, explicit: True) - - # --- install: all three steps ------------------------------------------------ @@ -40,7 +32,7 @@ def test_install_happy_path_runs_all_steps(monkeypatch): fake = FakeRun({("claude", "mcp", "get"): 1}) monkeypatch.setattr("aai_cli.setup_exec.subprocess.run", fake) - result = runner.invoke(app, ["setup", "install"]) + result = runner.invoke(app, ["setup", "install", "--json"]) assert result.exit_code == 0 statuses = _statuses(result) @@ -77,7 +69,7 @@ def test_install_skill_failed_when_npx_succeeds_but_nothing_installed(monkeypatc fake = FakeRun({("claude", "mcp", "get"): 1}, creates_skill=False) monkeypatch.setattr("aai_cli.setup_exec.subprocess.run", fake) - result = runner.invoke(app, ["setup", "install"]) + result = runner.invoke(app, ["setup", "install", "--json"]) assert result.exit_code == 1 # skill step failed assert _statuses(result)["skill"] == "failed" # The detail quotes the install command starting at `add` (_SKILL_ADD[3:]), so the @@ -88,7 +80,7 @@ def test_install_skill_failed_when_npx_succeeds_but_nothing_installed(monkeypatc assert "'add AssemblyAI/assemblyai-skill --global --yes'" in skill_detail # And status agrees: still not installed. - status_result = runner.invoke(app, ["setup", "status"]) + status_result = runner.invoke(app, ["setup", "status", "--json"]) assert _statuses(status_result)["skill"] == "not_installed" @@ -171,7 +163,7 @@ def test_install_idempotent_when_mcp_present(monkeypatch): fake = FakeRun({("claude", "mcp", "get"): 0}) monkeypatch.setattr("aai_cli.setup_exec.subprocess.run", fake) - result = runner.invoke(app, ["setup", "install"]) + result = runner.invoke(app, ["setup", "install", "--json"]) assert result.exit_code == 0 assert _statuses(result)["mcp"] == "already" # No `mcp add` should have run. @@ -184,7 +176,7 @@ def test_install_failure_exits_nonzero(monkeypatch): fake = FakeRun({("claude", "mcp", "get"): 1, ("claude", "mcp", "add"): 1}) monkeypatch.setattr("aai_cli.setup_exec.subprocess.run", fake) - result = runner.invoke(app, ["setup", "install"]) + result = runner.invoke(app, ["setup", "install", "--json"]) assert result.exit_code == 1 assert _statuses(result)["mcp"] == "failed" @@ -195,7 +187,7 @@ def test_install_force_remove_failure_reports_failed(monkeypatch): fake = FakeRun({("claude", "mcp", "get"): 0, ("claude", "mcp", "remove"): 1}) monkeypatch.setattr("aai_cli.setup_exec.subprocess.run", fake) - result = runner.invoke(app, ["setup", "install", "--force"]) + result = runner.invoke(app, ["setup", "install", "--force", "--json"]) assert result.exit_code == 1 assert _statuses(result)["mcp"] == "failed" assert not any(c[:3] == ["claude", "mcp", "add"] for c in fake.calls) @@ -220,7 +212,7 @@ def test_install_skips_mcp_when_claude_missing(monkeypatch): fake = FakeRun() monkeypatch.setattr("aai_cli.setup_exec.subprocess.run", fake) - result = runner.invoke(app, ["setup", "install"]) + result = runner.invoke(app, ["setup", "install", "--json"]) assert result.exit_code == 0 # skip is not a failure statuses = _statuses(result) assert statuses["mcp"] == "skipped" @@ -243,7 +235,7 @@ def test_install_skill_idempotent_when_present(monkeypatch): fake = FakeRun({("claude", "mcp", "get"): 1}) monkeypatch.setattr("aai_cli.setup_exec.subprocess.run", fake) - result = runner.invoke(app, ["setup", "install"]) + result = runner.invoke(app, ["setup", "install", "--json"]) assert result.exit_code == 0 assert _statuses(result)["skill"] == "already" # No `npx … add` should have run — the skill was already present. @@ -259,7 +251,7 @@ def test_install_force_reinstalls_skill(monkeypatch): fake = FakeRun({("claude", "mcp", "get"): 1}) monkeypatch.setattr("aai_cli.setup_exec.subprocess.run", fake) - result = runner.invoke(app, ["setup", "install", "--force"]) + result = runner.invoke(app, ["setup", "install", "--force", "--json"]) assert result.exit_code == 0 assert _statuses(result)["skill"] == "installed" assert [ @@ -281,7 +273,7 @@ def test_install_skips_skill_when_npx_missing(monkeypatch): fake = FakeRun({("claude", "mcp", "get"): 1}) monkeypatch.setattr("aai_cli.setup_exec.subprocess.run", fake) - result = runner.invoke(app, ["setup", "install"]) + result = runner.invoke(app, ["setup", "install", "--json"]) assert result.exit_code == 0 statuses = _statuses(result) assert statuses["skill"] == "skipped" @@ -302,7 +294,7 @@ def test_install_aai_cli_skill_idempotent_when_present(monkeypatch): fake = FakeRun({("claude", "mcp", "get"): 1}) monkeypatch.setattr("aai_cli.setup_exec.subprocess.run", fake) - result = runner.invoke(app, ["setup", "install"]) + result = runner.invoke(app, ["setup", "install", "--json"]) assert result.exit_code == 0 assert _statuses(result)["aai-cli skill"] == "already" # Not overwritten without --force. @@ -317,7 +309,7 @@ def test_install_aai_cli_skill_force_reinstalls(monkeypatch): fake = FakeRun({("claude", "mcp", "get"): 1}) monkeypatch.setattr("aai_cli.setup_exec.subprocess.run", fake) - result = runner.invoke(app, ["setup", "install", "--force"]) + result = runner.invoke(app, ["setup", "install", "--force", "--json"]) assert result.exit_code == 0 assert _statuses(result)["aai-cli skill"] == "installed" # Overwritten with the bundled copy (references/ exist; placeholder gone). @@ -340,7 +332,7 @@ def test_status_reports_all_installed(monkeypatch, tmp_path): FakeRun({("claude", "mcp", "get"): 0}), ) - result = runner.invoke(app, ["setup", "status"]) + result = runner.invoke(app, ["setup", "status", "--json"]) assert result.exit_code == 0 assert _statuses(result) == { "mcp": "installed", @@ -356,7 +348,7 @@ def test_status_reports_not_installed(monkeypatch): FakeRun({("claude", "mcp", "get"): 1}), ) - result = runner.invoke(app, ["setup", "status"]) + result = runner.invoke(app, ["setup", "status", "--json"]) assert result.exit_code == 0 assert _statuses(result) == { "mcp": "not_installed", @@ -372,6 +364,6 @@ def test_status_mcp_unknown_when_claude_missing(monkeypatch): ) monkeypatch.setattr("aai_cli.setup_exec.subprocess.run", FakeRun()) - result = runner.invoke(app, ["setup", "status"]) + result = runner.invoke(app, ["setup", "status", "--json"]) assert result.exit_code == 0 assert _statuses(result)["mcp"] == "unknown" diff --git a/tests/test_stream_command.py b/tests/test_stream_command.py index 8676555c..7cc02a01 100644 --- a/tests/test_stream_command.py +++ b/tests/test_stream_command.py @@ -77,7 +77,6 @@ def fake_stream_audio( def test_stream_mic_listening_notice_waits_for_mic_open(monkeypatch): config.set_api_key("default", "sk_live") - monkeypatch.setattr("aai_cli.output.resolve_json", lambda *, explicit: False) captured = {} @@ -112,7 +111,6 @@ def fake_stream_audio( def test_stream_file_shows_no_listening_notice(monkeypatch, tmp_path): config.set_api_key("default", "sk_live") - monkeypatch.setattr("aai_cli.output.resolve_json", lambda *, explicit: False) def fake(api_key, source, *, params, on_begin=None, on_turn=None, on_termination=None): if on_begin: @@ -194,7 +192,6 @@ def raise_kbd(*a, **k): def test_stream_ctrl_c_human_mode_prints_stopped(monkeypatch): config.set_api_key("default", "sk_live") - monkeypatch.setattr("aai_cli.output.resolve_json", lambda *, explicit: False) def raise_kbd(*a, **k): raise KeyboardInterrupt diff --git a/tests/test_stream_session.py b/tests/test_stream_session.py index 6ce4a4d9..0de29751 100644 --- a/tests/test_stream_session.py +++ b/tests/test_stream_session.py @@ -381,7 +381,6 @@ def fake_stream_audio(api_key, source, *, params, **_kwargs): def test_stream_system_audio_parallel_keyboard_interrupt_exits_cleanly(monkeypatch): config.set_api_key("default", "sk_live") - monkeypatch.setattr("aai_cli.output.resolve_json", lambda *, explicit: False) class FakeSystemAudio: def __init__(self, *, on_open=None): diff --git a/tests/test_telemetry_command.py b/tests/test_telemetry_command.py index 79f985cf..297d2500 100644 --- a/tests/test_telemetry_command.py +++ b/tests/test_telemetry_command.py @@ -11,10 +11,6 @@ runner = CliRunner() -def _human(monkeypatch): - monkeypatch.setattr("aai_cli.output.resolve_json", lambda *, explicit: explicit) - - def _capture_events(monkeypatch, *, token="pub_test"): monkeypatch.setenv(telemetry.ENV_CLIENT_TOKEN, token) # Pre-mint the device id: the one-time first-run disclosure (covered in @@ -77,8 +73,7 @@ def test_status_json_source_for_env_kill_switch(monkeypatch): } -def test_status_human_disabled(monkeypatch): - _human(monkeypatch) +def test_status_human_disabled(): result = runner.invoke(app, ["telemetry", "status"]) assert result.exit_code == 0 assert "Telemetry is disabled." in result.output @@ -89,7 +84,6 @@ def test_status_human_disabled(monkeypatch): def test_status_human_enabled(monkeypatch): - _human(monkeypatch) _capture_events(monkeypatch) result = runner.invoke(app, ["telemetry", "status"]) assert result.exit_code == 0 @@ -100,7 +94,6 @@ def test_status_human_enabled(monkeypatch): def test_status_human_says_why_when_env_overrides_persisted_enable(monkeypatch): - _human(monkeypatch) _capture_events(monkeypatch) config.set_telemetry_enabled(enabled=True) monkeypatch.setenv("DO_NOT_TRACK", "1") @@ -126,8 +119,7 @@ def test_enable_persists_and_confirms_json(): assert config.get_telemetry_enabled() is True -def test_enable_and_disable_human(monkeypatch): - _human(monkeypatch) +def test_enable_and_disable_human(): result = runner.invoke(app, ["telemetry", "disable"]) assert result.exit_code == 0 assert "Telemetry disabled." in result.output diff --git a/tests/test_transcribe.py b/tests/test_transcribe.py index 59d5a209..8302d240 100644 --- a/tests/test_transcribe.py +++ b/tests/test_transcribe.py @@ -289,7 +289,6 @@ def fake_transform( def test_transcribe_prompt_human_shows_only_transform(monkeypatch, mocker): _auth() - monkeypatch.setattr("aai_cli.output.resolve_json", lambda *, explicit: False) mocker.patch( "aai_cli.transcribe_exec.client.transcribe", autospec=True, @@ -309,7 +308,6 @@ def test_transcribe_chained_prompts_human_labels_each_step(monkeypatch, mocker): # Human render of a multi-step chain labels each step (the single-step path # prints only the lone output; this one enumerates "Step N"). _auth() - monkeypatch.setattr("aai_cli.output.resolve_json", lambda *, explicit: False) mocker.patch( "aai_cli.transcribe_exec.client.transcribe", autospec=True, @@ -443,9 +441,8 @@ def _no_download(url, d, *, download_sections=None): assert tx.call_args.args[1] == "https://example.com/episode.mp3" -def test_transcribe_renders_summary_human(monkeypatch, mocker): +def test_transcribe_renders_summary_human(mocker): _auth() - monkeypatch.setattr("aai_cli.output.resolve_json", lambda *, explicit: False) t = _fake_transcript(mocker) t.summary = "three bullet summary" t.chapters = [] diff --git a/tests/test_transcripts.py b/tests/test_transcripts.py index f7da4559..8ad94439 100644 --- a/tests/test_transcripts.py +++ b/tests/test_transcripts.py @@ -150,9 +150,8 @@ def test_get_short_json_flag_emits_json(mocker): assert json.loads(result.output)["id"] == "t_42" -def test_list_empty_shows_human_empty_state(monkeypatch, mocker): +def test_list_empty_shows_human_empty_state(mocker): config.set_api_key("default", "sk_live") - monkeypatch.setattr("aai_cli.output.resolve_json", lambda *, explicit: False) mocker.patch( "aai_cli.commands.transcripts.client.list_transcripts", autospec=True, return_value=[] ) @@ -216,9 +215,8 @@ def test_list_limit_must_be_at_least_one(mocker): list_.assert_not_called() -def test_list_human_mode_renders_table(monkeypatch, mocker): +def test_list_human_mode_renders_table(mocker): config.set_api_key("default", "sk_live") - monkeypatch.setattr("aai_cli.output.resolve_json", lambda *, explicit: False) rows = [{"id": "t1", "status": "completed", "created": "2026-01-01"}] mocker.patch( "aai_cli.commands.transcripts.client.list_transcripts", autospec=True, return_value=rows @@ -249,7 +247,6 @@ def test_list_table_colors_status(monkeypatch, mocker): from aai_cli.theme import make_console config.set_api_key("default", "sk_live") - monkeypatch.setattr("aai_cli.output.resolve_json", lambda *, explicit: False) # Pin a truecolor console with an empty _environ so the rendered ANSI is # deterministic: Rich otherwise reads ambient color env (NO_COLOR/COLORTERM/...) # at render time, which leaks across tests and flips the color depth. With