Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Der-Henning
GitHub Repository: Der-Henning/tgtg
Path: blob/main/tests/test_notifiers.py
725 views
1
import json
2
import platform
3
from time import sleep
4
from unittest.mock import MagicMock
5
6
import pytest
7
import responses
8
from pytest_mock.plugin import MockerFixture
9
10
from tgtg_scanner.models import Config, Cron, Favorites, Item, Reservations
11
from tgtg_scanner.notifiers.apprise import Apprise
12
from tgtg_scanner.notifiers.console import Console
13
from tgtg_scanner.notifiers.discord import Discord
14
from tgtg_scanner.notifiers.ifttt import IFTTT
15
from tgtg_scanner.notifiers.ntfy import Ntfy
16
from tgtg_scanner.notifiers.script import Script
17
from tgtg_scanner.notifiers.smtp import SMTP
18
from tgtg_scanner.notifiers.telegram import Telegram
19
from tgtg_scanner.notifiers.webhook import WebHook
20
21
SYS_PLATFORM = platform.system()
22
IS_WINDOWS = SYS_PLATFORM.lower() in {"windows", "cygwin"}
23
24
25
@pytest.fixture
26
def reservations() -> Reservations:
27
return MagicMock()
28
29
30
@pytest.fixture
31
def favorites() -> Favorites:
32
return MagicMock()
33
34
35
@responses.activate
36
def test_webhook_json(test_item: Item, reservations: Reservations, favorites: Favorites):
37
config = Config()
38
config.webhook.enabled = True
39
config.webhook.method = "POST"
40
config.webhook.url = "https://api.example.com"
41
config.webhook.type = "application/json"
42
config.webhook.headers = {"Accept": "json"}
43
config.webhook.cron = Cron()
44
config.webhook.body = (
45
'{"content": "${{items_available}} panier(s) disponible(s) à ${{price}} € \nÀ récupérer ${{pickupdate}}'
46
'\nhttps://toogoodtogo.com/item/${{item_id}}", "username": "${{display_name}}"}'
47
)
48
responses.add(responses.POST, "https://api.example.com", status=200)
49
50
webhook = WebHook(config, reservations, favorites)
51
webhook.start()
52
webhook.send(test_item)
53
webhook.stop()
54
55
request = responses.calls[0].request
56
body = json.loads(request.body)
57
58
assert request.headers.get("Accept") == "json"
59
assert request.headers.get("Content-Type") == "application/json"
60
assert body == {
61
"content": (
62
f"{test_item.items_available} panier(s) disponible(s) à {test_item.price} € \nÀ récupérer {test_item.pickupdate}"
63
f"\nhttps://toogoodtogo.com/item/{test_item.item_id}"
64
),
65
"username": f"{test_item.display_name}",
66
}
67
68
69
@responses.activate
70
def test_webhook_text(test_item: Item, reservations: Reservations, favorites: Favorites):
71
config = Config()
72
config.webhook.enabled = True
73
config.webhook.method = "POST"
74
config.webhook.url = "https://api.example.com"
75
config.webhook.type = "text/plain"
76
config.webhook.headers = {"Accept": "json"}
77
config.webhook.cron = Cron()
78
config.webhook.body = (
79
"${{items_available}} panier(s) disponible(s) à ${{price}} € \n"
80
"À récupérer ${{pickupdate}}\nhttps://toogoodtogo.com/item/${{item_id}}"
81
)
82
responses.add(responses.POST, "https://api.example.com", status=200)
83
84
webhook = WebHook(config, reservations, favorites)
85
webhook.start()
86
webhook.send(test_item)
87
webhook.stop()
88
89
request = responses.calls[0].request
90
91
assert request.headers.get("Accept") == "json"
92
assert request.headers.get("Content-Type") == "text/plain"
93
assert request.body.decode("utf-8") == (
94
f"{test_item.items_available} panier(s) disponible(s) à {test_item.price} € \n"
95
f"À récupérer {test_item.pickupdate}\nhttps://toogoodtogo.com/item/{test_item.item_id}"
96
)
97
98
99
@responses.activate
100
def test_ifttt(test_item: Item, reservations: Reservations, favorites: Favorites):
101
config = Config()
102
config.ifttt.enabled = True
103
config.ifttt.event = "tgtg_notification"
104
config.ifttt.key = "secret_key"
105
config.ifttt.cron = Cron()
106
config.ifttt.body = (
107
'{"value1": "${{display_name}}", "value2": ${{items_available}}, '
108
'"value3": "https://share.toogoodtogo.com/item/${{item_id}}"}'
109
)
110
responses.add(
111
responses.POST,
112
f"https://maker.ifttt.com/trigger/{config.ifttt.event}/with/key/{config.ifttt.key}",
113
body="Congratulations! You've fired the tgtg_notification event",
114
content_type="text/plain",
115
status=200,
116
)
117
118
ifttt = IFTTT(config, reservations, favorites)
119
ifttt.start()
120
ifttt.send(test_item)
121
ifttt.stop()
122
123
request = responses.calls[0].request
124
body = json.loads(request.body)
125
126
assert request.headers.get("Content-Type") == "application/json"
127
assert body == {
128
"value1": test_item.display_name,
129
"value2": test_item.items_available,
130
"value3": f"https://share.toogoodtogo.com/item/{test_item.item_id}",
131
}
132
133
134
@responses.activate
135
def test_ntfy(test_item: Item, reservations: Reservations, favorites: Favorites):
136
config = Config()
137
config.ntfy.enabled = True
138
config.ntfy.server = "https://ntfy.sh"
139
config.ntfy.topic = "tgtg_test"
140
config.ntfy.title = "New Items - ${{display_name}}"
141
config.ntfy.cron = Cron()
142
config.ntfy.body = "${{display_name}} - New Amount: ${{items_available}} - https://share.toogoodtogo.com/item/${{item_id}}"
143
responses.add(
144
responses.POST,
145
f"{config.ntfy.server}/{config.ntfy.topic}",
146
status=200,
147
)
148
149
ntfy = Ntfy(config, reservations, favorites)
150
ntfy.start()
151
ntfy.send(test_item)
152
ntfy.stop()
153
154
request = responses.calls[0].request
155
156
assert request.url == (f"{config.ntfy.server}/" f"{config.ntfy.topic}")
157
assert request.headers.get("X-Message").decode("utf-8") == (
158
f"{test_item.display_name} - New Amount: {test_item.items_available} - "
159
f"https://share.toogoodtogo.com/item/{test_item.item_id}"
160
)
161
assert request.headers.get("X-Title").decode("utf-8") == (f"New Items - {test_item.display_name}")
162
163
164
@responses.activate
165
def test_apprise(test_item: Item, reservations: Reservations, favorites: Favorites):
166
config = Config()
167
config.apprise.enabled = True
168
config.apprise.url = "ntfy://tgtg_test"
169
config.apprise.title = "New Items - ${{display_name}}"
170
config.apprise.cron = Cron()
171
config.apprise.body = "${{display_name}} - New Amount: ${{items_available}} - https://share.toogoodtogo.com/item/${{item_id}}"
172
responses.add(responses.POST, "https://ntfy.sh/", status=200)
173
174
apprise = Apprise(config, reservations, favorites)
175
apprise.start()
176
apprise.send(test_item)
177
apprise.stop()
178
179
request = responses.calls[0].request
180
body = json.loads(request.body)
181
182
assert request.url == "https://ntfy.sh/"
183
assert body.get("topic") == "tgtg_test"
184
assert body.get("message") == (
185
f"{test_item.display_name} - New Amount: {test_item.items_available} - "
186
f"https://share.toogoodtogo.com/item/{test_item.item_id}"
187
)
188
189
190
def test_console(
191
test_item: Item,
192
reservations: Reservations,
193
favorites: Favorites,
194
capsys: pytest.CaptureFixture,
195
):
196
config = Config()
197
config.console.enabled = True
198
config.console.cron = Cron()
199
config.console.body = "${{display_name}} - new amount: ${{items_available}}"
200
201
console = Console(config, reservations, favorites)
202
console.start()
203
console.send(test_item)
204
sleep(0.5)
205
captured = capsys.readouterr()
206
console.stop()
207
208
assert captured.out.rstrip() == f"{test_item.display_name} - new amount: {test_item.items_available}"
209
210
211
def test_script(
212
test_item: Item,
213
reservations: Reservations,
214
favorites: Favorites,
215
capfdbinary: pytest.CaptureFixture,
216
):
217
config = Config()
218
config.script.enabled = True
219
config.script.cron = Cron()
220
config.script.command = "echo ${{display_name}}"
221
222
script = Script(config, reservations, favorites)
223
script.start()
224
script.send(test_item)
225
sleep(0.5)
226
captured = capfdbinary.readouterr()
227
script.stop()
228
229
encoding = "cp1252" if IS_WINDOWS else "utf-8"
230
assert captured.out.decode(encoding).rstrip() == test_item.display_name
231
232
233
def test_smtp(test_item: Item, reservations: Reservations, favorites: Favorites, mocker: MockerFixture):
234
mock_SMTP = mocker.MagicMock(name="tgtg_scanner.notifiers.smtp.smtplib.SMTP")
235
mocker.patch("tgtg_scanner.notifiers.smtp.smtplib.SMTP", new=mock_SMTP)
236
mock_SMTP.return_value.noop.return_value = (250, "OK")
237
238
config = Config()
239
config.smtp.enabled = True
240
config.smtp.cron = Cron()
241
config.smtp.host = "localhost"
242
config.smtp.port = 25
243
config.smtp.use_tls = False
244
config.smtp.use_ssl = False
245
config.smtp.sender = "[email protected]"
246
config.smtp.recipients = ["[email protected]"]
247
config.smtp.subject = "New Magic Bags"
248
config.smtp.body = "<b>Á ê</b> </br>Amount: ${{items_available}}"
249
250
smtp = SMTP(config, reservations, favorites)
251
smtp.start()
252
smtp.send(test_item)
253
smtp.stop()
254
255
assert mock_SMTP.call_count == 1
256
assert mock_SMTP.return_value.sendmail.call_count == 1
257
call_args = mock_SMTP.return_value.sendmail.call_args_list[0][0]
258
assert call_args[0] == "[email protected]"
259
assert call_args[1] == ["[email protected]"]
260
body = call_args[2].split("\n")
261
assert body[0].startswith("Content-Type: multipart/alternative;")
262
assert body[2] == "From: [email protected]"
263
assert body[3] == "To: [email protected]"
264
assert body[4] == "Subject: New Magic Bags"
265
assert body[8] == 'Content-Type: text/html; charset="utf-8"'
266
assert body[12] == f"<b>=C3=81 =C3=AA</b> </br>Amount: {test_item.items_available}"
267
268
269
@pytest.fixture
270
def mocked_telegram(mocker: MockerFixture):
271
mocker.patch(
272
"telegram.Bot.get_me",
273
return_value=MagicMock(username="test_bot"),
274
)
275
mocker.patch(
276
"telegram.ext.Application.initialize",
277
return_value=None,
278
)
279
mocker.patch(
280
"telegram.ext.Updater.start_polling",
281
return_value=None,
282
)
283
mocker.patch(
284
"telegram.ext.ExtBot.set_my_commands",
285
return_value=True,
286
)
287
mocker.patch(
288
"telegram.ext.Application.start",
289
return_value=None,
290
)
291
mocker.patch(
292
"telegram.ext.Updater.stop",
293
return_value=None,
294
)
295
mocker.patch(
296
"telegram.ext.Application.stop",
297
return_value=None,
298
)
299
mocker.patch(
300
"telegram.ext.Application.shutdown",
301
return_value=None,
302
)
303
mocker.patch(
304
"telegram.Bot.send_message",
305
return_value=None,
306
)
307
return mocker
308
309
310
def test_telegram(test_item: Item, reservations: Reservations, favorites: Favorites, mocked_telegram):
311
config = Config()
312
config.telegram.enabled = True
313
config.telegram.token = "1234567890:ABCDEF"
314
config.telegram.cron = Cron()
315
config.telegram.chat_ids = ["123456"]
316
config.telegram.disable_commands = False
317
config.telegram.body = "New Magic Bags: ${{items_available}}"
318
config.telegram.image = None
319
320
telegram = Telegram(config, reservations, favorites)
321
telegram.start()
322
telegram.send(test_item)
323
sleep(0.5)
324
assert telegram.thread.is_alive()
325
telegram.stop()
326
assert not telegram.thread.is_alive()
327
328
329
@pytest.fixture
330
def mocked_discord(mocker: MockerFixture):
331
mocker.patch(
332
"discord.ext.commands.Bot.login",
333
return_value=None,
334
)
335
mocker.patch(
336
"discord.ext.commands.Bot.start",
337
return_value=None,
338
)
339
mocker.patch(
340
"discord.ext.commands.Bot.command",
341
return_value=MagicMock(),
342
)
343
mocker.patch(
344
"discord.ext.commands.Bot.event",
345
return_value=MagicMock(),
346
)
347
mocker.patch(
348
"discord.ext.commands.Bot.dispatch",
349
return_value=None,
350
)
351
mocker.patch(
352
"aiohttp.BaseConnector.close",
353
return_value=None,
354
)
355
return mocker
356
357
358
def test_discord(test_item: Item, reservations: Reservations, favorites: Favorites, mocked_discord):
359
config = Config()
360
config.discord.enabled = True
361
config.discord.channel = 123456789012345678
362
config.discord.token = "ABCDEFGHIJKLMNOPQRSTUVWXYZ.123456.ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKL"
363
364
discord = Discord(config, reservations, favorites)
365
discord.start()
366
discord.send(test_item)
367
sleep(0.5)
368
discord.stop()
369
370