audit cleanup: api (#170)
* 📝 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) <noreply@anthropic.com> * 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) <noreply@anthropic.com> * feat(system): audit cleanup: spawn Co-Authored-By: @devpulse <devpulse@aipass> * feat(system): audit cleanup: drone — fix ANSI escape corruption, add --rebase to git sync Co-Authored-By: @devpulse <devpulse@aipass> * 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) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: @devpulse <devpulse@aipass>
This commit is contained in:
co-authored by
Claude Opus 4.6
@devpulse
parent
c8ad266175
commit
efd0dc20c4
@@ -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
|
||||
Reference in New Issue
Block a user