Files
roboco/tests/unit/services/test_playbook_unindex.py
T
Renn F 4d4bf084c5 [chore] clear all 64 pre-existing mypy errors in tests/ (no type:ignore)
Convention: no type:ignore/noqa, and pre-existing violations still
violate. The make-quality gate runs 'mypy roboco/ tests/', but the
prior commits' gates only ran mypy on production files, masking 64
type errors across 15 test files (method-assign, unused-ignore,
no-untyped-def, attr-defined, union-attr, has-type, index, misc).

Fixed without any type:ignore:
- method-assign (svc.session.X = / svc.method = AsyncMock()): hold a
  local 'session: MagicMock'/'AsyncMock' and assert on it, or stub via
  object.__setattr__ / monkeypatch / a typed '_bind' helper returning
  Any, or alias 'cc: Any = c' (the pattern the file already used).
- unused 'type: ignore[assignment]' (real code was method-assign):
  removed; replaced with the no-suppression patterns above.
- 'Callable[...] has no attribute assert_*': keep a typed local ref to
  the AsyncMock and assert on the local, not the method-typed attr.
- no-untyped-def: annotate helper params (Any / pytest.MonkeyPatch).
- attr-defined / index / union-attr: type the helper as Any, narrow
  with an 'is not None' assert, or add the missing attr to a fake.
- has-type / return-value: fix the declared return type to the tuple
  the function actually returns.
- PLC0415 inline imports: hoisted to top-level.

test_pr_gate_notifies_pm._stub_gate_path converted fully to the
'cc: Any = c' alias (it already used it for one attr) so its five
'# type: ignore[method-assign]' suppressions are gone.

mypy tests/: 64 errors -> 0 (538 files). ruff check tests/: clean.
All 84 tests in the touched files pass.
2026-06-28 16:47:09 +02:00

122 lines
4.1 KiB
Python

"""PlaybookService.reject must flush the status change WITHOUT de-indexing
inline, and ``unindex_playbook`` is the separate post-commit de-index step.
A rejected/archived playbook that was previously approved must stop surfacing in
agent briefings, so a reject drops its chunks + tracking row. Originally (F011)
``reject`` called an ``_unindex_playbook`` helper inline. F057 split that out:
the RAG index write runs through its own auto-committing pool connection, so
de-indexing inline (before the caller commits the status) would drop a playbook
from the corpus even if the status transaction rolled back — a divergence. So
``reject`` now flushes the status ONLY; ``unindex_playbook`` is a separate
public step the caller runs AFTER committing. Both helpers stay gated on
``org_memory_enabled`` (inert when the loop is off) and best-effort.
"""
from __future__ import annotations
from typing import Any
from unittest.mock import AsyncMock, MagicMock, patch
from uuid import uuid4
import pytest
from roboco.config import settings
from roboco.models.base import PlaybookStatus
from roboco.services.playbook import PlaybookService
def _mock_playbook(
playbook_id: Any, *, status: str = PlaybookStatus.APPROVED.value
) -> MagicMock:
pb = MagicMock()
pb.id = playbook_id
pb.status = status
pb.title = "Retry a flaky pg test"
pb.problem = "..."
pb.procedure = "..."
pb.tags = ["backend"]
pb.team = "backend"
pb.scope = "org"
return pb
def _session_with(pb: Any) -> MagicMock:
session = MagicMock()
result = MagicMock()
result.scalar_one_or_none.return_value = pb
session.execute = AsyncMock(return_value=result)
session.flush = AsyncMock()
return session
@pytest.mark.asyncio
async def test_reject_archives_but_does_not_deindex_inline(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""``reject`` flushes the ARCHIVED status but must NOT touch the RAG index —
the de-index is a separate post-commit step (F057 ordering)."""
monkeypatch.setattr(settings, "org_memory_enabled", True)
playbook_id = uuid4()
pb = _mock_playbook(playbook_id, status=PlaybookStatus.APPROVED.value)
svc = PlaybookService(_session_with(pb))
optimal = MagicMock()
optimal.unindex_playbook = AsyncMock(return_value=None)
with (
patch(
"roboco.services.optimal.get_optimal_service",
AsyncMock(return_value=optimal),
),
):
out = await svc.reject(playbook_id, approver_id=uuid4(), reason="stale")
assert out.status == PlaybookStatus.ARCHIVED.value
optimal.unindex_playbook.assert_not_awaited()
@pytest.mark.asyncio
async def test_unindex_playbook_deindexes_approved_playbook(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""``unindex_playbook`` (the post-commit step) removes an approved playbook
from the index."""
monkeypatch.setattr(settings, "org_memory_enabled", True)
playbook_id = uuid4()
pb = _mock_playbook(playbook_id, status=PlaybookStatus.APPROVED.value)
svc = PlaybookService(_session_with(pb))
optimal = MagicMock()
optimal.unindex_playbook = AsyncMock(return_value=None)
with (
patch(
"roboco.services.optimal.get_optimal_service",
AsyncMock(return_value=optimal),
),
):
await svc.unindex_playbook(pb)
optimal.unindex_playbook.assert_awaited_once_with(str(playbook_id))
@pytest.mark.asyncio
async def test_unindex_playbook_skips_when_org_memory_disabled(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""org_memory_enabled=False ⇒ the index is inert; ``unindex_playbook`` must
not touch it."""
monkeypatch.setattr(settings, "org_memory_enabled", False)
playbook_id = uuid4()
pb = _mock_playbook(playbook_id)
svc = PlaybookService(_session_with(pb))
optimal = MagicMock()
optimal.unindex_playbook = AsyncMock(return_value=None)
with (
patch(
"roboco.services.optimal.get_optimal_service",
AsyncMock(return_value=optimal),
),
):
await svc.unindex_playbook(pb)
optimal.unindex_playbook.assert_not_awaited()