From f6f99b860b10f72d14fc414719da3be7b537517b Mon Sep 17 00:00:00 2001 From: lemoer Date: Fri, 20 Dec 2024 00:54:41 +0100 Subject: [PATCH] models: add next_user_to_send_request() --- models.py | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 101 insertions(+), 6 deletions(-) diff --git a/models.py b/models.py index 7bdb392..c870753 100644 --- a/models.py +++ b/models.py @@ -11,7 +11,8 @@ import datetime import pytest def utc_now(): - return datetime.datetime.now(datetime.timezone.utc) + # We remove the timezone information, because SQLite does not support it + return datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None) class User(SQLModel, table=True): id: int = Field(primary_key=True) @@ -41,6 +42,19 @@ def all_users_sorted(session: Session) -> List[User]: users.sort(key=lambda u: u.sort_time()) return users +def next_user_to_send_request(session: Session, task: "Task") -> User | None: + # Return the user who should send the next request (or None if we already + # sent requests for this Task to all users) + + requested_users = [r.user for r in task.participation_requests] + + users = all_users_sorted(session) + for u in users: + if u in requested_users: + continue + + return u + class Task(SQLModel, table=True): id: int = Field(primary_key=True) name: str @@ -65,8 +79,8 @@ class Task(SQLModel, table=True): def additional_requests_to_be_sent(self): # return the number of additional requests to be sent return self.required_number_of_participants \ - - len(self.accepted_participation_requests()) \ - - len(self.requested_participation_requests()) + - len(self.accepted_requests()) \ + - len(self.requested_requests()) def freshly_expired_requests(self, now) -> List["ParticipationRequest"]: expired_requests = [] @@ -152,7 +166,6 @@ class AlreadyRejected(StateTransitionError): class Timeout(StateTransition): pass - class ParticipationRequest(SQLModel, table=True): id: int = Field(primary_key=True) user_id: int = Field(foreign_key="user.id") @@ -283,6 +296,90 @@ def test_all_users_sorted(session): users = all_users_sorted(session) assert users == [u1, u3, u2] +def test_next_user(session): + u1 = User(name="u1") + u2 = User(name="u2") + u3 = User(name="u3") + + session.add(u1) + session.add(u2) + session.add(u3) + + session.commit() + + # Starting order + users = all_users_sorted(session) + assert users == [u1, u2, u3] + + t1 = Task(name="t1", required_number_of_participants=2, due=utc_now(), timeout=10) + + session.add(t1) + + assert t1.additional_requests_to_be_sent() == 2 + + assert next_user_to_send_request(session, t1) == u1 + + r1 = ParticipationRequest(user=u1, task=t1, requested_at=utc_now()) + session.add(r1) + + assert next_user_to_send_request(session, t1) == u2 + + # Starting order + users = all_users_sorted(session) + assert users == [u1, u2, u3] + + # Another additional request should be sent out. + assert t1.additional_requests_to_be_sent() == 1 + + # After rejecting, two additional requests should be sent out. + assert isinstance(r1.try_reject(utc_now()).unwrap(), RejectInTime) + assert t1.additional_requests_to_be_sent() == 2 + + # Create another Task + t2 = Task(name="t2", required_number_of_participants=2, due=utc_now(), timeout=10) + session.add(t2) + + # Next user should be user1 (because we rejected in t1) + assert next_user_to_send_request(session, t2) == u1 + + # Still starting order (because we rejected) + users = all_users_sorted(session) + assert users == [u1, u2, u3] + + # When we change to accept, only one additional request should be sent out. + assert isinstance(r1.try_accept(utc_now()).unwrap(), AcceptAfterRejectAllowed) + assert t1.additional_requests_to_be_sent() == 1 + + # Next user should be u2 for t2 (because u1 accepted for t1) + assert next_user_to_send_request(session, t2) == u2 + + # Next user should be user2 for t1 also + assert next_user_to_send_request(session, t1) == u2 + + # Still starting order (because we rejected) + users = all_users_sorted(session) + assert users == [u2, u3, u1] + + # Next user should be user2 + assert next_user_to_send_request(session, t1) == u2 + + r3 = ParticipationRequest(user=u3, task=t1, requested_at=utc_now()) + session.add(r3) + + # Next user should still be user2 + assert next_user_to_send_request(session, t1) == u2 + + r2 = ParticipationRequest(user=u2, task=t1, requested_at=utc_now()) + session.add(r2) + + # No user should be left + assert next_user_to_send_request(session, t1) is None + + session.commit() + + # We sent one request too much... + assert t1.additional_requests_to_be_sent() == -1 + def test_accept(session): now = datetime.datetime(2024, 12, 10, 0, 0) dt = datetime.timedelta(days=1) @@ -390,5 +487,3 @@ def test_reject(session): assert(r2.state == ParticipationState.REJECTED) session.commit() - -