diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..87e359e --- /dev/null +++ b/.coveragerc @@ -0,0 +1,8 @@ +[run] + source = + cogs/ + the_mines/ + utils/ + +[report] + fail_under = 65.0 diff --git a/.github/workflows/pythonappci.yml b/.github/workflows/pythonappci.yml index 9233bcf..e1dcd5d 100644 --- a/.github/workflows/pythonappci.yml +++ b/.github/workflows/pythonappci.yml @@ -19,4 +19,4 @@ jobs: pip install -r requirements.txt -r test_requirements.txt - name: Test with pytest run: | - pytest -v tests/ + pytest -m "not online" -v --cov tests/ diff --git a/.gitignore b/.gitignore index 90c18d5..c838fe8 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ tests/__pycache__ /the_mines/process/fussballdaten/matchday30.html /the_mines/process/fussballdaten/test.html /the_mines/process/fussballdaten/test.py +.coverage diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..825b8e7 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +markers = + online: marks tests that use online resources (deselect with '-m "not online"') diff --git a/test_requirements.txt b/test_requirements.txt index 6e7b4d3..cc475ac 100644 --- a/test_requirements.txt +++ b/test_requirements.txt @@ -1 +1,2 @@ pytest==5.3.5 +pytest-cov==2.10.0 diff --git a/tests/test_dummy_data.py b/tests/test_dummy_data.py index 36a22cc..043ee07 100644 --- a/tests/test_dummy_data.py +++ b/tests/test_dummy_data.py @@ -6,7 +6,7 @@ def test_get_dummy_data(): for result in get_dummy_data(): result_count += 1 if isinstance(result, int): - assert result > 0 + assert result >= 0 elif isinstance(result, str): assert len(result) == 2 diff --git a/tests/test_misc.py b/tests/test_misc.py new file mode 100644 index 0000000..4ebc83c --- /dev/null +++ b/tests/test_misc.py @@ -0,0 +1,62 @@ +import pytest +import re +from utils.misc import ( + umlaut, + unumlaut, + format_date, + open_default_html, + get_default_matchday, + get_default_season, +) + + +@pytest.mark.parametrize( + ("word", "expected"), + [("f\\xc3\\xb6\\xc3\\xb6", "föö"), ("bar", "bar"), ("f\\xc3\\xbctbol", "fütbol"),], +) +def test_umlaut(word, expected): + assert umlaut(word) == expected + + +@pytest.mark.parametrize( + ("word", "expected"), + [ + ("föö", "foo"), + ("bär", "bar"), + ("fütbol", "futbol"), + ("áēïōü", "aeiou"), + ("test", "test"), + ], +) +def test_unumlaut(word, expected): + assert unumlaut(word) == expected + + +@pytest.mark.parametrize( + ("date", "expected"), + [("10.10.2010", "October 10, 2010"), ("1.2.2012", "February 1, 2012"),], +) +def test_format_date(date, expected): + assert format_date(date) == expected + + +@pytest.mark.online +@pytest.mark.parametrize( + ("pattern"), + [("Die aktuelle Bundesliga 20\d\d/20\d\d - Der \d*\. Spieltag - Fussballdaten")], +) +def test_open_default_html(pattern): + assert re.match(pattern, open_default_html()) + + +@pytest.mark.online +@pytest.mark.parametrize(("earliest", "latest"), [(1, 34)]) +def test_get_default_matchday(earliest, latest): + result = int(get_default_matchday()) + assert result >= earliest and result <= latest + + +@pytest.mark.online +@pytest.mark.parametrize(("pattern"), [("20\d\d")]) +def test_get_default_season(pattern): + assert re.match(pattern, get_default_season()) diff --git a/tests/test_process_blurb.py b/tests/test_process_blurb.py new file mode 100644 index 0000000..4c7f1bf --- /dev/null +++ b/tests/test_process_blurb.py @@ -0,0 +1,125 @@ +import pytest + +from the_mines.process.fussballdaten.process_blurb import ( + build_matchup, + get_team_str, + get_glance_schedule, + get_glance_table_stats, + get_blurb, +) + + +@pytest.mark.parametrize( + ("title", "score", "expected"), + [ + ( + "Blah: Bayern gegen Dortmund (1.1.2001, DFB-Pokal)", + "2:2", + {"January 1, 2001 (DFB-Pokal)": "Bayern 2:2 Dortmund"}, + ), + ( + "Blah: Bayern gegen Dortmund (2.2.2002, Bundesliga)", + None, + {"February 2, 2002 (Bundesliga)": "Bayern vs Dortmund"}, + ), + ], +) +def test_build_matchup(title, score, expected): + assert build_matchup(title, score) == expected + + +@pytest.mark.parametrize( + ("target", "expected"), + [ + ("bayern", "fc-bayern-muenchen"), + ("leipzig", "rb-leipzig"), + ("mainz", "1-fsv-mainz-05"), + ], +) +def test_get_team_str(target, expected): + assert get_team_str(target) == expected + + +@pytest.mark.online +@pytest.mark.parametrize( + ("team", "season", "expected"), + [ + ( + "bayern", + "2020", + { + "June 20, 2020 (Bundesliga)": "Bayern 3:1 Freiburg", + "June 27, 2020 (Bundesliga)": "Wolfsburg 0:4 Bayern", + }, + ), + ( + "dortmund", + "2020", + { + "June 20, 2020 (Bundesliga)": "RB Leipzig 0:2 Dortmund", + "June 27, 2020 (Bundesliga)": "Dortmund 0:4 Hoffenheim", + }, + ), + ], +) +def test_get_glance_schedule(team, season, expected): + assert get_glance_schedule(team, season) == expected + + +@pytest.mark.online +@pytest.mark.parametrize( + ("team", "season", "expected"), + [ + ( + "bayern", + "2020", + {"title": "Bayern", "fields": {"Pos": "1", "W-T-L": "26-4-4", "Pts": "82"}}, + ), + ( + "dortmund", + "2020", + { + "title": "Dortmund", + "fields": {"Pos": "2", "W-T-L": "21-6-7", "Pts": "69"}, + }, + ), + ], +) +def test_get_glance_table_stats(team, season, expected): + assert get_glance_table_stats(team, season) == expected + + +@pytest.mark.online +@pytest.mark.parametrize( + ("team", "expected"), + [ + ( + "bayern", + { + "title": "Bayern", + "fields": { + "Pos": "1", + "W-T-L": "26-4-4", + "Pts": "82", + "June 20, 2020 (Bundesliga)": "Bayern 3:1 Freiburg", + "June 27, 2020 (Bundesliga)": "Wolfsburg 0:4 Bayern", + }, + }, + ), + ( + "dortmund", + { + "title": "Dortmund", + "fields": { + "Pos": "2", + "W-T-L": "21-6-7", + "Pts": "69", + "June 20, 2020 (Bundesliga)": "RB Leipzig 0:2 Dortmund", + "June 27, 2020 (Bundesliga)": "Dortmund 0:4 Hoffenheim", + }, + }, + ), + ], +) +def test_get_blurb(team, expected): + assert get_blurb(team) == expected diff --git a/tests/test_process_matchday.py b/tests/test_process_matchday.py new file mode 100644 index 0000000..0d6403f --- /dev/null +++ b/tests/test_process_matchday.py @@ -0,0 +1,37 @@ +import pytest + +from the_mines.process.fussballdaten.process_matchday import ( + live_match, + future_match, + past_match, + create_initial_dict, + get_initial_data, + process_results, +) + + +@pytest.mark.online +@pytest.mark.parametrize( + ("matchday", "season", "expected"), + [ + ( + "34", + "2020", + [ + [("Frankfurt", "3"), ("Paderborn", "2")], + [("Bremen", "6"), ("Köln", "1")], + [("Freiburg", "4"), ("Schalke", "0")], + [("Augsburg", "1"), ("RB Leipzig", "2")], + [("Union Berlin", "3"), ("Düsseldorf", "0")], + [("Dortmund", "0"), ("Hoffenheim", "4")], + [("Leverkusen", "1"), ("Mainz", "0")], + [("M\\'gladbach", "2"), ("Hertha BSC", "1")], + [("Wolfsburg", "0"), ("Bayern", "4")], + ], + ) + ], +) +def test_process_results(matchday, season, expected): + results = process_results(matchday, season)["27.06.2020"] + for match in expected: + assert match in results diff --git a/tests/test_table_handler.py b/tests/test_table_handler.py new file mode 100644 index 0000000..9c7e942 --- /dev/null +++ b/tests/test_table_handler.py @@ -0,0 +1,70 @@ +import pytest +from tempfile import TemporaryFile +from bs4 import BeautifulSoup + +from utils.table_handler import ( + tables_from_soup, + get_table, + find_team_in_table, + extract_full_table_stats, +) +from the_mines.download.get_html import download_raw_html + + +def get_soup(url="https://www.fussballdaten.de/bundesliga/tabelle/2019"): + with TemporaryFile("w+") as tmp: + tmp.write(download_raw_html(url)) + tmp.seek(0) + return BeautifulSoup(tmp, "html.parser") + + +@pytest.mark.online +@pytest.mark.parametrize("expected", [(4)]) +def test_tables_from_soup(expected): + assert len(tables_from_soup(get_soup())) == expected + + +@pytest.mark.online +@pytest.mark.parametrize( + ("full", "form", "home", "away", "expected"), + [ + (True, False, False, False, 19), + (False, True, False, False, 4), + (False, False, True, False, 4), + (False, False, False, True, 4), + ], +) +def test_get_table(full, form, home, away, expected): + assert ( + len( + get_table( + tables_from_soup(get_soup()), full=full, form=form, home=home, away=away + ) + ) + == expected + ) + + +@pytest.mark.online +@pytest.mark.parametrize( + ("team", "table", "expected"), + [("bayern", get_table(tables_from_soup(get_soup()), full=True), 10)], +) +def test_find_team_in_table(team, table, expected): + assert len(find_team_in_table(team, table)) == expected + + +@pytest.mark.online +@pytest.mark.parametrize( + ("row", "expected"), + [ + ( + find_team_in_table( + "bayern", get_table(tables_from_soup(get_soup()), full=True) + ), + ("1", "Bayern", "34", "24", "6", "4", "88:32", "56", "78"), + ) + ], +) +def test_extract_full_table_stats(row, expected): + assert extract_full_table_stats(row) == expected diff --git a/the_mines/process/fussballdaten/process_blurb.py b/the_mines/process/fussballdaten/process_blurb.py index 195a991..cf5579b 100644 --- a/the_mines/process/fussballdaten/process_blurb.py +++ b/the_mines/process/fussballdaten/process_blurb.py @@ -76,6 +76,7 @@ def get_team_str(target): def get_glance_schedule(team, season=get_default_season()): team = get_team_str(team) + results = {} # Get previous and current match url = f"https://www.fussballdaten.de/vereine/{team}/{season}/spielplan/" @@ -94,23 +95,27 @@ def get_glance_schedule(team, season=get_default_season()): prev_score, _ = prev.find_all("span") curr_score, _ = curr.find_all("span") - # Get next match - url = f"https://www.fussballdaten.de/vereine/{team}/{season}/" - with TemporaryFile("w+") as tmp: - tmp.write(download_raw_html(url)) - tmp.seek(0) - soup = BeautifulSoup(tmp, "html.parser") - - (upcoming,) = soup.find_all("div", attrs={"class": "naechste-spiele"}) - - # The list of upcoming matches depends on how many matches are left - # in the season. We can't reliably list decompose so we'll have to pop - next = upcoming.find_all("a").pop(0) + results.update(build_matchup(prev.attrs["title"], prev_score.get_text())) + results.update(build_matchup(curr.attrs["title"], curr_score.get_text())) - results = {} - results.update(build_matchup(prev.attrs["title"], prev_score.get_text())) - results.update(build_matchup(curr.attrs["title"], curr_score.get_text())) - results.update(build_matchup(next.attrs["title"])) + # Get next match + # This needs a rework. We can't get next matches at the end of the season. + try: + url = f"https://www.fussballdaten.de/vereine/{team}/{season}/" + with TemporaryFile("w+") as tmp: + tmp.write(download_raw_html(url)) + tmp.seek(0) + soup = BeautifulSoup(tmp, "html.parser") + + (upcoming,) = soup.find_all("div", attrs={"class": "naechste-spiele"}) + + # The list of upcoming matches depends on how many matches are left + # in the season. We can't reliably list decompose so we'll have to pop + next = upcoming.find_all("a").pop(0) + results.update(build_matchup(next.attrs["title"])) + except: + logger.debug("No upcoming matches") + pass return results