"""Tests for adapters/storage/json_file.py — JSON file storage adapter.""" import pytest import tempfile import shutil from pathlib import Path from core.models import Bounty, RoomData, TrackingData, TrackedBounty from core.ports import RoomStorage, TrackingStorage from adapters.storage.json_file import JsonFileRoomStorage, JsonFileTrackingStorage class TestJsonFileRoomStorage: """Tests for JsonFileRoomStorage adapter.""" @pytest.fixture def temp_dir(self): """Create a temporary directory for test files.""" tmp = tempfile.mkdtemp() yield Path(tmp) shutil.rmtree(tmp) @pytest.fixture def storage(self, temp_dir): """Create a JsonFileRoomStorage instance with temp directory.""" return JsonFileRoomStorage(data_dir=temp_dir) def test_implements_room_storage_protocol(self, storage): """Verify JsonFileRoomStorage implements RoomStorage protocol.""" assert isinstance(storage, RoomStorage) def test_load_returns_none_for_nonexistent_room(self, storage): """Load returns None if room doesn't exist.""" result = storage.load(-1001) assert result is None def test_save_and_load_room(self, storage): """Test saving and loading room data.""" room = RoomData(room_id=-1001, bounties=[], next_id=1) storage.save(room) loaded = storage.load(-1001) assert loaded is not None assert loaded.room_id == -1001 assert loaded.next_id == 1 assert loaded.bounties == [] def test_add_bounty_creates_room(self, storage): """Test add_bounty creates room if it doesn't exist.""" bounty = Bounty( id=1, text="Test bounty", link=None, due_date_ts=None, created_at=1000, created_by_user_id=123, ) storage.add_bounty(-1001, bounty) room = storage.load(-1001) assert room is not None assert len(room.bounties) == 1 assert room.bounties[0].text == "Test bounty" assert room.next_id == 2 def test_add_bounty_appends_to_existing_room(self, storage): """Test add_bounty appends to existing room.""" bounty1 = Bounty( id=1, text="First", link=None, due_date_ts=None, created_at=1000, created_by_user_id=123, ) bounty2 = Bounty( id=2, text="Second", link=None, due_date_ts=None, created_at=1001, created_by_user_id=123, ) storage.add_bounty(-1001, bounty1) storage.add_bounty(-1001, bounty2) room = storage.load(-1001) assert len(room.bounties) == 2 assert room.next_id == 3 def test_update_bounty(self, storage): """Test updating an existing bounty.""" bounty = Bounty( id=1, text="Original", link=None, due_date_ts=None, created_at=1000, created_by_user_id=123, ) storage.add_bounty(-1001, bounty) updated = Bounty( id=1, text="Updated", link="https://example.com", due_date_ts=2000, created_at=1000, created_by_user_id=123, ) storage.update_bounty(-1001, updated) result = storage.get_bounty(-1001, 1) assert result is not None assert result.text == "Updated" assert result.link == "https://example.com" def test_delete_bounty(self, storage): """Test deleting a bounty.""" bounty = Bounty( id=1, text="To delete", link=None, due_date_ts=None, created_at=1000, created_by_user_id=123, ) storage.add_bounty(-1001, bounty) storage.delete_bounty(-1001, 1) result = storage.get_bounty(-1001, 1) assert result is None def test_get_bounty_returns_none_for_nonexistent(self, storage): """Test get_bounty returns None for non-existent bounty.""" result = storage.get_bounty(-1001, 999) assert result is None def test_update_bounty_for_nonexistent_room(self, storage): """Test update_bounty does nothing for non-existent room.""" bounty = Bounty( id=1, text="Test", link=None, due_date_ts=None, created_at=1000, created_by_user_id=123, ) result = storage.update_bounty(-1001, bounty) # Should not raise, should just return class TestJsonFileTrackingStorage: """Tests for JsonFileTrackingStorage adapter.""" @pytest.fixture def temp_dir(self): """Create a temporary directory for test files.""" tmp = tempfile.mkdtemp() yield Path(tmp) shutil.rmtree(tmp) @pytest.fixture def storage(self, temp_dir): """Create a JsonFileTrackingStorage instance with temp directory.""" return JsonFileTrackingStorage(tracking_dir=temp_dir) def test_implements_tracking_storage_protocol(self, storage): """Verify JsonFileTrackingStorage implements TrackingStorage protocol.""" assert isinstance(storage, TrackingStorage) def test_load_returns_none_for_nonexistent_tracking(self, storage): """Load returns None if tracking doesn't exist.""" result = storage.load(-1001, 123456) assert result is None def test_save_and_load_tracking(self, storage): """Test saving and loading tracking data.""" tracking = TrackingData( room_id=-1001, user_id=123456, tracked=[ TrackedBounty(bounty_id=1, created_at=1000), ], ) storage.save(tracking) loaded = storage.load(-1001, 123456) assert loaded is not None assert loaded.room_id == -1001 assert loaded.user_id == 123456 assert len(loaded.tracked) == 1 def test_track_bounty_creates_tracking(self, storage): """Test track_bounty creates tracking if it doesn't exist.""" tracked = TrackedBounty(bounty_id=5, created_at=1000) storage.track_bounty(-1001, 123456, tracked) result = storage.load(-1001, 123456) assert result is not None assert len(result.tracked) == 1 assert result.tracked[0].bounty_id == 5 def test_track_bounty_appends_to_existing(self, storage): """Test track_bounty appends to existing tracking.""" tracked1 = TrackedBounty(bounty_id=1, created_at=1000) tracked2 = TrackedBounty(bounty_id=2, created_at=1001) storage.track_bounty(-1001, 123456, tracked1) storage.track_bounty(-1001, 123456, tracked2) result = storage.load(-1001, 123456) assert len(result.tracked) == 2 def test_untrack_bounty(self, storage): """Test untracking a bounty.""" tracked = TrackedBounty(bounty_id=5, created_at=1000) storage.track_bounty(-1001, 123456, tracked) storage.untrack_bounty(-1001, 123456, 5) result = storage.load(-1001, 123456) assert len(result.tracked) == 0 def test_untrack_nonexistent_does_not_raise(self, storage): """Test untracking non-existent bounty doesn't raise.""" # Should not raise storage.untrack_bounty(-1001, 123456, 999) def test_tracking_persists_across_storage_instances(self, temp_dir): """Test that tracking data persists when using different storage instances.""" storage1 = JsonFileTrackingStorage(tracking_dir=temp_dir) tracked = TrackedBounty(bounty_id=5, created_at=1000) storage1.track_bounty(-1001, 123456, tracked) # Create a new storage instance pointing to the same directory storage2 = JsonFileTrackingStorage(tracking_dir=temp_dir) result = storage2.load(-1001, 123456) assert result is not None assert len(result.tracked) == 1 class TestJsonFileRoomStoragePersistence: """Test that room data persists across storage instances.""" @pytest.fixture def temp_dir(self): """Create a temporary directory for test files.""" tmp = tempfile.mkdtemp() yield Path(tmp) shutil.rmtree(tmp) def test_room_persists_across_storage_instances(self, temp_dir): """Test room data persists when using different storage instances.""" storage1 = JsonFileRoomStorage(data_dir=temp_dir) bounty = Bounty( id=1, text="Persisted bounty", link=None, due_date_ts=None, created_at=1000, created_by_user_id=123, ) storage1.add_bounty(-1001, bounty) # Create a new storage instance pointing to the same directory storage2 = JsonFileRoomStorage(data_dir=temp_dir) room = storage2.load(-1001) assert room is not None assert len(room.bounties) == 1 assert room.bounties[0].text == "Persisted bounty"