feat: add test workflow and implement unit tests for main functionality
This commit is contained in:
39
.gitea/workflows/test-application.yaml
Normal file
39
.gitea/workflows/test-application.yaml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
name: Test Application
|
||||||
|
run-name: ${{ gitea.actor }} tests the application
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check out repository code
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: "3.12"
|
||||||
|
|
||||||
|
- name: Create and activate virtual environment
|
||||||
|
run: |
|
||||||
|
python -m venv venv
|
||||||
|
source venv/bin/activate
|
||||||
|
pip install -r requirements.txt
|
||||||
|
pip install pytest
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: |
|
||||||
|
source venv/bin/activate
|
||||||
|
pytest --maxfail=1 --disable-warnings -q
|
||||||
|
|
||||||
|
- name: Deactivate virtual environment
|
||||||
|
run: deactivate
|
||||||
|
|
||||||
|
- name: Clean up virtual environment
|
||||||
|
run: rm -rf venv
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
pytest
|
||||||
python-dotenv
|
python-dotenv
|
||||||
pytz
|
pytz
|
||||||
requests
|
requests
|
||||||
|
|||||||
111
test_main.py
Normal file
111
test_main.py
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
import io
|
||||||
|
import time
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
import main
|
||||||
|
|
||||||
|
|
||||||
|
SAMPLE_TIMEZONE_CSV = """Etc/UTC,ZZ,UTC,0,0,0
|
||||||
|
America/New_York,US,EST,0,-18000,0
|
||||||
|
Europe/London,GB,BST,0,0,1
|
||||||
|
"""
|
||||||
|
|
||||||
|
SAMPLE_COUNTRY_CSV = """ZZ,Unknown
|
||||||
|
US,United States
|
||||||
|
GB,United Kingdom
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_timezones_and_countries(monkeypatch):
|
||||||
|
tzs = main.load_timezones()
|
||||||
|
countries = main.load_countries()
|
||||||
|
assert any(t['zone_name'] == 'America/New_York' for t in tzs)
|
||||||
|
assert any(c['country_code'] == 'US' for c in countries)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_tz_and_country_info():
|
||||||
|
timezones = [{'zone_name': 'A/B', 'country_code': 'US'}]
|
||||||
|
countries = [{'country_code': 'US', 'country_name': 'United States'}]
|
||||||
|
assert main.get_tz_info('A/B', timezones)['zone_name'] == 'A/B'
|
||||||
|
assert main.get_country_info('US', countries)[
|
||||||
|
'country_name'] == 'United States'
|
||||||
|
assert main.get_tz_info('X/Y', timezones) is None
|
||||||
|
assert main.get_country_info('XX', countries) is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_embed_all_types(monkeypatch):
|
||||||
|
# Prevent create_embed from trying to read actual CSV files by patching loaders
|
||||||
|
monkeypatch.setattr(main, 'load_timezones', lambda: [
|
||||||
|
{'zone_name': 'Etc/UTC', 'country_code': 'ZZ'}])
|
||||||
|
monkeypatch.setattr(main, 'load_countries', lambda: [
|
||||||
|
{'country_code': 'ZZ', 'country_name': 'Nowhere'}])
|
||||||
|
|
||||||
|
# reminder
|
||||||
|
emb = main.create_embed('reminder')
|
||||||
|
assert emb['title'] == 'Reminder'
|
||||||
|
assert '5 minute' in emb['description']
|
||||||
|
assert emb['color'] == 0xe67e22
|
||||||
|
|
||||||
|
# reminder_halftime
|
||||||
|
emb = main.create_embed('reminder_halftime')
|
||||||
|
assert emb['title'] == 'Reminder halftime'
|
||||||
|
assert 'Half-time in 5 minutes' in emb['description']
|
||||||
|
|
||||||
|
# halftime (should include image)
|
||||||
|
monkeypatch.setattr(main, 'where_is_it_420', lambda tzs, cs: [])
|
||||||
|
emb = main.create_embed('halftime')
|
||||||
|
assert emb['title'] == 'Halftime'
|
||||||
|
assert emb['image'] is not None
|
||||||
|
|
||||||
|
# 420 (should include image and appended tz info string when list empty)
|
||||||
|
monkeypatch.setattr(main, 'where_is_it_420', lambda tzs, cs: [])
|
||||||
|
emb = main.create_embed('420')
|
||||||
|
assert emb['title'] == '420'
|
||||||
|
assert emb['image'] is not None
|
||||||
|
assert "not 4:20" in emb['description'] or "It's 4:20" in emb['description']
|
||||||
|
|
||||||
|
# unknown
|
||||||
|
emb = main.create_embed('nope')
|
||||||
|
assert emb['description'] == 'Unknown notification type'
|
||||||
|
|
||||||
|
|
||||||
|
def test_where_is_it_420(monkeypatch):
|
||||||
|
# Limit timezones to a predictable set
|
||||||
|
monkeypatch.setattr(main.pytz, 'all_timezones', ['Etc/UTC'])
|
||||||
|
|
||||||
|
tzs = [{'zone_name': 'Etc/UTC', 'country_code': 'ZZ'}]
|
||||||
|
countries = [{'country_code': 'ZZ', 'country_name': 'Nowhere'}]
|
||||||
|
|
||||||
|
class FakeDatetime:
|
||||||
|
@staticmethod
|
||||||
|
def now(tz):
|
||||||
|
class R:
|
||||||
|
hour = 4
|
||||||
|
return R()
|
||||||
|
|
||||||
|
monkeypatch.setattr(main, 'datetime', FakeDatetime)
|
||||||
|
monkeypatch.setattr(main, 'get_tz_info', lambda name,
|
||||||
|
t: tzs[0] if name == 'Etc/UTC' else None)
|
||||||
|
monkeypatch.setattr(main, 'get_country_info', lambda code,
|
||||||
|
c: countries[0] if code == 'ZZ' else None)
|
||||||
|
|
||||||
|
res = main.where_is_it_420(tzs, countries)
|
||||||
|
assert res == ['Nowhere']
|
||||||
|
|
||||||
|
|
||||||
|
def test_main_exits_quickly(monkeypatch):
|
||||||
|
# Patch send_notification so it doesn't perform network
|
||||||
|
monkeypatch.setattr(main, 'send_notification', lambda x: None)
|
||||||
|
# Make schedule.run_pending raise KeyboardInterrupt to exit loop
|
||||||
|
monkeypatch.setattr(main.schedule, 'run_pending', lambda: (
|
||||||
|
_ for _ in ()).throw(KeyboardInterrupt()))
|
||||||
|
# Patch time.sleep to no-op
|
||||||
|
monkeypatch.setattr(main.time, 'sleep', lambda s: None)
|
||||||
|
# Ensure WEBHOOK_URL present to avoid early return
|
||||||
|
monkeypatch.setenv('DISCORD_WEBHOOK_URL', 'http://example.com/webhook')
|
||||||
|
main.WEBHOOK_URL = 'http://example.com/webhook'
|
||||||
|
|
||||||
|
# Should exit quickly due to KeyboardInterrupt from run_pending
|
||||||
|
main.main()
|
||||||
Reference in New Issue
Block a user