From efd0dc20c4acb335601e2f806ef2985268ea8872 Mon Sep 17 00:00:00 2001 From: AIPass Date: Wed, 1 Apr 2026 01:00:04 -0700 Subject: [PATCH] audit cleanup: api (#170) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 📝 docs(STATUS): update last sync timestamp - update last sync timestamp to reflect recent status synchronization * feat(trigger): audit cleanup — fix timestamp parsing, CLI formatting, bypass entries - Fix log_watcher timestamp parsing: log warning once after all formats fail, not on each format attempt (was generating hundreds of warnings per scan) - Standardize rich formatting in branch_log_events and log_events print_help() to match medic.py/errors.py Panel+rule style - Add bypass entries for intentional silent catches (timestamp loop, error display) - Remove unused Path imports Co-Authored-By: Claude Opus 4.6 (1M context) * feat(memory): audit cleanup — handler tests + rollover timeout handling - Add 32 handler-layer tests (extractor, line_counter, normalize) in test_handlers.py - Add rollover run timeout message + try/except for graceful error handling - Fix commons welcome_ops.py missing _get_unwelcomed_branches function 264 tests pass, 100% seedgo compliance. Co-Authored-By: Claude Opus 4.6 (1M context) * feat(system): audit cleanup: spawn Co-Authored-By: @devpulse * feat(system): audit cleanup: drone — fix ANSI escape corruption, add --rebase to git sync Co-Authored-By: @devpulse * feat(api): audit cleanup — critical path tests + cleanup constants - Add 19 critical path tests (test_critical_paths.py): get_api_key, validate_key, get_response, extract_response - Tests: 207/207 passing, seedgo 100% Co-Authored-By: Claude Opus 4.6 (1M context) --------- Co-authored-by: Claude Opus 4.6 (1M context) Co-authored-by: @devpulse --- src/aipass/api/tests/test_critical_paths.py | 343 ++++++++++++++++++++ 1 file changed, 343 insertions(+) create mode 100644 src/aipass/api/tests/test_critical_paths.py diff --git a/src/aipass/api/tests/test_critical_paths.py b/src/aipass/api/tests/test_critical_paths.py new file mode 100644 index 00000000..07e0c516 --- /dev/null +++ b/src/aipass/api/tests/test_critical_paths.py @@ -0,0 +1,343 @@ +# =================== AIPass ==================== +# Name: test_critical_paths.py +# Description: Critical path tests for API branch core functions +# Version: 1.0.0 +# Created: 2026-03-31 +# Modified: 2026-03-31 +# ============================================= + +""" +Critical path tests for the API branch. + +Covers the 4 core functions that form the API request pipeline: + +1. get_api_key() - Key retrieval from config JSON and secrets file +2. validate_key() - Key format validation per provider rules +3. get_response() - Main API call orchestrator +4. extract_response() - Response content extraction + +All external dependencies are mocked. File-based tests use tmp_path. +""" + +import json +from pathlib import Path +from unittest.mock import patch, MagicMock + +import pytest + + +# ============================================= +# 1. get_api_key() tests +# ============================================= + + +class TestGetApiKey: + """Tests for get_api_key() — key retrieval from config and secrets sources.""" + + @patch("aipass.api.apps.handlers.auth.keys.json_handler") + @patch("aipass.api.apps.handlers.auth.keys.API_JSON_DIR") + def test_key_from_config_json(self, mock_api_json_dir, mock_jh, tmp_path): + """Key found in api_connect_config.json is returned after validation.""" + from aipass.api.apps.handlers.auth.keys import get_api_key + + config_path = tmp_path / "api_connect_config.json" + config_data = { + "config": { + "providers": { + "openrouter": { + "api_key": "sk-or-v1-valid-key-that-is-long-enough" + } + } + } + } + config_path.write_text(json.dumps(config_data), encoding="utf-8") + + mock_api_json_dir.__truediv__ = lambda self, name: tmp_path / name + + result = get_api_key("openrouter") + + assert result == "sk-or-v1-valid-key-that-is-long-enough" + mock_jh.log_operation.assert_called_once() + + @patch("aipass.api.apps.handlers.auth.keys.json_handler") + @patch("aipass.api.apps.handlers.auth.keys.API_JSON_DIR") + def test_key_from_secrets_file(self, mock_api_json_dir, mock_jh, tmp_path): + """Key found in ~/.secrets/aipass/.env when config has no key.""" + from aipass.api.apps.handlers.auth.keys import get_api_key + + # Config file exists but has no key for openrouter + config_path = tmp_path / "api_connect_config.json" + config_data = {"config": {"providers": {}}} + config_path.write_text(json.dumps(config_data), encoding="utf-8") + mock_api_json_dir.__truediv__ = lambda self, name: tmp_path / name + + # Create secrets file + secrets_dir = tmp_path / ".secrets" / "aipass" + secrets_dir.mkdir(parents=True) + env_file = secrets_dir / ".env" + env_file.write_text( + "OPENROUTER_API_KEY=sk-or-v1-secret-key-long-enough-here\n", + encoding="utf-8", + ) + + with patch("aipass.api.apps.handlers.auth.keys.Path") as mock_path_cls: + # Path.home() should return tmp_path so secrets resolve there + mock_path_cls.home.return_value = tmp_path + result = get_api_key("openrouter") + + assert result == "sk-or-v1-secret-key-long-enough-here" + + @patch("aipass.api.apps.handlers.auth.keys.json_handler") + @patch("aipass.api.apps.handlers.auth.keys.API_JSON_DIR") + def test_no_key_found_returns_none(self, mock_api_json_dir, mock_jh, tmp_path): + """Returns None when no key exists in any source.""" + from aipass.api.apps.handlers.auth.keys import get_api_key + + # Config file with no providers + config_path = tmp_path / "api_connect_config.json" + config_data = {"config": {"providers": {}}} + config_path.write_text(json.dumps(config_data), encoding="utf-8") + mock_api_json_dir.__truediv__ = lambda self, name: tmp_path / name + + # No secrets file exists + with patch("aipass.api.apps.handlers.auth.keys.Path") as mock_path_cls: + mock_home = tmp_path / "nonexistent_home" + mock_path_cls.home.return_value = mock_home + result = get_api_key("openrouter") + + assert result is None + + @patch("aipass.api.apps.handlers.auth.keys.json_handler") + @patch("aipass.api.apps.handlers.auth.keys.API_JSON_DIR") + def test_invalid_key_format_returns_none(self, mock_api_json_dir, mock_jh, tmp_path): + """Key exists in config but fails validation (wrong prefix) returns None.""" + from aipass.api.apps.handlers.auth.keys import get_api_key + + config_path = tmp_path / "api_connect_config.json" + config_data = { + "config": { + "providers": { + "openrouter": { + "api_key": "INVALID-PREFIX-key-that-is-long-enough" + } + } + } + } + config_path.write_text(json.dumps(config_data), encoding="utf-8") + mock_api_json_dir.__truediv__ = lambda self, name: tmp_path / name + + # No secrets file fallback + with patch("aipass.api.apps.handlers.auth.keys.Path") as mock_path_cls: + mock_path_cls.home.return_value = tmp_path / "no_home" + result = get_api_key("openrouter") + + assert result is None + + +# ============================================= +# 2. validate_key() tests +# ============================================= + + +class TestValidateKey: + """Tests for validate_key() — key format validation per provider rules.""" + + def test_valid_openrouter_key(self): + """Valid openrouter key with correct prefix and length passes.""" + from aipass.api.apps.handlers.auth.keys import validate_key + + key = "sk-or-v1-abcdefghijklmnopqrst" + assert validate_key(key, "openrouter") is True + + def test_key_too_short(self): + """Key shorter than min_length fails validation.""" + from aipass.api.apps.handlers.auth.keys import validate_key + + key = "sk-or-v1-short" + assert len(key) < 20 + assert validate_key(key, "openrouter") is False + + def test_wrong_prefix(self): + """Key with wrong prefix for provider fails validation.""" + from aipass.api.apps.handlers.auth.keys import validate_key + + key = "sk-wrong-prefix-but-long-enough-to-pass-length" + assert validate_key(key, "openrouter") is False + + def test_empty_key(self): + """Empty string key fails validation.""" + from aipass.api.apps.handlers.auth.keys import validate_key + + assert validate_key("", "openrouter") is False + + def test_none_key(self): + """None key fails validation.""" + from aipass.api.apps.handlers.auth.keys import validate_key + + assert validate_key(None, "openrouter") is False # type: ignore[arg-type] + + def test_generic_provider_no_prefix_required(self): + """Generic provider only checks min_length, no prefix required.""" + from aipass.api.apps.handlers.auth.keys import validate_key + + key = "any-key-that-is-long-enough" + assert validate_key(key, "unknown_provider") is True + + def test_generic_provider_too_short(self): + """Generic provider rejects keys shorter than 10 chars.""" + from aipass.api.apps.handlers.auth.keys import validate_key + + assert validate_key("short", "unknown_provider") is False + + +# ============================================= +# 3. get_response() tests +# ============================================= + + +MODULE = "aipass.api.apps.handlers.openrouter.client" + + +class TestGetResponse: + """Tests for get_response() — main API call orchestrator.""" + + @patch(f"{MODULE}.track_usage") + @patch(f"{MODULE}.extract_response") + @patch(f"{MODULE}.make_api_request") + @patch(f"{MODULE}.get_cached_client") + @patch(f"{MODULE}.get_api_key") + @patch(f"{MODULE}.ensure_caller_config") + @patch(f"{MODULE}.get_caller_info") + def test_successful_call( + self, + mock_caller_info, + mock_ensure, + mock_get_key, + mock_get_client, + mock_make_req, + mock_extract, + mock_track, + ): + """Full successful pipeline: detect caller, get key, make request, extract, track.""" + from aipass.api.apps.handlers.openrouter.client import get_response + + mock_caller_info.return_value = {"caller_name": "test-branch"} + mock_get_key.return_value = "sk-or-v1-test-key-long-enough" + mock_get_client.return_value = MagicMock() + mock_make_req.return_value = MagicMock() + mock_extract.return_value = { + "content": "Hello, world!", + "id": "gen-abc123", + "model": "anthropic/claude-3.5-sonnet", + } + + result = get_response( + "What is Python?", model="anthropic/claude-3.5-sonnet" + ) + + assert result is not None + assert result["content"] == "Hello, world!" + assert result["id"] == "gen-abc123" + mock_track.assert_called_once() + + @patch(f"{MODULE}.get_api_key") + @patch(f"{MODULE}.ensure_caller_config") + @patch(f"{MODULE}.get_caller_info") + def test_no_model_returns_none( + self, mock_caller_info, mock_ensure, mock_get_key + ): + """Missing model parameter returns None without making API call.""" + from aipass.api.apps.handlers.openrouter.client import get_response + + mock_caller_info.return_value = {"caller_name": "test"} + + result = get_response("What is Python?", model=None) + + assert result is None + mock_get_key.assert_not_called() + + @patch(f"{MODULE}.get_api_key") + @patch(f"{MODULE}.ensure_caller_config") + @patch(f"{MODULE}.get_caller_info") + def test_no_api_key_returns_none( + self, mock_caller_info, mock_ensure, mock_get_key + ): + """No API key available returns None.""" + from aipass.api.apps.handlers.openrouter.client import get_response + + mock_caller_info.return_value = {"caller_name": "test"} + mock_get_key.return_value = None + + result = get_response( + "What is Python?", model="anthropic/claude-3.5-sonnet" + ) + + assert result is None + + +# ============================================= +# 4. extract_response() tests +# ============================================= + + +class TestExtractResponse: + """Tests for extract_response() — response content extraction from OpenAI objects.""" + + def test_valid_response(self): + """Extracts content, id, and model from a well-formed response.""" + from aipass.api.apps.handlers.openrouter.client import extract_response + + choice = MagicMock() + choice.message.content = "The answer is 42." + choice.finish_reason = "stop" + + response = MagicMock() + response.choices = [choice] + response.id = "gen-xyz789" + response.model = "anthropic/claude-3.5-sonnet" + + result = extract_response(response) + + assert result is not None + assert result["content"] == "The answer is 42." + assert result["id"] == "gen-xyz789" + assert result["model"] == "anthropic/claude-3.5-sonnet" + + def test_none_response(self): + """None response returns None.""" + from aipass.api.apps.handlers.openrouter.client import extract_response + + assert extract_response(None) is None + + def test_response_no_choices(self): + """Response with empty choices list returns None.""" + from aipass.api.apps.handlers.openrouter.client import extract_response + + response = MagicMock() + response.choices = [] + + assert extract_response(response) is None + + def test_response_no_content(self): + """Response with choice but no content returns None.""" + from aipass.api.apps.handlers.openrouter.client import extract_response + + choice = MagicMock() + choice.message.content = None + + response = MagicMock() + response.choices = [choice] + response.id = "gen-empty" + response.model = "test/model" + + assert extract_response(response) is None + + def test_response_missing_message(self): + """Response choice without message attribute returns None.""" + from aipass.api.apps.handlers.openrouter.client import extract_response + + choice = MagicMock(spec=[]) # No attributes at all + response = MagicMock() + response.choices = [choice] + + assert extract_response(response) is None