"""Unit: forge state persistence (PRD forge-native-integration).""" from __future__ import annotations import tempfile import unittest from pathlib import Path from unittest.mock import patch from bot_bottle.contrib.gitea import forge_state as fs from bot_bottle.contrib.gitea.forge_state import ( STATUS_FROZEN, STATUS_RUNNING, ForgeState, ) def _state(**over) -> ForgeState: base = { "owner": "didericis", "repo": "bot-bottle", "issue_number": 17, "slug": "implementer-abc12", "agent_name": "implementer", "bottle_names": ["claude"], "backend_name": "docker", "agent_git_user": "didericis-claude", "pr_number": 42, "status": STATUS_FROZEN, "last_checkin_at": "2026-06-29T12:04:12-04:00", } base.update(over) return ForgeState(**base) class ForgeStateTest(unittest.TestCase): def setUp(self) -> None: # enterContext handles cleanup; pylint doesn't recognize it as CM-aware. root = Path(self.enterContext( # pylint: disable=consider-using-with tempfile.TemporaryDirectory())) patcher = patch.object(fs, "bot_bottle_root", return_value=root) patcher.start() self.addCleanup(patcher.stop) def test_round_trip(self): fs.write_forge_state(_state()) got = fs.read_forge_state("didericis", "bot-bottle", 17) self.assertEqual(_state(), got) def test_missing_returns_none(self): self.assertIsNone(fs.read_forge_state("nobody", "nope", 1)) def test_path_layout(self): path = fs.forge_state_path("didericis", "bot-bottle", 17) self.assertTrue(str(path).endswith("forge/didericis/bot-bottle/issue-17.json")) def test_write_is_atomic_no_tmp_left(self): fs.write_forge_state(_state()) path = fs.forge_state_path("didericis", "bot-bottle", 17) self.assertFalse(path.with_suffix(".json.tmp").exists()) self.assertTrue(path.exists()) def test_update_overwrites(self): fs.write_forge_state(_state(status=STATUS_RUNNING)) fs.write_forge_state(_state(status=STATUS_FROZEN)) got = fs.read_forge_state("didericis", "bot-bottle", 17) assert got is not None self.assertEqual(STATUS_FROZEN, got.status) def test_delete_is_idempotent(self): fs.write_forge_state(_state()) fs.delete_forge_state("didericis", "bot-bottle", 17) fs.delete_forge_state("didericis", "bot-bottle", 17) # no raise self.assertIsNone(fs.read_forge_state("didericis", "bot-bottle", 17)) def test_all_forge_states_lists_across_repos(self): fs.write_forge_state(_state(issue_number=17)) fs.write_forge_state(_state(issue_number=18, slug="other")) fs.write_forge_state(_state(owner="acme", repo="widget", issue_number=3)) states = fs.all_forge_states() self.assertEqual(3, len(states)) self.assertEqual({17, 18, 3}, {s.issue_number for s in states}) def test_all_forge_states_empty_when_no_dir(self): self.assertEqual([], fs.all_forge_states()) def test_from_dict_ignores_unknown_keys(self): st = ForgeState.from_dict({ "owner": "o", "repo": "r", "issue_number": 1, "slug": "s", "agent_name": "a", "future_field": "ignored", }) self.assertEqual("o", st.owner) self.assertIsNone(st.pr_number) def test_pr_number_optional(self): fs.write_forge_state(_state(pr_number=None)) got = fs.read_forge_state("didericis", "bot-bottle", 17) assert got is not None self.assertIsNone(got.pr_number) if __name__ == "__main__": unittest.main()