Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion maxapi/types/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class User(FetchableMixin, BaseModel, BotMixin, PeerShortcutMixin):
username: Имя пользователя (ник). Может быть None.
is_bot: Флаг, указывающий, является ли пользователь ботом.
last_activity_time: Временная метка последней активности.
Может быть None для удалённых пользователей (DELETED USER).
description: Описание пользователя. Может быть None.
avatar_url: URL аватара пользователя. Может быть None.
full_avatar_url: URL полного аватара пользователя.
Expand All @@ -39,7 +40,7 @@ class User(FetchableMixin, BaseModel, BotMixin, PeerShortcutMixin):
last_name: str | None = None
username: str | None = None
is_bot: bool
last_activity_time: int
last_activity_time: int | None = None
description: str | None = None
avatar_url: str | None = None
full_avatar_url: str | None = None
Expand Down
89 changes: 89 additions & 0 deletions tests/test_swagger_alignment.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from maxapi.methods.get_chat_by_link import GetChatByLink
from maxapi.methods.get_messages import GetMessages
from maxapi.methods.types.added_members_chat import AddedMembersChat
from maxapi.methods.types.getted_members_chat import GettedMembersChat
from maxapi.types.attachments.attachment import ContactAttachmentPayload
from maxapi.types.attachments.image import PhotoAttachmentRequestPayload
from maxapi.types.attachments.video import Video
Expand Down Expand Up @@ -102,6 +103,7 @@ def test_user_and_chat_admin_keep_swagger_compat_fields():
)

assert user.first_name == "Alice"
assert user.last_activity_time == 0
assert admin.alias == "owner"


Expand All @@ -122,6 +124,7 @@ def test_chat_member_accepts_swagger_fields_with_nullable_permissions():
}
)

assert member.last_activity_time == 0
assert member.last_access_time == 10
assert member.is_owner is False
assert member.is_admin is True
Expand All @@ -130,6 +133,92 @@ def test_chat_member_accepts_swagger_fields_with_nullable_permissions():
assert member.alias == "moderator"


def test_chat_member_allows_missing_last_activity_time_for_deleted_user():
"""Issue #179: удалённые пользователи (DELETED USER) не содержат
last_activity_time в ответе API, но содержат last_access_time.
"""

member = ChatMember.model_validate(
{
"user_id": 1,
"first_name": "DELETED USER",
"is_bot": False,
"name": "DELETED USER",
"last_access_time": 0,
}
)

assert member.last_activity_time is None
assert member.last_access_time == 0
assert member.first_name == "DELETED USER"
assert member.user_id == 1


def test_chat_member_still_requires_identity_fields():
"""Убедиться, что фикс #179 не ослабляет остальные обязательные
поля — падать должны только они, а не last_activity_time.
"""

with pytest.raises(ValidationError) as exc_info:
ChatMember.model_validate(
{"last_access_time": 0, "name": "DELETED USER"}
)

missing_fields = {error["loc"][0] for error in exc_info.value.errors()}
assert missing_fields == {"user_id", "first_name", "is_bot"}


def test_user_allows_missing_last_activity_time():
user = User.model_validate(
{"user_id": 1, "first_name": "DELETED USER", "is_bot": False}
)

assert user.last_activity_time is None


def test_getted_members_chat_accepts_deleted_and_normal_members():
"""Issue #179: get_chat_members не должен падать, если среди
участников чата есть удалённый пользователь.
"""

raw = {
"members": [
{
"user_id": 1,
"first_name": "Alice",
"last_name": "Smith",
"username": "alice",
"is_bot": False,
"last_activity_time": 1739184000000,
"name": "Alice Smith",
"last_access_time": 1739184000000,
"is_owner": True,
"is_admin": True,
"join_time": 1739000000000,
"permissions": ["write"],
"alias": None,
},
{
"user_id": 2,
"first_name": "DELETED USER",
"is_bot": False,
"name": "DELETED USER",
"last_access_time": 0,
},
],
"marker": 42,
}

result = GettedMembersChat(**raw)

assert len(result.members) == 2
assert result.members[0].last_activity_time == 1739184000000
assert result.members[0].last_access_time == 1739184000000
assert result.members[1].last_activity_time is None
assert result.members[1].last_access_time == 0
assert result.marker == 42


def test_get_messages_requires_chat_id_or_message_ids(bot):
with pytest.raises(ValueError, match="chat_id или message_ids"):
GetMessages(bot=bot)
Expand Down
Loading