Files
duty-teller/tests/test_group_duty_pin_service.py
Nikolay Tatarinov e3240d0981 feat: enhance duty information handling with contact details and current duty view
- Added `bot_username` to settings for dynamic retrieval of the bot's username.
- Implemented `_resolve_bot_username` function to fetch the bot's username if not set, improving user experience in group chats.
- Updated `DutyWithUser` schema to include optional `phone` and `username` fields for enhanced duty information.
- Enhanced API responses to include contact details for users, ensuring better communication.
- Introduced a new current duty view in the web app, displaying active duty information along with contact options.
- Updated CSS styles for better presentation of contact information in duty cards.
- Added unit tests to verify the inclusion of contact details in API responses and the functionality of the current duty view.
2026-03-02 16:09:08 +03:00

203 lines
6.8 KiB
Python

"""Tests for duty_teller.services.group_duty_pin_service."""
from datetime import datetime
from types import SimpleNamespace
from unittest.mock import patch
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from duty_teller.db.models import Base, Duty, User
from duty_teller.services import group_duty_pin_service as svc
@pytest.fixture
def session():
"""In-memory SQLite session with all models."""
engine = create_engine(
"sqlite:///:memory:", connect_args={"check_same_thread": False}
)
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine, autocommit=False, autoflush=False)
s = Session()
try:
yield s
finally:
s.close()
engine.dispose()
@pytest.fixture
def user(session):
"""Create a user in DB."""
u = User(
telegram_user_id=123,
full_name="Test User",
username="testuser",
first_name="Test",
last_name="User",
phone="+79001234567",
)
session.add(u)
session.commit()
session.refresh(u)
return u
@pytest.fixture
def duty(session, user):
"""Create a duty in DB (event_type='duty')."""
d = Duty(
user_id=user.id,
start_at="2025-02-20T06:00:00Z",
end_at="2025-02-21T06:00:00Z",
event_type="duty",
)
session.add(d)
session.commit()
session.refresh(d)
return d
class TestFormatDutyMessage:
"""Tests for format_duty_message."""
def test_none_duty_returns_no_duty(self):
user = SimpleNamespace(full_name="U")
with patch("duty_teller.services.group_duty_pin_service.t") as mock_t:
mock_t.return_value = "No duty"
result = svc.format_duty_message(None, user, "Europe/Moscow", "en")
assert result == "No duty"
mock_t.assert_called_with("en", "duty.no_duty")
def test_none_user_returns_no_duty(self):
duty = SimpleNamespace(
start_at="2025-01-15T09:00:00Z", end_at="2025-01-15T18:00:00Z"
)
with patch("duty_teller.services.group_duty_pin_service.t") as mock_t:
mock_t.return_value = "No duty"
result = svc.format_duty_message(duty, None, "Europe/Moscow", "en")
assert result == "No duty"
def test_with_duty_and_user_returns_formatted(self):
"""Formatted message includes time range and full name only; no contact info (phone/username)."""
duty = SimpleNamespace(
start_at="2025-01-15T09:00:00Z",
end_at="2025-01-15T18:00:00Z",
)
user = SimpleNamespace(
full_name="Иван Иванов",
phone="+79001234567",
username="ivan",
)
with patch("duty_teller.services.group_duty_pin_service.t") as mock_t:
mock_t.side_effect = lambda lang, key: "Duty" if key == "duty.label" else ""
result = svc.format_duty_message(duty, user, "Europe/Moscow", "ru")
assert "Иван Иванов" in result
assert "Duty" in result
# Contact info is restricted to Mini App; not shown in pinned group message.
assert "+79001234567" not in result and "79001234567" not in result
assert "@ivan" not in result
class TestGetDutyMessageText:
"""Tests for get_duty_message_text."""
def test_no_current_duty_returns_no_duty(self, session):
svc.duty_pin_cache.invalidate_pattern(("duty_message_text",))
with patch(
"duty_teller.services.group_duty_pin_service.get_current_duty",
return_value=None,
):
with patch("duty_teller.services.group_duty_pin_service.t") as mock_t:
mock_t.return_value = "No duty"
result = svc.get_duty_message_text(session, "Europe/Moscow", "en")
assert result == "No duty"
def test_with_current_duty_returns_formatted(self, session, duty, user):
svc.duty_pin_cache.invalidate_pattern(("duty_message_text",))
with patch(
"duty_teller.services.group_duty_pin_service.get_current_duty",
return_value=(duty, user),
):
with patch("duty_teller.services.group_duty_pin_service.t") as mock_t:
mock_t.side_effect = lambda lang, key: (
"Duty" if key == "duty.label" else "No duty"
)
result = svc.get_duty_message_text(session, "Europe/Moscow", "en")
assert "Test User" in result
assert "Duty" in result
class TestGetNextShiftEndUtc:
"""Tests for get_next_shift_end_utc."""
def test_no_next_shift_returns_none(self, session):
svc.duty_pin_cache.invalidate(("next_shift_end",))
with patch(
"duty_teller.services.group_duty_pin_service.get_next_shift_end",
return_value=None,
):
result = svc.get_next_shift_end_utc(session)
assert result is None
def test_has_next_shift_returns_naive_utc(self, session):
svc.duty_pin_cache.invalidate(("next_shift_end",))
naive = datetime(2025, 2, 21, 6, 0, 0)
with patch(
"duty_teller.services.group_duty_pin_service.get_next_shift_end",
return_value=naive,
):
result = svc.get_next_shift_end_utc(session)
assert result == naive
class TestSavePin:
"""Tests for save_pin."""
def test_save_pin_creates_record(self, session):
svc.save_pin(session, chat_id=100, message_id=42)
mid = svc.get_message_id(session, 100)
assert mid == 42
def test_save_pin_updates_existing(self, session):
svc.save_pin(session, chat_id=100, message_id=42)
svc.save_pin(session, chat_id=100, message_id=99)
mid = svc.get_message_id(session, 100)
assert mid == 99
class TestDeletePin:
"""Tests for delete_pin."""
def test_delete_pin_removes_record(self, session):
svc.save_pin(session, chat_id=200, message_id=1)
assert svc.get_message_id(session, 200) == 1
svc.delete_pin(session, chat_id=200)
assert svc.get_message_id(session, 200) is None
class TestGetMessageId:
"""Tests for get_message_id."""
def test_no_pin_returns_none(self, session):
assert svc.get_message_id(session, 999) is None
def test_after_save_returns_message_id(self, session):
svc.save_pin(session, chat_id=300, message_id=77)
assert svc.get_message_id(session, 300) == 77
class TestGetAllPinChatIds:
"""Tests for get_all_pin_chat_ids."""
def test_empty_returns_empty_list(self, session):
assert svc.get_all_pin_chat_ids(session) == []
def test_returns_all_chat_ids_with_pins(self, session):
svc.save_pin(session, chat_id=10, message_id=1)
svc.save_pin(session, chat_id=20, message_id=2)
chat_ids = svc.get_all_pin_chat_ids(session)
assert set(chat_ids) == {10, 20}