f211ece6bf
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
76 lines
2.4 KiB
Python
76 lines
2.4 KiB
Python
"""Unit: `cli.py resume --headless` non-interactive rehydrate path.
|
|
|
|
The freeze / rehydrate loop needs a non-interactive `resume`: deliver a
|
|
follow-up prompt and skip the y/N preflight, reusing the same launch
|
|
core (`assume_yes` + `headless_prompt_text`) as `start --headless`.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import unittest
|
|
from typing import Any
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import bot_bottle.cli.resume as resume_mod
|
|
from bot_bottle.log import Die
|
|
|
|
|
|
def _metadata():
|
|
md = MagicMock()
|
|
md.agent_name = "implementer"
|
|
md.copy_cwd = False
|
|
md.cwd = "/repo"
|
|
md.identity = "implementer-abc12"
|
|
md.bottle_names = ["claude"]
|
|
md.backend = "docker"
|
|
return md
|
|
|
|
|
|
class ResumeHeadlessTest(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
self._launch = patch.object(
|
|
resume_mod, "_launch_bottle", return_value=0
|
|
).start()
|
|
patch.object(
|
|
resume_mod, "read_metadata", return_value=_metadata()
|
|
).start()
|
|
manifest = MagicMock()
|
|
manifest.require_agent = MagicMock(return_value=None)
|
|
patch.object(
|
|
resume_mod.ManifestIndex, "resolve", return_value=manifest
|
|
).start()
|
|
self.addCleanup(patch.stopall)
|
|
|
|
def _launch_kwargs(self) -> dict[str, Any]:
|
|
self._launch.assert_called_once()
|
|
return dict(self._launch.call_args.kwargs)
|
|
|
|
def test_headless_passes_assume_yes_and_prompt(self):
|
|
rc = resume_mod.cmd_resume(
|
|
["implementer-abc12", "--headless", "--prompt", "Address the review"]
|
|
)
|
|
self.assertEqual(0, rc)
|
|
kwargs = self._launch_kwargs()
|
|
self.assertTrue(kwargs["assume_yes"])
|
|
self.assertEqual("Address the review", kwargs["headless_prompt_text"])
|
|
|
|
def test_interactive_resume_unchanged(self):
|
|
resume_mod.cmd_resume(["implementer-abc12"])
|
|
kwargs = self._launch_kwargs()
|
|
self.assertFalse(kwargs["assume_yes"])
|
|
self.assertEqual("", kwargs["headless_prompt_text"])
|
|
|
|
def test_headless_without_prompt_errors(self):
|
|
with self.assertRaises(Die):
|
|
resume_mod.cmd_resume(["implementer-abc12", "--headless"])
|
|
self._launch.assert_not_called()
|
|
|
|
def test_prompt_without_headless_errors(self):
|
|
with self.assertRaises(Die):
|
|
resume_mod.cmd_resume(["implementer-abc12", "--prompt", "hi"])
|
|
self._launch.assert_not_called()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|