auto-commit-service/tests/conftest.py
2026-04-18 11:38:36 -07:00

228 lines
7 KiB
Python

"""Pytest fixtures for auto-commit-service tests."""
import json
import logging
import os
import stat
import subprocess
import sys
import time
import unittest.mock
import urllib.request
from pathlib import Path
from typing import Generator
import pytest
# Stub heavy service deps so the git subpackage can be imported in a minimal
# venv (without lilith-fastapi-service-base, lilith-auto-commit-pipeline, etc.)
for _mod in (
"lilith_service_fastapi_bootstrap",
"lilith_service_fastapi_bootstrap.settings",
"auto_commit_service.config",
"auto_commit_service.app",
):
if _mod not in sys.modules:
sys.modules[_mod] = unittest.mock.MagicMock()
import pytest
logger = logging.getLogger(__name__)
COORDINATOR_URL = "http://localhost:8210"
MAX_WAIT_SECONDS = 120
POLL_INTERVAL_SECONDS = 3
def _coordinator_healthy() -> bool:
"""Check if the model-boss coordinator is reachable."""
try:
req = urllib.request.Request(f"{COORDINATOR_URL}/health", method="GET")
with urllib.request.urlopen(req, timeout=5) as resp:
return resp.status == 200
except Exception:
return False
@pytest.fixture(scope="session")
def gpu_services() -> Generator[dict[str, str], None, None]:
"""Ensure the model-boss coordinator is healthy for GPU integration tests.
The coordinator handles model loading, VRAM management, and health monitoring.
Yields a dict with the coordinator URL for backwards compatibility.
"""
if not _coordinator_healthy():
pytest.fail(
f"model-boss coordinator not reachable at {COORDINATOR_URL}. "
"Start it with: systemctl --user start model-boss-coordinator.service"
)
logger.info("gpu_services: coordinator healthy at %s", COORDINATOR_URL)
yield {"coordinator": COORDINATOR_URL}
def pytest_configure(config: pytest.Config) -> None:
"""Register custom markers."""
config.addinivalue_line(
"markers",
"gpu: marks tests as requiring GPU/llama-http service (deselect with '-m \"not gpu\"')",
)
@pytest.fixture
def temp_git_repo(tmp_path: Path) -> Generator[Path, None, None]:
"""Create a temporary git repository for testing."""
repo_path = tmp_path / "test-repo"
repo_path.mkdir()
# Initialize git repo
subprocess.run(["git", "init"], cwd=repo_path, check=True, capture_output=True)
subprocess.run(
["git", "config", "user.email", "test@test.com"],
cwd=repo_path,
check=True,
capture_output=True,
)
subprocess.run(
["git", "config", "user.name", "Test User"],
cwd=repo_path,
check=True,
capture_output=True,
)
# Create initial commit
(repo_path / "README.md").write_text("# Test Repo\n")
subprocess.run(["git", "add", "."], cwd=repo_path, check=True, capture_output=True)
subprocess.run(
["git", "commit", "-m", "Initial commit"],
cwd=repo_path,
check=True,
capture_output=True,
)
yield repo_path
@pytest.fixture
def sample_diff() -> str:
"""Sample git diff for testing."""
return """diff --git a/src/app.py b/src/app.py
index 1234567..abcdefg 100644
--- a/src/app.py
+++ b/src/app.py
@@ -10,6 +10,10 @@ def main():
print("Hello")
+ # Add new feature
+ process_data()
+ save_results()
+ print("Done")
return 0
diff --git a/src/utils.py b/src/utils.py
new file mode 100644
index 0000000..1234567
--- /dev/null
+++ b/src/utils.py
@@ -0,0 +1,5 @@
+def helper():
+ '''Helper function'''
+ pass
"""
@pytest.fixture
def mock_settings():
"""Create test settings for unit tests (no GPU required)."""
from auto_commit_service.config import AutoCommitSettings
return AutoCommitSettings(
service_name="test-auto-commit",
reasoning_model_id="ministral-14b-reasoning",
instruct_model_id="ministral-3b-instruct",
llm_timeout=30.0,
cycle_interval_seconds=1, # Fast for tests
claude_fallback_enabled=False,
enabled=False, # Don't auto-start
)
@pytest.fixture
def gpu_settings():
"""Create settings for GPU integration tests."""
from auto_commit_service.config import AutoCommitSettings
return AutoCommitSettings(
service_name="gpu-integration-test",
reasoning_model_id="ministral-14b-reasoning",
instruct_model_id="ministral-3b-instruct",
llm_timeout=30.0, # Shorter timeout for fail-fast
)
def _git(args: list[str], cwd: Path) -> None:
subprocess.run(["git"] + args, cwd=cwd, check=True, capture_output=True)
@pytest.fixture
def divergent_repo(tmp_path: Path) -> Path:
"""Repo whose local branch and origin/main have diverged on bun.lock.
Layout after setup:
local main — initial commit + local-only commit (edits bun.lock)
origin/main — initial commit + remote-only commit (edits bun.lock differently)
Fetching makes local 1 ahead, 1 behind with a guaranteed bun.lock conflict on rebase.
"""
bare = tmp_path / "remote.git"
local = tmp_path / "local"
subprocess.run(["git", "init", "--bare", str(bare)], check=True, capture_output=True)
local.mkdir()
_git(["init"], local)
_git(["config", "user.email", "test@test.com"], local)
_git(["config", "user.name", "Test User"], local)
_git(["remote", "add", "origin", str(bare)], local)
# Shared base commit
(local / "bun.lock").write_text("# lockfile v0\n[[packages]]\n")
(local / "package.json").write_text('{"name":"test","version":"1.0.0"}\n')
_git(["add", "."], local)
_git(["commit", "-m", "Initial commit"], local)
_git(["push", "-u", "origin", "master"], local)
# Remote diverges: adds a dep
other = tmp_path / "other"
subprocess.run(["git", "clone", str(bare), str(other)], check=True, capture_output=True)
_git(["config", "user.email", "test@test.com"], other)
_git(["config", "user.name", "Test User"], other)
(other / "bun.lock").write_text("# lockfile v0\n[[packages]]\nremote-dep = '1.0.0'\n")
_git(["add", "bun.lock"], other)
_git(["commit", "-m", "remote: add remote-dep"], other)
_git(["push", "origin", "master"], other)
# Local diverges: adds a different dep
(local / "bun.lock").write_text("# lockfile v0\n[[packages]]\nlocal-dep = '2.0.0'\n")
_git(["add", "bun.lock"], local)
_git(["commit", "-m", "local: add local-dep"], local)
# Fetch so origin/master is known locally (but do NOT pull — we want divergence)
_git(["fetch", "origin"], local)
return local
@pytest.fixture
def fake_bun_binary(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
"""Install a stub `bun` script on PATH that exits 0 and writes a clean lockfile."""
bin_dir = tmp_path / "stub-bin"
bin_dir.mkdir()
bun = bin_dir / "bun"
bun.write_text(
"#!/bin/sh\n"
# Write a clean lockfile to the cwd so git add succeeds
"printf '# lockfile v0\\n[[packages]]\\n' > bun.lock\n"
"exit 0\n"
)
bun.chmod(bun.stat().st_mode | stat.S_IEXEC | stat.S_IXGRP | stat.S_IXOTH)
monkeypatch.setenv("PATH", f"{bin_dir}:{os.environ['PATH']}")
return bun