From e691abce60172a4406891126f5c7647bb289110c Mon Sep 17 00:00:00 2001 From: shokollm <270575765+shokollm@users.noreply.github.com> Date: Fri, 3 Apr 2026 08:36:04 +0000 Subject: [PATCH] Revert "Merge feat/issue-6-storage-ports: JSON file storage adapter for issue #9" This reverts commit 9e3641a8501bffd6284235718ae84e432765cbcf, reversing changes made to 8aebb763ee83888ed09df2f86f92617f05932c59. --- adapters/__init__.py | 0 adapters/storage/__init__.py | 0 adapters/storage/json_file.py | 202 ------------------------ tests/test_json_file_adapter.py | 269 -------------------------------- 4 files changed, 471 deletions(-) delete mode 100644 adapters/__init__.py delete mode 100644 adapters/storage/__init__.py delete mode 100644 adapters/storage/json_file.py delete mode 100644 tests/test_json_file_adapter.py diff --git a/adapters/__init__.py b/adapters/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/adapters/storage/__init__.py b/adapters/storage/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/adapters/storage/json_file.py b/adapters/storage/json_file.py deleted file mode 100644 index f2f8de0..0000000 --- a/adapters/storage/json_file.py +++ /dev/null @@ -1,202 +0,0 @@ -"""JSON file storage adapter for JIGAIDO. - -Implements RoomStorage and TrackingStorage ports using JSON file persistence. -Data stored at: -- Rooms: ~/.jigaido/data/.json -- Tracking: ~/.jigaido/tracking/_.json -""" - -import json -import os -from pathlib import Path - -from core.models import Bounty, RoomData, TrackingData, TrackedBounty -from core.ports import RoomStorage, TrackingStorage - - -class JsonFileRoomStorage: - """RoomStorage implementation using JSON files. - - Stores room data at ~/.jigaido/data/.json - """ - - def __init__(self, data_dir: Path | None = None): - if data_dir is None: - data_dir = Path.home() / ".jigaido" / "data" - self._data_dir = data_dir - self._data_dir.mkdir(parents=True, exist_ok=True) - - def _get_file_path(self, room_id: int) -> Path: - return self._data_dir / f"{room_id}.json" - - def load(self, room_id: int) -> RoomData | None: - """Load room data from JSON file. Returns None if not found.""" - file_path = self._get_file_path(room_id) - if not file_path.exists(): - return None - - with open(file_path, "r") as f: - data = json.load(f) - - bounties = [ - Bounty( - id=b["id"], - text=b.get("text"), - link=b.get("link"), - due_date_ts=b.get("due_date_ts"), - created_at=b["created_at"], - created_by_user_id=b["created_by_user_id"], - ) - for b in data.get("bounties", []) - ] - - return RoomData( - room_id=data["room_id"], - bounties=bounties, - next_id=data["next_id"], - ) - - def save(self, room_data: RoomData) -> None: - """Save room data to JSON file.""" - data = { - "room_id": room_data.room_id, - "next_id": room_data.next_id, - "bounties": [ - { - "id": b.id, - "text": b.text, - "link": b.link, - "due_date_ts": b.due_date_ts, - "created_at": b.created_at, - "created_by_user_id": b.created_by_user_id, - } - for b in room_data.bounties - ], - } - - with open(self._get_file_path(room_data.room_id), "w") as f: - json.dump(data, f) - - def add_bounty(self, room_id: int, bounty: Bounty) -> None: - """Add a bounty to a room, creating the room if necessary.""" - room_data = self.load(room_id) - if room_data is None: - room_data = RoomData(room_id=room_id, bounties=[], next_id=1) - - room_data.bounties.append(bounty) - if bounty.id >= room_data.next_id: - room_data.next_id = bounty.id + 1 - - self.save(room_data) - - def update_bounty(self, room_id: int, bounty: Bounty) -> None: - """Update an existing bounty in a room.""" - room_data = self.load(room_id) - if room_data is None: - return - - for i, b in enumerate(room_data.bounties): - if b.id == bounty.id: - room_data.bounties[i] = bounty - break - - self.save(room_data) - - def delete_bounty(self, room_id: int, bounty_id: int) -> None: - """Delete a bounty from a room.""" - room_data = self.load(room_id) - if room_data is None: - return - - room_data.bounties = [b for b in room_data.bounties if b.id != bounty_id] - self.save(room_data) - - def get_bounty(self, room_id: int, bounty_id: int) -> Bounty | None: - """Get a specific bounty from a room by ID.""" - room_data = self.load(room_id) - if room_data is None: - return None - - for b in room_data.bounties: - if b.id == bounty_id: - return b - - return None - - -class JsonFileTrackingStorage: - """TrackingStorage implementation using JSON files. - - Stores tracking data at ~/.jigaido/tracking/_.json - """ - - def __init__(self, tracking_dir: Path | None = None): - if tracking_dir is None: - tracking_dir = Path.home() / ".jigaido" / "tracking" - self._tracking_dir = tracking_dir - self._tracking_dir.mkdir(parents=True, exist_ok=True) - - def _get_file_path(self, room_id: int, user_id: int) -> Path: - return self._tracking_dir / f"{room_id}_{user_id}.json" - - def load(self, room_id: int, user_id: int) -> TrackingData | None: - """Load tracking data from JSON file. Returns None if not found.""" - file_path = self._get_file_path(room_id, user_id) - if not file_path.exists(): - return None - - with open(file_path, "r") as f: - data = json.load(f) - - tracked = [ - TrackedBounty( - bounty_id=t["bounty_id"], - created_at=t["created_at"], - ) - for t in data.get("tracked", []) - ] - - return TrackingData( - room_id=data["room_id"], - user_id=data["user_id"], - tracked=tracked, - ) - - def save(self, tracking_data: TrackingData) -> None: - """Save tracking data to JSON file.""" - data = { - "room_id": tracking_data.room_id, - "user_id": tracking_data.user_id, - "tracked": [ - { - "bounty_id": t.bounty_id, - "created_at": t.created_at, - } - for t in tracking_data.tracked - ], - } - - with open( - self._get_file_path(tracking_data.room_id, tracking_data.user_id), "w" - ) as f: - json.dump(data, f) - - def track_bounty(self, room_id: int, user_id: int, tracked: TrackedBounty) -> None: - """Add a bounty to a user's tracking list, creating the tracking entry if needed.""" - tracking_data = self.load(room_id, user_id) - if tracking_data is None: - tracking_data = TrackingData(room_id=room_id, user_id=user_id, tracked=[]) - - tracking_data.tracked.append(tracked) - self.save(tracking_data) - - def untrack_bounty(self, room_id: int, user_id: int, bounty_id: int) -> None: - """Remove a bounty from a user's tracking list.""" - tracking_data = self.load(room_id, user_id) - if tracking_data is None: - return - - tracking_data.tracked = [ - t for t in tracking_data.tracked if t.bounty_id != bounty_id - ] - self.save(tracking_data) diff --git a/tests/test_json_file_adapter.py b/tests/test_json_file_adapter.py deleted file mode 100644 index 068d2f8..0000000 --- a/tests/test_json_file_adapter.py +++ /dev/null @@ -1,269 +0,0 @@ -"""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"