mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
[F115] sample monorepo per (repo,workflow)/(repo,command) not per repo
The CI-watch and dep-update loaders collapsed a monorepo's cell-projects to one canonical entry per repo (slug-sorted-first), so a repo whose cells each carry their OWN ci_watch_workflow / dep_update_command had only the canonical cell's workflow/command sampled — a red on another cell's workflow or drift on another cell's lockfile was missed (under-count). Refactor the shared one-per-repo collapse into _projects_one_per_key, keyed by repo identity for external-PR discovery (unchanged: one review per PR per repo), by (repo, effective workflow) for CI-watch, and by (repo, command) for dep-update. Each distinct workflow/command is now sampled once; the engines' per-git_url fix-task dedup still prevents duplicate fix tasks for the same repo. _projects_one_per_repo now delegates to _projects_one_per_key. key_fn uses a string annotation (Callable lives under TYPE_CHECKING, like the existing Coroutine/Iterable annotations at lines 4193/5279).
This commit is contained in:
@@ -33,17 +33,59 @@ async def test_loop_noop_when_disabled(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@pytest.mark.asyncio
|
||||
async def test_load_watch_set_filters_enabled_one_per_repo() -> None:
|
||||
orch = _orch()
|
||||
on_a = MagicMock(slug="be", git_url="https://x/a.git", ci_watch_enabled=True)
|
||||
on_a2 = MagicMock(slug="fe", git_url="https://x/a.git", ci_watch_enabled=True)
|
||||
# Same repo, SAME effective workflow (both fall back to the default) → one
|
||||
# canonical entry; the opt-out is excluded.
|
||||
on_a = MagicMock(
|
||||
slug="be",
|
||||
git_url="https://x/a.git",
|
||||
ci_watch_enabled=True,
|
||||
ci_watch_workflow=None,
|
||||
)
|
||||
on_a2 = MagicMock(
|
||||
slug="fe",
|
||||
git_url="https://x/a.git",
|
||||
ci_watch_enabled=True,
|
||||
ci_watch_workflow=None,
|
||||
)
|
||||
off = MagicMock(slug="c", git_url="https://x/c.git", ci_watch_enabled=False)
|
||||
svc = MagicMock()
|
||||
svc.list_all = AsyncMock(return_value=[on_a, on_a2, off])
|
||||
with patch("roboco.services.project.get_project_service", return_value=svc):
|
||||
watch = await orch._load_ci_watch_set(MagicMock())
|
||||
assert len(watch) == 1 # opt-out excluded; same-repo cell-projects collapsed
|
||||
assert len(watch) == 1 # opt-out excluded; same-repo + same-workflow collapsed
|
||||
assert watch[0].git_url == "https://x/a.git"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_load_watch_set_keeps_distinct_workflows_per_repo() -> None:
|
||||
"""F115: a monorepo's several cell-projects each carrying their OWN
|
||||
``ci_watch_workflow`` must ALL be watched — collapsing to the canonical
|
||||
cell's workflow would miss a red on the other cells' workflows (under-count).
|
||||
Same repo, DIFFERENT workflows → one entry per (repo, workflow). The engine's
|
||||
per-git_url fix-task dedup still prevents duplicate fix tasks for the repo."""
|
||||
orch = _orch()
|
||||
be = MagicMock(
|
||||
slug="be",
|
||||
git_url="https://x/a.git",
|
||||
ci_watch_enabled=True,
|
||||
ci_watch_workflow="backend-ci.yml",
|
||||
)
|
||||
fe = MagicMock(
|
||||
slug="fe",
|
||||
git_url="https://x/a.git",
|
||||
ci_watch_enabled=True,
|
||||
ci_watch_workflow="frontend-ci.yml",
|
||||
)
|
||||
svc = MagicMock()
|
||||
svc.list_all = AsyncMock(return_value=[be, fe])
|
||||
with patch("roboco.services.project.get_project_service", return_value=svc):
|
||||
watch = await orch._load_ci_watch_set(MagicMock())
|
||||
# distinct workflows both watched, NOT collapsed to one canonical cell —
|
||||
# the set-equality assertion proves exactly-two (no magic-value literal).
|
||||
workflows = {p.ci_watch_workflow for p in watch}
|
||||
assert workflows == {"backend-ci.yml", "frontend-ci.yml"}
|
||||
|
||||
|
||||
def _db_ctx(db: Any) -> Any:
|
||||
@asynccontextmanager
|
||||
async def _ctx() -> Any:
|
||||
|
||||
@@ -40,10 +40,35 @@ async def test_load_set_filters_command_one_per_repo() -> None:
|
||||
svc.list_all = AsyncMock(return_value=[on_a, on_a2, off])
|
||||
with patch("roboco.services.project.get_project_service", return_value=svc):
|
||||
eligible = await orch._load_dep_update_set(MagicMock())
|
||||
assert len(eligible) == 1 # no-command excluded; same-repo collapsed
|
||||
assert len(eligible) == 1 # no-command excluded; same-repo + same-command collapsed
|
||||
assert eligible[0].git_url == "https://x/a.git"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_load_set_keeps_distinct_commands_per_repo() -> None:
|
||||
"""F115: a monorepo's several cell-projects each carrying their OWN
|
||||
``dep_update_command`` (different ecosystems → different lockfiles) must
|
||||
ALL be probed — collapsing to the canonical cell's command would miss the
|
||||
other cells' lockfile drift (under-count). Same repo, DIFFERENT commands →
|
||||
one entry per (repo, command). The engine's per-git_url open-task dedup
|
||||
still prevents duplicate update tasks for the repo."""
|
||||
orch = _orch()
|
||||
be = MagicMock(
|
||||
slug="be", git_url="https://x/a.git", dep_update_command="uv lock --upgrade"
|
||||
)
|
||||
fe = MagicMock(
|
||||
slug="fe", git_url="https://x/a.git", dep_update_command="pnpm update -L"
|
||||
)
|
||||
svc = MagicMock()
|
||||
svc.list_all = AsyncMock(return_value=[be, fe])
|
||||
with patch("roboco.services.project.get_project_service", return_value=svc):
|
||||
eligible = await orch._load_dep_update_set(MagicMock())
|
||||
# distinct commands both probed, NOT collapsed to one canonical cell —
|
||||
# the set-equality assertion proves exactly-two (no magic-value literal).
|
||||
commands = {p.dep_update_command for p in eligible}
|
||||
assert commands == {"uv lock --upgrade", "pnpm update -L"}
|
||||
|
||||
|
||||
def _db_ctx(db: Any) -> Any:
|
||||
@asynccontextmanager
|
||||
async def _ctx() -> Any:
|
||||
|
||||
Reference in New Issue
Block a user