Files
AIPass/src/aipass/api/tests/test_provision.py
T
AIPassand@devpulse e2c868084a feat(system): chore(lint): ruff auto-fix sweep — 303 errors across 178 files (F401 unused imports + F541 f-string placeholders + F811 redefined); restored report_error re-export + added logger call in errors.py (#333)
* feat(system): fix(windows-ci): combine pip bootstrap fix + venv activation in Verify step

Combines both fixes needed to get windows-test.yml actually passing:

1. setup.sh: stop swallowing ensurepip errors (quiet + 2>/dev/null + || true was
   hiding real failures — pip was silently not installed). Add get-pip.py fallback
   and hard pip verification.

2. windows-test.yml: add 'source .venv/Scripts/activate' to Verify step. Each CI
   step gets a fresh shell — setup.sh's venv activation doesn't carry over, so
   drone wasn't on PATH in the subsequent step.

Together, these should take Windows CI from the 'silent failure every run since
creation' state to actually green. Supersedes PRs #330 and #331 which had the
fixes on separate branches (neither green alone).

Co-Authored-By: @devpulse <devpulse@aipass>

* feat(system): chore(lint): ruff auto-fix sweep — 303 errors across 178 files (F401 unused imports + F541 f-string placeholders + F811 redefined); restored report_error re-export + added logger call in errors.py

Co-Authored-By: @devpulse <devpulse@aipass>

---------

Co-authored-by: @devpulse <devpulse@aipass>
2026-04-16 12:58:53 -07:00

321 lines
10 KiB
Python

# =================== AIPass ====================
# Name: test_provision.py
# Description: Tests for caller auto-provisioning handler
# Version: 1.0.0
# Created: 2026-03-20
# Modified: 2026-03-20
# =============================================
"""
Tests for provision.py — caller auto-provisioning.
Tests:
- create_caller_config() creates 3 JSON files with correct defaults
- ensure_caller_config() provisions on first call, returns existing on second
- Idempotency: second call doesn't overwrite existing config
- Config defaults match expected values
"""
import json
from pathlib import Path
from unittest.mock import patch
from aipass.api.apps.handlers.openrouter.provision import (
create_caller_config,
ensure_caller_config,
get_default_caller_config,
get_default_caller_data,
get_default_caller_log,
provision_json_folder,
read_json,
write_json,
)
# =============================================
# create_caller_config tests
# =============================================
def test_create_caller_config_creates_three_files(tmp_path: Path):
"""create_caller_config() should create config, data, and log JSON files."""
json_folder = tmp_path / "test_json"
result = create_caller_config("test_caller", json_folder)
assert result != {}
assert (json_folder / "openrouter_config.json").exists()
assert (json_folder / "openrouter_data.json").exists()
assert (json_folder / "openrouter_log.json").exists()
def test_create_caller_config_defaults(tmp_path: Path):
"""Config defaults should match: ai_temperature=0.7, ai_max_tokens=4000, enabled=True."""
json_folder = tmp_path / "test_json"
create_caller_config("test_caller", json_folder)
config = read_json(json_folder / "openrouter_config.json")
assert config is not None
assert config["config"]["ai_temperature"] == 0.7
assert config["config"]["ai_max_tokens"] == 4000
assert config["config"]["enabled"] is True
assert config["config"]["ai_model"] == ""
assert config["module_name"] == "openrouter"
def test_create_caller_config_data_defaults(tmp_path: Path):
"""Data file should have zeroed counters."""
json_folder = tmp_path / "test_json"
create_caller_config("test_caller", json_folder)
data = read_json(json_folder / "openrouter_data.json")
assert data is not None
assert data["data"]["total_requests"] == 0
assert data["data"]["successful_requests"] == 0
assert data["data"]["failed_requests"] == 0
assert data["data"]["models_used"] == {}
assert data["data"]["last_request"] is None
def test_create_caller_config_log_defaults(tmp_path: Path):
"""Log file should have empty logs list."""
json_folder = tmp_path / "test_json"
create_caller_config("test_caller", json_folder)
log = read_json(json_folder / "openrouter_log.json")
assert log is not None
assert log["logs"] == []
assert log["module_name"] == "openrouter"
def test_create_caller_config_creates_folder(tmp_path: Path):
"""Should create the json_folder if it doesn't exist."""
json_folder = tmp_path / "nested" / "deep" / "test_json"
assert not json_folder.exists()
create_caller_config("test_caller", json_folder)
assert json_folder.exists()
# =============================================
# provision_json_folder tests
# =============================================
def test_provision_json_folder_creates(tmp_path: Path):
"""Should create folder when it doesn't exist."""
folder = tmp_path / "new_folder"
assert not folder.exists()
result = provision_json_folder(folder)
assert result is True
assert folder.exists()
def test_provision_json_folder_existing(tmp_path: Path):
"""Should return True for already-existing folder."""
folder = tmp_path / "existing"
folder.mkdir()
result = provision_json_folder(folder)
assert result is True
# =============================================
# ensure_caller_config tests (mocked stack detection)
# =============================================
@patch("aipass.api.apps.handlers.openrouter.provision.detect_caller_from_stack")
def test_ensure_caller_config_provisions_new(mock_detect, tmp_path: Path):
"""ensure_caller_config() should create config when none exists."""
json_folder = tmp_path / "caller_json"
mock_detect.return_value = ("test_caller", json_folder)
result = ensure_caller_config("test_caller")
assert result != {}
assert (json_folder / "openrouter_config.json").exists()
assert (json_folder / "openrouter_data.json").exists()
assert (json_folder / "openrouter_log.json").exists()
@patch("aipass.api.apps.handlers.openrouter.provision.detect_caller_from_stack")
def test_ensure_caller_config_returns_existing(mock_detect, tmp_path: Path):
"""Second call should return existing config without overwriting."""
json_folder = tmp_path / "caller_json"
mock_detect.return_value = ("test_caller", json_folder)
# First call — creates config
first_result = ensure_caller_config("test_caller")
assert first_result != {}
# Read the created config and modify it to detect overwrites
config_path = json_folder / "openrouter_config.json"
config = read_json(config_path)
assert config is not None
config["config"]["ai_model"] = "test/modified-model"
with open(config_path, "w", encoding="utf-8") as f:
json.dump(config, f, indent=2)
# Second call — should return existing (modified) config, not overwrite
second_result = ensure_caller_config("test_caller")
assert second_result["config"]["ai_model"] == "test/modified-model"
@patch("aipass.api.apps.handlers.openrouter.provision.detect_caller_from_stack")
def test_ensure_caller_config_no_folder_detected(mock_detect):
"""Should return empty dict when stack detection fails to find json_folder."""
mock_detect.return_value = (None, None)
result = ensure_caller_config()
assert result == {}
# =============================================
# get_default_caller_config tests
# =============================================
def test_get_default_caller_config_structure():
"""Default config should have expected structure."""
config = get_default_caller_config()
assert "module_name" in config
assert "timestamp" in config
assert "config" in config
assert isinstance(config["config"], dict)
assert set(config["config"].keys()) == {"ai_model", "ai_temperature", "ai_max_tokens", "enabled"}
# =============================================
# write_json tests
# =============================================
def test_write_json_creates_file(tmp_path: Path):
"""write_json writes valid JSON and returns True."""
target = tmp_path / "output.json"
data = {"key": "value", "number": 42}
result = write_json(target, data)
assert result is True
assert target.exists()
with open(target, "r", encoding="utf-8") as f:
loaded = json.load(f)
assert loaded == data
def test_write_json_creates_parent_dirs(tmp_path: Path):
"""write_json creates parent directories when they don't exist."""
target = tmp_path / "nested" / "deep" / "data.json"
data = {"created": True}
result = write_json(target, data)
assert result is True
assert target.exists()
with open(target, "r", encoding="utf-8") as f:
loaded = json.load(f)
assert loaded == data
def test_write_json_uses_indent_and_ensure_ascii(tmp_path: Path):
"""write_json formats with indent=2 and preserves unicode."""
target = tmp_path / "unicode.json"
data = {"name": "caf\u00e9"}
write_json(target, data)
raw = target.read_text(encoding="utf-8")
# indent=2 means keys are indented
assert ' "name"' in raw
# ensure_ascii=False means unicode is preserved literally
assert "caf\u00e9" in raw
def test_write_json_overwrites_existing(tmp_path: Path):
"""write_json overwrites an existing file."""
target = tmp_path / "overwrite.json"
write_json(target, {"version": 1})
write_json(target, {"version": 2})
with open(target, "r", encoding="utf-8") as f:
loaded = json.load(f)
assert loaded["version"] == 2
def test_write_json_returns_false_on_error(tmp_path: Path):
"""write_json returns False when writing fails."""
# Use a path where the parent is a file, not a directory
blocker = tmp_path / "blocker"
blocker.write_text("not a dir", encoding="utf-8")
target = blocker / "sub" / "data.json"
result = write_json(target, {"key": "value"})
assert result is False
# =============================================
# get_default_caller_data tests
# =============================================
def test_get_default_caller_data_structure():
"""get_default_caller_data returns dict with expected keys and zeroed counters."""
data = get_default_caller_data()
assert data["module_name"] == "openrouter"
assert "timestamp" in data
assert isinstance(data["data"], dict)
counters = data["data"]
assert counters["total_requests"] == 0
assert counters["successful_requests"] == 0
assert counters["failed_requests"] == 0
assert counters["models_used"] == {}
assert counters["last_request"] is None
def test_get_default_caller_data_timestamp_is_iso():
"""Timestamp should be a valid ISO-format string."""
from datetime import datetime
data = get_default_caller_data()
# Should not raise
datetime.fromisoformat(data["timestamp"])
# =============================================
# get_default_caller_log tests
# =============================================
def test_get_default_caller_log_structure():
"""get_default_caller_log returns dict with module_name, timestamp, and empty logs."""
log = get_default_caller_log()
assert log["module_name"] == "openrouter"
assert "timestamp" in log
assert log["logs"] == []
assert isinstance(log["logs"], list)
def test_get_default_caller_log_timestamp_is_iso():
"""Timestamp should be a valid ISO-format string."""
from datetime import datetime
log = get_default_caller_log()
# Should not raise
datetime.fromisoformat(log["timestamp"])