Skip to content

Commit 8468697

Browse files
feat(webapp): monitor degraded status (#376)
1 parent ba4c842 commit 8468697

File tree

5 files changed

+99
-6
lines changed

5 files changed

+99
-6
lines changed

src/common/webapp.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from werkzeug.middleware.proxy_fix import ProxyFix
1313

1414
# local imports
15-
from src.common.common import app_dir, colors
15+
from src.common.common import app_dir, colors, version
1616
from src.common import crypto
1717
from src.common import globals
1818
from src.common import time
@@ -77,7 +77,20 @@ def html_to_md(html: str) -> str:
7777

7878
@app.route('/status')
7979
def status():
80-
return "LizardByte-bot is live!"
80+
degraded_checks = [
81+
getattr(globals.DISCORD_BOT, 'DEGRADED', True),
82+
getattr(globals.REDDIT_BOT, 'DEGRADED', True),
83+
]
84+
85+
s = 'ok'
86+
if any(degraded_checks):
87+
s = 'degraded'
88+
89+
result = {
90+
"status": s,
91+
"version": version,
92+
}
93+
return jsonify(result)
8194

8295

8396
@app.route("/favicon.ico")

src/discord/bot.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ def __init__(self, *args, **kwargs):
3232
kwargs['auto_sync_commands'] = True
3333
super().__init__(*args, **kwargs)
3434

35+
self.DEGRADED = False
36+
3537
self.bot_thread = threading.Thread(target=lambda: None)
3638
self.token = os.environ['DISCORD_BOT_TOKEN']
3739
self.db = Database(db_path=os.path.join(data_dir, 'discord_bot_database'))
@@ -122,7 +124,12 @@ async def async_send_message(
122124
embed.description = embed.description[:-cut_length] + "..."
123125

124126
channel = await self.fetch_channel(channel_id)
125-
return await channel.send(content=message, embed=embed)
127+
128+
try:
129+
return await channel.send(content=message, embed=embed)
130+
except Exception as e:
131+
print(f"Error sending message: {e}")
132+
self.DEGRADED = True
126133

127134
def send_message(
128135
self,
@@ -273,10 +280,12 @@ def start_threaded(self):
273280
self.bot_thread.start()
274281
except KeyboardInterrupt:
275282
print("Keyboard Interrupt Detected")
283+
self.DEGRADED = True
276284
self.stop()
277285

278286
def stop(self, future: asyncio.Future = None):
279287
print("Attempting to stop tasks")
288+
self.DEGRADED = True
280289
self.daily_task.stop()
281290
self.role_update_task.stop()
282291
self.clean_ephemeral_cache.stop()

src/reddit/bot.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
class Bot:
2020
def __init__(self, **kwargs):
2121
self.STOP_SIGNAL = False
22+
self.DEGRADED = False
2223

2324
# threads
2425
self.bot_thread = threading.Thread(target=lambda: None)
@@ -57,8 +58,7 @@ def __init__(self, **kwargs):
5758
self.migrate_shelve()
5859
self.migrate_last_online()
5960

60-
@staticmethod
61-
def validate_env() -> bool:
61+
def validate_env(self) -> bool:
6262
required_env = [
6363
'DISCORD_REDDIT_CHANNEL_ID',
6464
'PRAW_CLIENT_ID',
@@ -69,6 +69,7 @@ def validate_env() -> bool:
6969
for env in required_env:
7070
if env not in os.environ:
7171
sys.stderr.write(f"Environment variable ``{env}`` must be defined\n")
72+
self.DEGRADED = True
7273
return False
7374
return True
7475

@@ -165,6 +166,7 @@ def discord(self, submission: models.Submission):
165166
try:
166167
redditor = self.reddit.redditor(name=submission.author)
167168
except Exception:
169+
self.DEGRADED = True
168170
return
169171

170172
# create the discord embed
@@ -266,11 +268,13 @@ def start_threaded(self):
266268
self.bot_thread.start()
267269
except KeyboardInterrupt:
268270
print("Keyboard Interrupt Detected")
271+
self.DEGRADED = True
269272
self.stop()
270273

271274
def stop(self):
272275
print("Attempting to stop reddit bot")
273276
self.STOP_SIGNAL = True
277+
self.DEGRADED = True
274278
if self.bot_thread is not None and self.bot_thread.is_alive():
275279
self.comment_thread.join()
276280
self.submission_thread.join()

tests/unit/common/test_common.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# standard imports
2+
import os
3+
import re
4+
5+
# lib imports
6+
import pytest
7+
8+
# local imports
9+
from src.common import common
10+
11+
12+
@pytest.fixture(scope='module')
13+
def github_email():
14+
15+
16+
17+
def test_colors():
18+
for color in common.colors.values():
19+
assert 0x000000 <= color <= 0xFFFFFF, f"{color} is not a valid hex color"
20+
21+
22+
def test_get_bot_avatar(github_email):
23+
url = common.get_bot_avatar(gravatar=github_email)
24+
print(url)
25+
assert url.startswith('https://www.gravatar.com/avatar/')
26+
27+
28+
def test_get_avatar_bytes(github_email, mocker):
29+
mocker.patch('src.common.common.avatar', common.get_bot_avatar(gravatar=github_email))
30+
avatar_bytes = common.get_avatar_bytes()
31+
assert avatar_bytes
32+
assert isinstance(avatar_bytes, bytes)
33+
34+
35+
def test_get_app_dirs():
36+
app_dir, data_dir = common.get_app_dirs()
37+
assert app_dir
38+
assert data_dir
39+
assert os.path.exists(app_dir)
40+
assert os.path.exists(data_dir)
41+
assert os.path.isdir(app_dir)
42+
assert os.path.isdir(data_dir)
43+
assert app_dir == (os.getcwd() or '/app')
44+
assert data_dir == (os.path.join(os.getcwd(), 'data') or '/data')
45+
46+
47+
def test_version():
48+
assert common.version
49+
assert isinstance(common.version, str)
50+
assert re.match(r'^\d+\.\d+\.\d+$', common.version)

tests/unit/common/test_webapp.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,31 @@ def test_client():
2424
yield test_client # this is where the testing happens!
2525

2626

27-
def test_status(test_client):
27+
@pytest.mark.parametrize("degraded", [
28+
False,
29+
True,
30+
])
31+
def test_status(test_client, discord_bot, degraded, mocker):
2832
"""
2933
WHEN the '/status' page is requested (GET)
3034
THEN check that the response is valid
3135
"""
36+
# patch reddit bot, since we're not using its fixture
37+
mocker.patch('src.common.globals.REDDIT_BOT', Mock(DEGRADED=False))
38+
39+
if degraded:
40+
mocker.patch('src.common.globals.DISCORD_BOT.DEGRADED', True)
41+
3242
response = test_client.get('/status')
3343
assert response.status_code == 200
3444

45+
if not degraded:
46+
assert response.json['status'] == 'ok'
47+
else:
48+
assert response.json['status'] == 'degraded'
49+
50+
assert response.json['version']
51+
3552

3653
def test_favicon(test_client):
3754
"""

0 commit comments

Comments
 (0)