File size: 9,113 Bytes
c53c2ab
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""Taking a channel out of the bot: the bot asks whether the channel's saved posts (its data in My posts) are deleted as
well - Yes removes the channel and the data, No removes only the channel and keeps the data."""
import asyncio

from app.db import utcnow
from app.sync_engine import sync_channel

from .harness import App, norm
from .test_sync import old_msg, opts, world


def run(coro):
    return asyncio.run(coro)


async def make_app(tmp_path, **cfg):
    """Two channels: "Anime Channel" (id 1) has three published posts and a draft, "Other Channel" (id 2) has two posts;
    channel 1 also has what the sync keeps (where it stopped, a post it must forget) and a timed deletion."""
    app = App(tmp_path, edit_delay=0, **cfg)
    await app.start()
    await app.db.save_channel(1, 5, "Anime Channel", "animech", 1)
    await app.db.save_channel(2, 6, "Other Channel", "otherch", 1)
    for cid, n in ((1, 3), (2, 2)):
        for i in range(1, n + 1):
            await app.db.create_post(
                channel_id=cid, message_id=i, status="sent", source="adopted", text=f"post {cid}/{i}", entities=[], buttons=[]
            )
    await app.db.create_post(channel_id=1, status="draft", source="bot", text="a draft", entities=[], buttons=[])
    for cid in (1, 2):
        await app.db.save_sync(cid, last_top=3, last_pts=3)
        await app.db.start_history(cid, 3)
        await app.db.ignore_post(cid, 9)
        app.ctx.sync_ignore.add((cid, 9))
    await app.db.schedule_deletes(
        [dict(channel_id=1, message_id=2, delete_at=utcnow(), group_id="g", created_by=1)]
    )
    return app


async def tap_channel(app, label_part="1"):
    await app.text("/channels")
    await app.press(app.out.callback_exact(label_part))


# ============================================================================================ the question
def test_the_owner_is_asked_whether_the_saved_posts_go_too(tmp_path):
    async def go():
        app = await make_app(tmp_path)
        await tap_channel(app)
        text = norm(app.out.last_text)
        assert "Take Anime Channel out of the bot" in text
        assert "Do you also want to delete its saved posts" in text and "4 post(s)" in text  # three published + a draft
        assert "Nothing is deleted in the channel itself" in text
        assert app.out.last_buttons() == [["πŸ—‘ Yes, delete them too", "πŸ“¦ No, keep them"], ["πŸ”™ Back"]]
        # the question alone changes nothing
        assert [c.id for c in await app.db.list_channels()] == [1, 2] and await app.db.count_channel_posts(1) == 4
        await app.press(app.out.callback_exact("πŸ”™ Back"))
        assert "Your channels" in norm(app.out.last_text)
        await app.db.close()

    run(go())


def test_a_channel_without_saved_posts_has_nothing_to_ask(tmp_path):
    async def go():
        app = await make_app(tmp_path)
        await app.db.save_channel(3, 7, "Empty Channel", "emptych", 1)
        await app.text("/channels")
        await app.press(app.out.callback_exact("2"))  # Anime, Empty, Other: the second one
        text = norm(app.out.last_text)
        assert "Take Empty Channel out of the bot?" in text and "no saved posts" in text
        assert app.out.last_buttons() == [["βœ… Yes, remove it", "πŸ”™ Back"]]
        await app.press(app.out.callback_exact("βœ… Yes, remove it"))
        assert [c.id for c in await app.db.list_channels()] == [1, 2]
        await app.db.close()

    run(go())


# ================================================================================================ yes
def test_yes_removes_the_channel_and_deletes_its_saved_posts(tmp_path):
    async def go():
        app = await make_app(tmp_path)
        await tap_channel(app)
        await app.press(app.out.callback_data("Yes, delete them too"))
        text = norm(app.out.last_text)
        assert "Anime Channel was removed from the bot" in text and "4 saved post(s) were deleted from My posts" in text
        assert "Nothing was deleted in the channel" in text
        assert [c.id for c in await app.db.list_channels()] == [2]
        row = await app.db.get_channel(1)
        assert row is not None and not row.active  # switched off, not erased: a repost or shift that names it still finds it
        assert await app.db.count_channel_posts(1) == 0 and await app.db.sent_posts(1) == []
        _, total = await app.db.list_posts(1, "draft", 0, 10)
        assert total == 0
        # what the sync kept about the channel goes too, so it starts from scratch when it is added again
        assert await app.db.get_sync(1) is None and await app.db.get_history(1) is None
        assert await app.db.ignored_ids(1) == set() and not any(k[0] == 1 for k in app.ctx.sync_ignore)
        # ... and nothing of the other channel is touched
        assert await app.db.count_channel_posts(2) == 2
        assert await app.db.get_sync(2) is not None and await app.db.get_history(2) is not None
        assert await app.db.ignored_ids(2) == {9} and (2, 9) in app.ctx.sync_ignore
        # a timed deletion is a promise about a message that is still in the channel: it stays
        assert [r.message_id for r in await app.db.pending_deletes()] == [2]
        await app.db.close()

    run(go())


def test_nothing_is_deleted_in_telegram_when_the_data_goes(tmp_path):
    async def go():
        app = await make_app(tmp_path)
        before = len(app.tg.requests)
        await tap_channel(app)
        await app.press(app.out.callback_data("Yes, delete them too"))
        assert app.tg.deleted == [] and len(app.tg.requests) == before
        await app.db.close()

    run(go())


def test_the_posts_are_not_deleted_while_a_look_at_the_channels_is_running(tmp_path):
    async def go():
        app = await make_app(tmp_path)
        lock = app.ctx.syncer.exclusive()
        await lock.acquire()  # a look at the channels is in progress: it could save posts again
        task = asyncio.create_task(app.press("chy:1:1"))
        await asyncio.sleep(0.2)
        assert not task.done() and await app.db.count_channel_posts(1) == 4
        lock.release()
        await task
        assert await app.db.count_channel_posts(1) == 0
        await app.db.close()

    run(go())


# =================================================================================================== no
def test_no_removes_only_the_channel_and_keeps_its_saved_posts(tmp_path):
    async def go():
        app = await make_app(tmp_path)
        await tap_channel(app)
        await app.press(app.out.callback_data("No, keep them"))
        text = norm(app.out.last_text)
        assert "Anime Channel was removed from the bot" in text and "saved posts are kept" in text
        assert [c.id for c in await app.db.list_channels()] == [2]
        assert await app.db.count_channel_posts(1) == 4 and await app.db.get_sync(1) is not None
        assert await app.db.ignored_ids(1) == {9} and (1, 9) in app.ctx.sync_ignore
        # added again: everything is there again
        await app.db.save_channel(1, 5, "Anime Channel", "animech", 1)
        assert [c.id for c in await app.db.list_channels()] == [1, 2] and await app.db.count_channel_posts(1) == 4
        await app.db.close()

    run(go())


def test_a_button_from_the_old_question_still_removes_only_the_channel(tmp_path):
    async def go():
        app = await make_app(tmp_path)
        await app.press("chy:1")  # what the earlier version of the bot put on its button
        assert [c.id for c in await app.db.list_channels()] == [2]
        assert await app.db.count_channel_posts(1) == 4
        assert "saved posts are kept" in norm(app.out.last_text)
        await app.db.close()

    run(go())


# ============================================================================================ who may do it
def test_only_an_owner_can_remove_a_channel_or_delete_its_data(tmp_path):
    async def go():
        app = await make_app(tmp_path, owner=7, admins=frozenset({1}))
        app.uid = 1  # an admin, not an owner
        for data in ("chd:1", "chy:1:1", "chy:1:0", "chy:1"):
            await app.press(data)
        assert [c.id for c in await app.db.list_channels()] == [1, 2] and await app.db.count_channel_posts(1) == 4
        await app.db.close()

    run(go())


# ========================================================================================= coming back
def test_a_channel_that_comes_back_after_its_data_was_deleted_is_read_in_again(tmp_path):
    async def go():
        chan, db, ch = await world(tmp_path, {i: old_msg(i, f"older post {i}") for i in range(1, 6)}, save=False)
        rep = await sync_channel(chan, db, ch, opts())
        assert rep.first_look and rep.imported == 5
        assert await db.delete_channel_data(1) == 5
        assert await db.sent_posts(1) == [] and await db.get_sync(1) is None and await db.get_history(1) is None
        rep = await sync_channel(chan, db, ch, opts())  # as if it had never been looked at
        assert rep.first_look and rep.imported == 5 and rep.history_done
        assert len(await db.sent_posts(1)) == 5
        await db.close()

    run(go())