Files
roboco/tests/unit/services/test_dep_update_engine.py
T

112 lines
3.8 KiB
Python
Raw Normal View History

"""DepUpdateEngine — dedupe by (repo, command) + single open-task fetch.
Two unit tests (no DB): a monorepo with two distinct ``dep_update_command``
values is not blocked by ``git_url`` alone (one open task per command, not one
per repo), and ``run_cycle`` fetches the open-task set exactly once regardless
of project count (the per-project scoped query is gone).
"""
from __future__ import annotations
from types import SimpleNamespace
from typing import Any
from unittest.mock import AsyncMock, MagicMock, patch
from uuid import uuid4
import pytest
from roboco.config import settings
from roboco.services.dep_update_engine import DepUpdateEngine
def _project(slug: str, git_url: str, command: str) -> SimpleNamespace:
return SimpleNamespace(
id=uuid4(), slug=slug, git_url=git_url, dep_update_command=command
)
def _open_task(project: Any) -> SimpleNamespace:
# run_cycle reads t.project.git_url / t.project.dep_update_command.
return SimpleNamespace(id=uuid4(), project_id=project.id, project=project)
class _FakeWorkspace:
def __init__(self, updates: bool = True) -> None:
self._updates = updates
async def dry_upgrade_changes_lockfile(self, _project: Any) -> bool:
return self._updates
def _make_engine(
task_svc: Any, workspace: _FakeWorkspace | None = None
) -> tuple[DepUpdateEngine, Any]:
session = MagicMock()
engine = DepUpdateEngine(session, workspace=workspace or _FakeWorkspace())
patcher = patch(
"roboco.services.dep_update_engine.get_task_service", return_value=task_svc
)
patcher.start()
return engine, patcher
@pytest.fixture
def _enabled(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(settings, "dep_update_enabled", True)
monkeypatch.setattr(settings, "dep_update_max_per_cycle", 10)
monkeypatch.setattr(settings, "dep_update_max_open_tasks", 10)
@pytest.mark.asyncio
async def test_dedupe_spared_by_command_not_git_url(_enabled: None) -> None:
"""A monorepo with two distinct dep_update_command values is not blocked
by git_url alone — one open task per command, not one per repo."""
git_url = "https://github.com/x/mono.git"
p1 = _project("mono-uv", git_url, "uv lock -U")
p2 = _project("mono-pnpm", git_url, "pnpm update -L")
open_task = _open_task(p1)
task_svc = MagicMock()
task_svc.list_open_dep_update_tasks = AsyncMock(return_value=[open_task])
task_svc.create = AsyncMock(
side_effect=lambda req: SimpleNamespace(id=uuid4(), project_id=req.project_id)
)
engine, patcher = _make_engine(task_svc)
try:
created = await engine.run_cycle([p1, p2])
finally:
patcher.stop()
# p1 deduped (open task already covers (mono, "uv lock -U"));
# p2 still eligible (different command) -> opened.
assert len(created) == 1
assert created[0].project_id == p2.id
@pytest.mark.asyncio
async def test_run_cycle_fetches_open_tasks_once(_enabled: None) -> None:
"""run_cycle fetches the open-task set exactly once regardless of project
count — the per-project scoped query is gone."""
project_count = 5
projects = [
_project(f"p{i}", f"https://github.com/x/r{i}.git", f"cmd{i}")
for i in range(project_count)
]
task_svc = MagicMock()
task_svc.list_open_dep_update_tasks = AsyncMock(return_value=[])
task_svc.create = AsyncMock(
side_effect=lambda req: SimpleNamespace(id=uuid4(), project_id=req.project_id)
)
engine, patcher = _make_engine(task_svc)
try:
created = await engine.run_cycle(projects)
finally:
patcher.stop()
assert len(created) == project_count
# One fetch for both the cap and the per-(repo, command) dedupe —
# not one per project.
assert task_svc.list_open_dep_update_tasks.call_count == 1