File: test_forum.py

package info (click to toggle)
python-telegram-bot 22.3-1
  • links: PTS
  • area: main
  • in suites: sid
  • size: 11,060 kB
  • sloc: python: 90,298; makefile: 176; sh: 4
file content (467 lines) | stat: -rw-r--r-- 18,127 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program.  If not, see [http://www.gnu.org/licenses/].
import asyncio
import datetime as dtm

import pytest

from telegram import (
    ForumTopic,
    ForumTopicClosed,
    ForumTopicCreated,
    ForumTopicEdited,
    ForumTopicReopened,
    GeneralForumTopicHidden,
    GeneralForumTopicUnhidden,
    Sticker,
)
from telegram.error import BadRequest
from tests.auxil.constants import TEST_MSG_TEXT, TEST_TOPIC_ICON_COLOR, TEST_TOPIC_NAME
from tests.auxil.slots import mro_slots


@pytest.fixture(scope="module")
async def forum_topic_object(forum_group_id, emoji_id):
    return ForumTopic(
        message_thread_id=forum_group_id,
        name=TEST_TOPIC_NAME,
        icon_color=TEST_TOPIC_ICON_COLOR,
        icon_custom_emoji_id=emoji_id,
    )


class TestForumTopicWithoutRequest:
    def test_slot_behaviour(self, forum_topic_object):
        inst = forum_topic_object
        for attr in inst.__slots__:
            assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'"
        assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot"

    async def test_expected_values(self, emoji_id, forum_group_id, forum_topic_object):
        assert forum_topic_object.message_thread_id == forum_group_id
        assert forum_topic_object.icon_color == TEST_TOPIC_ICON_COLOR
        assert forum_topic_object.name == TEST_TOPIC_NAME
        assert forum_topic_object.icon_custom_emoji_id == emoji_id

    def test_de_json(self, offline_bot, emoji_id, forum_group_id):

        json_dict = {
            "message_thread_id": forum_group_id,
            "name": TEST_TOPIC_NAME,
            "icon_color": TEST_TOPIC_ICON_COLOR,
            "icon_custom_emoji_id": emoji_id,
        }
        topic = ForumTopic.de_json(json_dict, offline_bot)
        assert topic.api_kwargs == {}

        assert topic.message_thread_id == forum_group_id
        assert topic.icon_color == TEST_TOPIC_ICON_COLOR
        assert topic.name == TEST_TOPIC_NAME
        assert topic.icon_custom_emoji_id == emoji_id

    def test_to_dict(self, emoji_id, forum_group_id, forum_topic_object):
        topic_dict = forum_topic_object.to_dict()

        assert isinstance(topic_dict, dict)
        assert topic_dict["message_thread_id"] == forum_group_id
        assert topic_dict["name"] == TEST_TOPIC_NAME
        assert topic_dict["icon_color"] == TEST_TOPIC_ICON_COLOR
        assert topic_dict["icon_custom_emoji_id"] == emoji_id

    def test_equality(self, emoji_id, forum_group_id):
        a = ForumTopic(
            message_thread_id=forum_group_id,
            name=TEST_TOPIC_NAME,
            icon_color=TEST_TOPIC_ICON_COLOR,
        )
        b = ForumTopic(
            message_thread_id=forum_group_id,
            name=TEST_TOPIC_NAME,
            icon_color=TEST_TOPIC_ICON_COLOR,
            icon_custom_emoji_id=emoji_id,
        )
        c = ForumTopic(
            message_thread_id=forum_group_id,
            name=f"{TEST_TOPIC_NAME}!",
            icon_color=TEST_TOPIC_ICON_COLOR,
        )
        d = ForumTopic(
            message_thread_id=forum_group_id + 1,
            name=TEST_TOPIC_NAME,
            icon_color=TEST_TOPIC_ICON_COLOR,
        )
        e = ForumTopic(
            message_thread_id=forum_group_id,
            name=TEST_TOPIC_NAME,
            icon_color=0xFFD67E,
        )

        assert a == b
        assert hash(a) == hash(b)

        assert a != c
        assert hash(a) != hash(c)

        assert a != d
        assert hash(a) != hash(d)

        assert a != e
        assert hash(a) != hash(e)


class TestForumMethodsWithRequest:
    async def test_create_forum_topic(self, real_topic):
        result = real_topic
        assert isinstance(result, ForumTopic)
        assert result.name == TEST_TOPIC_NAME
        assert result.message_thread_id
        assert isinstance(result.icon_color, int)
        assert isinstance(result.icon_custom_emoji_id, str)

    async def test_create_forum_topic_with_only_required_args(self, bot, forum_group_id):
        result = await bot.create_forum_topic(chat_id=forum_group_id, name=TEST_TOPIC_NAME)
        assert isinstance(result, ForumTopic)
        assert result.name == TEST_TOPIC_NAME
        assert result.message_thread_id
        assert isinstance(result.icon_color, int)  # color is still there though it was not passed
        assert result.icon_custom_emoji_id is None

        result = await bot.delete_forum_topic(
            chat_id=forum_group_id, message_thread_id=result.message_thread_id
        )
        assert result is True, "Failed to delete forum topic"

    async def test_get_forum_topic_icon_stickers(self, bot):
        emoji_sticker_list = await bot.get_forum_topic_icon_stickers()
        first_sticker = emoji_sticker_list[0]

        assert first_sticker.emoji == "📰"
        assert first_sticker.height == 512
        assert first_sticker.width == 512
        assert first_sticker.is_animated
        assert not first_sticker.is_video
        assert first_sticker.set_name == "Topics"
        assert first_sticker.type == Sticker.CUSTOM_EMOJI
        assert first_sticker.thumbnail.width == 128
        assert first_sticker.thumbnail.height == 128

        # The following data of first item returned has changed in the past already,
        # so check sizes loosely and ID's only by length of string
        assert first_sticker.thumbnail.file_size in range(2000, 7000)
        assert first_sticker.file_size in range(20000, 70000)
        assert len(first_sticker.custom_emoji_id) == 19
        assert len(first_sticker.thumbnail.file_unique_id) == 16
        assert len(first_sticker.file_unique_id) == 15

    async def test_edit_forum_topic(self, emoji_id, forum_group_id, bot, real_topic):
        result = await bot.edit_forum_topic(
            chat_id=forum_group_id,
            message_thread_id=real_topic.message_thread_id,
            name=f"{TEST_TOPIC_NAME}_EDITED",
            icon_custom_emoji_id=emoji_id,
        )
        assert result is True, "Failed to edit forum topic"
        # no way of checking the edited name, just the boolean result

    async def test_send_message_to_topic(self, bot, forum_group_id, real_topic):
        message_thread_id = real_topic.message_thread_id

        message = await bot.send_message(
            chat_id=forum_group_id, text=TEST_MSG_TEXT, message_thread_id=message_thread_id
        )

        assert message.text == TEST_MSG_TEXT
        assert message.is_topic_message is True
        assert message.message_thread_id == message_thread_id

    async def test_close_and_reopen_forum_topic(self, bot, forum_group_id, real_topic):
        message_thread_id = real_topic.message_thread_id

        result = await bot.close_forum_topic(
            chat_id=forum_group_id,
            message_thread_id=message_thread_id,
        )
        assert result is True, "Failed to close forum topic"
        # bot will still be able to send a message to a closed topic, so can't test anything like
        # the inability to post to the topic

        result = await bot.reopen_forum_topic(
            chat_id=forum_group_id,
            message_thread_id=message_thread_id,
        )
        assert result is True, "Failed to reopen forum topic"

    async def test_unpin_all_forum_topic_messages(self, bot, forum_group_id, real_topic):
        # We need 2 or more pinned msgs for this to work, else we get Chat_not_modified error
        message_thread_id = real_topic.message_thread_id
        pin_msg_tasks = set()

        awaitables = {
            bot.send_message(forum_group_id, TEST_MSG_TEXT, message_thread_id=message_thread_id)
            for _ in range(2)
        }
        for coro in asyncio.as_completed(awaitables):
            msg = await coro
            pin_msg_tasks.add(asyncio.create_task(msg.pin()))

        assert all([await task for task in pin_msg_tasks]) is True, "Message(s) were not pinned"

        result = await bot.unpin_all_forum_topic_messages(forum_group_id, message_thread_id)
        assert result is True, "Failed to unpin all the messages in forum topic"

    async def test_unpin_all_general_forum_topic_messages(self, bot, forum_group_id):
        # We need 2 or more pinned msgs for this to work, else we get Chat_not_modified error
        pin_msg_tasks = set()

        awaitables = {bot.send_message(forum_group_id, TEST_MSG_TEXT) for _ in range(2)}
        for coro in asyncio.as_completed(awaitables):
            msg = await coro
            pin_msg_tasks.add(asyncio.create_task(msg.pin()))

        assert all([await task for task in pin_msg_tasks]) is True, "Message(s) were not pinned"

        result = await bot.unpin_all_general_forum_topic_messages(forum_group_id)
        assert result is True, "Failed to unpin all the messages in forum topic"

    async def test_edit_general_forum_topic(self, bot, forum_group_id):
        result = await bot.edit_general_forum_topic(
            chat_id=forum_group_id,
            name=f"GENERAL_{dtm.datetime.now().timestamp()}",
        )
        assert result is True, "Failed to edit general forum topic"
        # no way of checking the edited name, just the boolean result

    async def test_close_reopen_hide_unhide_general_forum_topic(self, bot, forum_group_id):
        """Since reopening also unhides and hiding also closes, testing (un)hiding and
        closing/reopening in different tests would mean that the tests have to be executed in
        a specific order. For stability, we instead test all of them in one test."""

        # We first ensure that the topic is open and visible
        # Otherwise the tests below will fail
        try:
            await bot.reopen_general_forum_topic(chat_id=forum_group_id)
        except BadRequest as exc:
            # If the topic is already open, we get BadRequest: Topic_not_modified
            if "Topic_not_modified" not in exc.message:
                raise exc

        # first just close, bot don't hide
        result = await bot.close_general_forum_topic(
            chat_id=forum_group_id,
        )
        assert result is True, "Failed to close general forum topic"

        # then hide
        result = await bot.hide_general_forum_topic(
            chat_id=forum_group_id,
        )
        assert result is True, "Failed to hide general forum topic"

        # then unhide, but don't reopen
        result = await bot.unhide_general_forum_topic(
            chat_id=forum_group_id,
        )
        assert result is True, "Failed to unhide general forum topic"

        # finally, reopen
        # as this also unhides, this should ensure that the topic is open and visible
        # for the next test run
        result = await bot.reopen_general_forum_topic(
            chat_id=forum_group_id,
        )
        assert result is True, "Failed to reopen general forum topic"


@pytest.fixture(scope="module")
def topic_created():
    return ForumTopicCreated(name=TEST_TOPIC_NAME, icon_color=TEST_TOPIC_ICON_COLOR)


class TestForumTopicCreatedWithoutRequest:
    def test_slot_behaviour(self, topic_created):
        for attr in topic_created.__slots__:
            assert getattr(topic_created, attr, "err") != "err", f"got extra slot '{attr}'"
        assert len(mro_slots(topic_created)) == len(
            set(mro_slots(topic_created))
        ), "duplicate slot"

    def test_expected_values(self, topic_created):
        assert topic_created.icon_color == TEST_TOPIC_ICON_COLOR
        assert topic_created.name == TEST_TOPIC_NAME

    def test_de_json(self, offline_bot):

        json_dict = {"icon_color": TEST_TOPIC_ICON_COLOR, "name": TEST_TOPIC_NAME}
        action = ForumTopicCreated.de_json(json_dict, offline_bot)
        assert action.api_kwargs == {}

        assert action.icon_color == TEST_TOPIC_ICON_COLOR
        assert action.name == TEST_TOPIC_NAME

    def test_to_dict(self, topic_created):
        action_dict = topic_created.to_dict()

        assert isinstance(action_dict, dict)
        assert action_dict["name"] == TEST_TOPIC_NAME
        assert action_dict["icon_color"] == TEST_TOPIC_ICON_COLOR

    def test_equality(self, emoji_id):
        a = ForumTopicCreated(name=TEST_TOPIC_NAME, icon_color=TEST_TOPIC_ICON_COLOR)
        b = ForumTopicCreated(
            name=TEST_TOPIC_NAME,
            icon_color=TEST_TOPIC_ICON_COLOR,
            icon_custom_emoji_id=emoji_id,
        )
        c = ForumTopicCreated(name=f"{TEST_TOPIC_NAME}!", icon_color=TEST_TOPIC_ICON_COLOR)
        d = ForumTopicCreated(name=TEST_TOPIC_NAME, icon_color=0xFFD67E)

        assert a == b
        assert hash(a) == hash(b)

        assert a != c
        assert hash(a) != hash(c)

        assert a != d
        assert hash(a) != hash(d)


class TestForumTopicClosedWithoutRequest:
    def test_slot_behaviour(self):
        action = ForumTopicClosed()
        for attr in action.__slots__:
            assert getattr(action, attr, "err") != "err", f"got extra slot '{attr}'"
        assert len(mro_slots(action)) == len(set(mro_slots(action))), "duplicate slot"

    def test_de_json(self):
        action = ForumTopicClosed.de_json({}, None)
        assert action.api_kwargs == {}
        assert isinstance(action, ForumTopicClosed)

    def test_to_dict(self):
        action = ForumTopicClosed()
        action_dict = action.to_dict()
        assert action_dict == {}


class TestForumTopicReopenedWithoutRequest:
    def test_slot_behaviour(self):
        action = ForumTopicReopened()
        for attr in action.__slots__:
            assert getattr(action, attr, "err") != "err", f"got extra slot '{attr}'"
        assert len(mro_slots(action)) == len(set(mro_slots(action))), "duplicate slot"

    def test_de_json(self):
        action = ForumTopicReopened.de_json({}, None)
        assert action.api_kwargs == {}
        assert isinstance(action, ForumTopicReopened)

    def test_to_dict(self):
        action = ForumTopicReopened()
        action_dict = action.to_dict()
        assert action_dict == {}


@pytest.fixture(scope="module")
def topic_edited(emoji_id):
    return ForumTopicEdited(name=TEST_TOPIC_NAME, icon_custom_emoji_id=emoji_id)


class TestForumTopicEdited:
    def test_slot_behaviour(self, topic_edited):
        for attr in topic_edited.__slots__:
            assert getattr(topic_edited, attr, "err") != "err", f"got extra slot '{attr}'"
        assert len(mro_slots(topic_edited)) == len(set(mro_slots(topic_edited))), "duplicate slot"

    def test_expected_values(self, topic_edited, emoji_id):
        assert topic_edited.name == TEST_TOPIC_NAME
        assert topic_edited.icon_custom_emoji_id == emoji_id

    def test_de_json(self, bot, emoji_id):
        json_dict = {"name": TEST_TOPIC_NAME, "icon_custom_emoji_id": emoji_id}
        action = ForumTopicEdited.de_json(json_dict, bot)
        assert action.api_kwargs == {}

        assert action.name == TEST_TOPIC_NAME
        assert action.icon_custom_emoji_id == emoji_id
        # special test since it is mentioned in the docs that icon_custom_emoji_id can be an
        # empty string
        json_dict = {"icon_custom_emoji_id": ""}
        action = ForumTopicEdited.de_json(json_dict, bot)
        assert not action.icon_custom_emoji_id

    def test_to_dict(self, topic_edited, emoji_id):
        action_dict = topic_edited.to_dict()

        assert isinstance(action_dict, dict)
        assert action_dict["name"] == TEST_TOPIC_NAME
        assert action_dict["icon_custom_emoji_id"] == emoji_id

    def test_equality(self, emoji_id):
        a = ForumTopicEdited(name=TEST_TOPIC_NAME, icon_custom_emoji_id="")
        b = ForumTopicEdited(
            name=TEST_TOPIC_NAME,
            icon_custom_emoji_id="",
        )
        c = ForumTopicEdited(name=f"{TEST_TOPIC_NAME}!", icon_custom_emoji_id=emoji_id)
        d = ForumTopicEdited(icon_custom_emoji_id="")

        assert a == b
        assert hash(a) == hash(b)

        assert a != c
        assert hash(a) != hash(c)

        assert a != d
        assert hash(a) != hash(d)


class TestGeneralForumTopicHidden:
    def test_slot_behaviour(self):
        action = GeneralForumTopicHidden()
        for attr in action.__slots__:
            assert getattr(action, attr, "err") != "err", f"got extra slot '{attr}'"
        assert len(mro_slots(action)) == len(set(mro_slots(action))), "duplicate slot"

    def test_de_json(self):
        action = GeneralForumTopicHidden.de_json({}, None)
        assert action.api_kwargs == {}
        assert isinstance(action, GeneralForumTopicHidden)

    def test_to_dict(self):
        action = GeneralForumTopicHidden()
        action_dict = action.to_dict()
        assert action_dict == {}


class TestGeneralForumTopicUnhidden:
    def test_slot_behaviour(self):
        action = GeneralForumTopicUnhidden()
        for attr in action.__slots__:
            assert getattr(action, attr, "err") != "err", f"got extra slot '{attr}'"
        assert len(mro_slots(action)) == len(set(mro_slots(action))), "duplicate slot"

    def test_de_json(self):
        action = GeneralForumTopicUnhidden.de_json({}, None)
        assert action.api_kwargs == {}
        assert isinstance(action, GeneralForumTopicUnhidden)

    def test_to_dict(self):
        action = GeneralForumTopicUnhidden()
        action_dict = action.to_dict()
        assert action_dict == {}