diff --git a/src/gradescopeapi/classes/_helpers/_course_helpers.py b/src/gradescopeapi/classes/_helpers/_course_helpers.py index 23ab416..182e4df 100644 --- a/src/gradescopeapi/classes/_helpers/_course_helpers.py +++ b/src/gradescopeapi/classes/_helpers/_course_helpers.py @@ -167,8 +167,13 @@ def get_course_members(soup: BeautifulSoup, course_id: str) -> list[Member]: data_url: str = rosterName_button.get("data-url", None) user_id = data_url.split("user_id=")[-1] - # fetch number of submissions from table cell - num_submissions = int(cells[num_submissions_column].text) + # fetch number of submissions from table cell. Some roster layouts have + # extra trailing action columns, so the heuristic column index can land + # on an empty/non-numeric cell; default to 0 rather than raising. + try: + num_submissions = int(cells[num_submissions_column].text) + except (ValueError, IndexError): + num_submissions = 0 # create Member object with all relevant info member_list.append( diff --git a/tests/test_roster_unit.py b/tests/test_roster_unit.py new file mode 100644 index 0000000..692719e --- /dev/null +++ b/tests/test_roster_unit.py @@ -0,0 +1,65 @@ +"""Unit tests for get_course_members — no network/credentials required. + +Builds a roster page from crafted HTML and checks that a roster whose +heuristic "# submissions" column is empty still parses (count defaults to 0), +and that a numeric count is read correctly. +""" +import json + +from bs4 import BeautifulSoup + +from gradescopeapi.classes._helpers._course_helpers import get_course_members + + +def _roster_html(rows): + headers = "".join(f"h{i}" for i in range(6)) # -> column index 3 + body = "" + for r in rows: + cm = json.dumps({ + "full_name": r["full_name"], + "first_name": r["first_name"], + "last_name": r["last_name"], + "sid": r["sid"], + }) + # 8 cells; index 3 is the heuristic submissions column + cells = [ + f'', + "email", "role", + f'{r["submissions"]}', # index 3 + "", "", "", "", + ] + body += f'{"".join(cells)}' + return f'{headers}{body}
' + + +def test_empty_submissions_cell_does_not_crash(): + rows = [{ + "full_name": "Ada Lovelace", "first_name": "Ada", "last_name": "Lovelace", + "sid": "123", "email": "ada@example.edu", "role": "0", + "user_id": "555", "submissions": "", # empty submissions cell + }] + soup = BeautifulSoup(_roster_html(rows), "html.parser") + members = get_course_members(soup, "999") + assert len(members) == 1 + m = members[0] + assert m.email == "ada@example.edu" + assert m.role == "Student" + assert m.full_name == "Ada Lovelace" + assert m.user_id == "555" + assert m.num_submissions == 0 + + +def test_numeric_submissions_cell_is_parsed(): + rows = [{ + "full_name": "Alan Turing", "first_name": "Alan", "last_name": "Turing", + "sid": "9", "email": "alan@example.edu", "role": "2", + "user_id": "777", "submissions": "5", + }] + soup = BeautifulSoup(_roster_html(rows), "html.parser") + members = get_course_members(soup, "999") + assert members[0].num_submissions == 5 + assert members[0].role == "TA"