fix(tests): resolve pyright strict errors in forge test helpers
lint / lint (push) Successful in 2m18s
test / unit (pull_request) Successful in 1m1s
test / integration (pull_request) Successful in 22s
test / coverage (pull_request) Successful in 1m19s

CI runs `pyright .` over the whole repo including tests; the earlier
run only checked the source paths. The test helpers used `**over`
dict-splat into typed constructors, which pyright strict rejects.

- forge_state: build a typed ForgeState base and dataclasses.replace(**over)
- provenance: explicit typed keyword params instead of a **over dict
- resume: _launch_kwargs returns dict[str, Any] (copy call_args.kwargs)
- forge_base: assert PermissionError in __mro__ (avoids always-true issubclass)
- client: annotate _resp body param; type: ignore the mock __enter__ lambda

pyright . now 0 errors; 47 tests still pass; pylint 9.97/10.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WL77TgFxKbs3cidGMG9dz7
This commit is contained in:
2026-07-01 07:58:55 -04:00
parent a229a22d54
commit f211ece6bf
5 changed files with 45 additions and 31 deletions
+3 -2
View File
@@ -8,6 +8,7 @@ core (`assume_yes` + `headless_prompt_text`) as `start --headless`.
from __future__ import annotations from __future__ import annotations
import unittest import unittest
from typing import Any
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
import bot_bottle.cli.resume as resume_mod import bot_bottle.cli.resume as resume_mod
@@ -40,9 +41,9 @@ class ResumeHeadlessTest(unittest.TestCase):
).start() ).start()
self.addCleanup(patch.stopall) self.addCleanup(patch.stopall)
def _launch_kwargs(self) -> dict: def _launch_kwargs(self) -> dict[str, Any]:
self._launch.assert_called_once() self._launch.assert_called_once()
return self._launch.call_args.kwargs return dict(self._launch.call_args.kwargs)
def test_headless_passes_assume_yes_and_prompt(self): def test_headless_passes_assume_yes_and_prompt(self):
rc = resume_mod.cmd_resume( rc = resume_mod.cmd_resume(
+1 -1
View File
@@ -88,7 +88,7 @@ class TestScopedForgeWrites(unittest.TestCase):
def test_scope_error_is_permission_error(self): def test_scope_error_is_permission_error(self):
# Sidecars can catch the stdlib base type. # Sidecars can catch the stdlib base type.
self.assertTrue(issubclass(ForgeScopeError, PermissionError)) self.assertIn(PermissionError, ForgeScopeError.__mro__)
if __name__ == "__main__": if __name__ == "__main__":
+2 -2
View File
@@ -20,11 +20,11 @@ def _client() -> GiteaClient:
) )
def _resp(body, status: int = 200) -> MagicMock: def _resp(body: object, status: int = 200) -> MagicMock:
resp = MagicMock() resp = MagicMock()
resp.read.return_value = json.dumps(body).encode() if body is not None else b"" resp.read.return_value = json.dumps(body).encode() if body is not None else b""
resp.status = status resp.status = status
resp.__enter__ = lambda s: s resp.__enter__ = lambda s: s # type: ignore
resp.__exit__ = MagicMock(return_value=False) resp.__exit__ = MagicMock(return_value=False)
return resp return resp
+16 -16
View File
@@ -4,6 +4,7 @@ from __future__ import annotations
import tempfile import tempfile
import unittest import unittest
from dataclasses import replace
from pathlib import Path from pathlib import Path
from unittest.mock import patch from unittest.mock import patch
@@ -15,22 +16,21 @@ from bot_bottle.contrib.gitea.forge_state import (
) )
def _state(**over) -> ForgeState: def _state(**over: object) -> ForgeState:
base = { base = ForgeState(
"owner": "didericis", owner="didericis",
"repo": "bot-bottle", repo="bot-bottle",
"issue_number": 17, issue_number=17,
"slug": "implementer-abc12", slug="implementer-abc12",
"agent_name": "implementer", agent_name="implementer",
"bottle_names": ["claude"], bottle_names=["claude"],
"backend_name": "docker", backend_name="docker",
"agent_git_user": "didericis-claude", agent_git_user="didericis-claude",
"pr_number": 42, pr_number=42,
"status": STATUS_FROZEN, status=STATUS_FROZEN,
"last_checkin_at": "2026-06-29T12:04:12-04:00", last_checkin_at="2026-06-29T12:04:12-04:00",
} )
base.update(over) return replace(base, **over)
return ForgeState(**base)
class ForgeStateTest(unittest.TestCase): class ForgeStateTest(unittest.TestCase):
+23 -10
View File
@@ -7,16 +7,29 @@ import unittest
from bot_bottle.contrib.gitea.provenance import build_provenance_footer from bot_bottle.contrib.gitea.provenance import build_provenance_footer
def _footer(slug: str = "implementer-abc12", **over) -> str: def _footer(
base = { slug: str = "implementer-abc12",
"agent_name": "implementer", *,
"bottle_names": ("claude",), agent_name: str = "implementer",
"started_at": "2026-06-29T12:00:00-04:00", bottle_names: tuple[str, ...] = ("claude",),
"finished_at": "2026-06-29T12:04:12-04:00", started_at: str = "2026-06-29T12:00:00-04:00",
"exit_code": 0, finished_at: str = "2026-06-29T12:04:12-04:00",
} exit_code: int = 0,
base.update(over) watchdog_fired: bool = False,
return build_provenance_footer(slug, **base) gitleaks_clean: bool | None = None,
egress_routes: list[str] | None = None,
) -> str:
return build_provenance_footer(
slug,
agent_name=agent_name,
bottle_names=bottle_names,
started_at=started_at,
finished_at=finished_at,
exit_code=exit_code,
watchdog_fired=watchdog_fired,
gitleaks_clean=gitleaks_clean,
egress_routes=egress_routes,
)
class ProvenanceTest(unittest.TestCase): class ProvenanceTest(unittest.TestCase):