feat(core): implement domain dataclasses for issue #5 #19
15
core/__init__.py
Normal file
15
core/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
||||
"""Core domain models for JIGAIDO."""
|
||||
|
||||
from core.models import (
|
||||
Bounty,
|
||||
TrackedBounty,
|
||||
RoomData,
|
||||
TrackingData,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"Bounty",
|
||||
"TrackedBounty",
|
||||
"RoomData",
|
||||
"TrackingData",
|
||||
]
|
||||
61
core/models.py
Normal file
61
core/models.py
Normal file
@@ -0,0 +1,61 @@
|
||||
"""Domain dataclasses for JIGAIDO bounty tracker."""
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class Bounty:
|
||||
"""A bounty created by a user.
|
||||
|
||||
The created_by_user_id field always refers to the user who created the bounty.
|
||||
It does NOT indicate whether the bounty is a group or personal bounty.
|
||||
"""
|
||||
|
||||
id: int
|
||||
text: str | None
|
||||
link: str | None
|
||||
due_date_ts: int | None
|
||||
created_at: int
|
||||
created_by_user_id: int
|
||||
|
||||
|
||||
@dataclass
|
||||
|
han marked this conversation as resolved
|
||||
class TrackedBounty:
|
||||
"""A bounty that a user is tracking.
|
||||
|
||||
Lightweight relation/pointer - the actual tracking context (including room)
|
||||
lives in TrackingData, not here.
|
||||
"""
|
||||
|
||||
bounty_id: int
|
||||
created_at: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class RoomData:
|
||||
"""All data for a room (group or DM).
|
||||
|
||||
The room_id can be negative for Telegram groups or positive for DMs.
|
||||
The next_id field is used to generate unique bounty IDs within this room.
|
||||
"""
|
||||
|
han marked this conversation as resolved
han
commented
since room_id can be negative in RoomData, I don't think we need is_group anymore. if its negative, its group, that should be clear enough since room_id can be negative in RoomData, I don't think we need is_group anymore. if its negative, its group, that should be clear enough
|
||||
|
||||
room_id: int
|
||||
bounties: list[Bounty]
|
||||
next_id: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class TrackingData:
|
||||
"""User tracking state within a room (group or DM).
|
||||
|
||||
TrackingData vs TrackedBounty:
|
||||
- Use TrackingData to store ALL tracked bounties for a user in a specific room.
|
||||
It contains the room_id, user_id, and a list of TrackedBounty entries.
|
||||
- Use TrackedBounty to represent a single tracked bounty entry within that list.
|
||||
|
||||
TrackingData is the container, TrackedBounty is the item.
|
||||
"""
|
||||
|
||||
room_id: int
|
||||
user_id: int
|
||||
tracked: list[TrackedBounty]
|
||||
143
tests/test_models.py
Normal file
143
tests/test_models.py
Normal file
@@ -0,0 +1,143 @@
|
||||
"""Tests for core/models.py — domain dataclasses."""
|
||||
|
||||
import time
|
||||
|
||||
import pytest
|
||||
|
||||
from core.models import (
|
||||
Bounty,
|
||||
TrackedBounty,
|
||||
RoomData,
|
||||
TrackingData,
|
||||
)
|
||||
|
||||
|
||||
class TestBounty:
|
||||
def test_create_bounty(self):
|
||||
b = Bounty(
|
||||
id=1,
|
||||
text="Fix the bug",
|
||||
link="https://github.com/example/repo/issues/1",
|
||||
due_date_ts=1735689600,
|
||||
created_at=1735603200,
|
||||
created_by_user_id=123,
|
||||
)
|
||||
assert b.id == 1
|
||||
assert b.text == "Fix the bug"
|
||||
assert b.link == "https://github.com/example/repo/issues/1"
|
||||
assert b.due_date_ts == 1735689600
|
||||
assert b.created_at == 1735603200
|
||||
assert b.created_by_user_id == 123
|
||||
|
||||
def test_bounty_optional_fields_can_be_none(self):
|
||||
b = Bounty(
|
||||
id=1,
|
||||
text=None,
|
||||
link=None,
|
||||
due_date_ts=None,
|
||||
created_at=0,
|
||||
created_by_user_id=123,
|
||||
)
|
||||
assert b.text is None
|
||||
assert b.link is None
|
||||
assert b.due_date_ts is None
|
||||
|
||||
def test_bounty_comparison_equal(self):
|
||||
b1 = Bounty(
|
||||
id=1,
|
||||
text="a",
|
||||
link=None,
|
||||
due_date_ts=None,
|
||||
created_at=0,
|
||||
created_by_user_id=123,
|
||||
)
|
||||
b2 = Bounty(
|
||||
id=1,
|
||||
text="a",
|
||||
link=None,
|
||||
due_date_ts=None,
|
||||
created_at=0,
|
||||
created_by_user_id=123,
|
||||
)
|
||||
assert b1 == b2
|
||||
|
||||
def test_bounty_comparison_not_equal(self):
|
||||
b1 = Bounty(
|
||||
id=1,
|
||||
text="a",
|
||||
link=None,
|
||||
due_date_ts=None,
|
||||
created_at=0,
|
||||
created_by_user_id=123,
|
||||
)
|
||||
b2 = Bounty(
|
||||
id=2,
|
||||
text="b",
|
||||
link=None,
|
||||
due_date_ts=None,
|
||||
created_at=0,
|
||||
created_by_user_id=456,
|
||||
)
|
||||
assert b1 != b2
|
||||
|
||||
|
||||
class TestTrackedBounty:
|
||||
def test_create_tracked_bounty(self):
|
||||
tb = TrackedBounty(bounty_id=5, created_at=1735600000)
|
||||
assert tb.bounty_id == 5
|
||||
assert tb.created_at == 1735600000
|
||||
|
||||
def test_tracked_bounty_comparison(self):
|
||||
tb1 = TrackedBounty(bounty_id=1, created_at=0)
|
||||
tb2 = TrackedBounty(bounty_id=1, created_at=0)
|
||||
assert tb1 == tb2
|
||||
|
||||
|
||||
class TestRoomData:
|
||||
def test_create_group_room_data(self):
|
||||
rd = RoomData(
|
||||
room_id=-1001,
|
||||
bounties=[],
|
||||
next_id=1,
|
||||
)
|
||||
assert rd.room_id == -1001
|
||||
assert rd.bounties == []
|
||||
assert rd.next_id == 1
|
||||
|
||||
def test_create_dm_room_data(self):
|
||||
rd = RoomData(
|
||||
room_id=123456,
|
||||
bounties=[],
|
||||
next_id=1,
|
||||
)
|
||||
assert rd.room_id == 123456
|
||||
assert rd.bounties == []
|
||||
assert rd.next_id == 1
|
||||
|
||||
def test_room_data_with_bounties(self):
|
||||
b = Bounty(
|
||||
id=1,
|
||||
text="Task",
|
||||
link=None,
|
||||
due_date_ts=None,
|
||||
created_at=0,
|
||||
created_by_user_id=123,
|
||||
)
|
||||
rd = RoomData(room_id=-1001, bounties=[b], next_id=2)
|
||||
assert len(rd.bounties) == 1
|
||||
assert rd.bounties[0].text == "Task"
|
||||
assert rd.bounties[0].created_by_user_id == 123
|
||||
|
||||
|
||||
class TestTrackingData:
|
||||
def test_create_tracking_data(self):
|
||||
td = TrackingData(room_id=-1001, user_id=123456, tracked=[])
|
||||
assert td.room_id == -1001
|
||||
assert td.user_id == 123456
|
||||
assert td.tracked == []
|
||||
|
||||
def test_tracking_data_with_tracked(self):
|
||||
tb = TrackedBounty(bounty_id=5, created_at=0)
|
||||
td = TrackingData(room_id=-1001, user_id=123, tracked=[tb])
|
||||
assert len(td.tracked) == 1
|
||||
assert td.tracked[0].bounty_id == 5
|
||||
Reference in New Issue
Block a user
can we simplify the Bounty by not having both GroupBounty and PersonalBounty? can we utilize Bounty by adding optional created_by_user_id so in this case we can use Bounty for Group and Personal Bounty. what do you think?