diff --git a/pittapi/textbook.py b/pittapi/textbook.py index d608b92..197314a 100644 --- a/pittapi/textbook.py +++ b/pittapi/textbook.py @@ -1,377 +1,225 @@ -import grequests -import requests - -from typing import Any, Callable, Generator - -BASE_URL = "http://pitt.verbacompare.com/" - -CODES = [ - "ADMJ", - "ADMPS", - "AFRCNA", - "AFROTC", - "ANTH", - "ARABIC", - "ARTSC", - "ASL", - "ASTRON", - "ATHLTR", - "BACC", - "BCHS", - "BECN", - "BFIN", - "BHRM", - "BIND", - "BIOENG", - "BIOETH", - "BIOINF", - "BIOSC", - "BIOST", - "BMIS", - "BMKT", - "BOAH", - "BORG", - "BQOM", - "BSEO", - "BSPP", - "BUS", - "BUSACC", - "BUSADM", - "BUSBIS", - "BUSECN", - "BUSENV", - "BUSERV", - "BUSFIN", - "BUSHRM", - "BUSMKT", - "BUSORG", - "BUSQOM", - "BUSSCM", - "BUSSPP", - "CDACCT", - "CDENT", - "CEE", - "CGS", - "CHE", - "CHEM", - "CHIN", - "CLASS", - "CLRES", - "CLST", - "CMMUSIC", - "CMPBIO", - "COE", - "COEA", - "COEE", - "COMMRC", - "CS", - "CSD", - "DENHYG", - "DENT", - "DIASCI", - "DSANE", - "EAS", - "ECE", - "ECON", - "EDUC", - "ELI", - "EM", - "ENDOD", - "ENGCMP", - "ENGFLM", - "ENGLIT", - "ENGR", - "ENGSCI", - "ENGWRT", - "ENRES", - "EOH", - "EPIDEM", - "FACDEV", - "FILMG", - "FILMST", - "FP", - "FR", - "FTADMA", - "FTDA", - "FTDB", - "FTDC", - "FTDR", - "GEOL", - "GER", - "GERON", - "GREEK", - "GREEKM", - "GSWS", - "HAA", - "HIM", - "HINDI", - "HIST", - "HONORS", - "HPA", - "HPM", - "HPS", - "HRS", - "HUGEN", - "IDM", - "IE", - "IL", - "IMB", - "INFSCI", - "INTBP", - "IRISH", - "ISB", - "ISSP", - "ITAL", - "JPNSE", - "JS", - "KOREAN", - "LATIN", - "LAW", - "LCTL", - "LDRSHP", - "LEGLST", - "LING", - "LIS", - "LSAP", - "MATH", - "ME", - "MED", - "MEDEDU", - "MEMS", - "MILS", - "MOLBPH", - "MSCBIO", - "MSCBMP", - "MSCMP", - "MSE", - "MSIMM", - "MSMBPH", - "MSMGDB", - "MSMPHL", - "MSMVM", - "MSNBIO", - "MUSIC", - "NEURO", - "NPHS", - "NROSCI", - "NUR", - "NURCNS", - "NURNM", - "NURNP", - "NURSAN", - "NURSP", - "NUTR", - "ODO", - "OLLI", - "ORBIOL", - "ORSUR", - "OT", - "PAS", - "PEDC", - "PEDENT", - "PERIO", - "PERS", - "PETE", - "PHARM", - "PHIL", - "PHYS", - "PIA", - "POLISH", - "PORT", - "PROSTH", - "PS", - "PSY", - "PSYC", - "PSYED", - "PT", - "PUBHLT", - "PUBSRV", - "REHSCI", - "REL", - "RELGST", - "RESTD", - "RUSS", - "SA", - "SERCRO", - "SLAV", - "SLOVAK", - "SOC", - "SOCWRK", - "SPAN", - "STAT", - "SWAHIL", - "SWBEH", - "SWCOSA", - "SWE", - "SWGEN", - "SWINT", - "SWRES", - "SWWEL", - "TELCOM", - "THEA", - "TURKSH", - "UKRAIN", - "URBNST", - "VIET", -] -KEYS = ["isbn", "citation", "title", "edition", "author"] -QUERIES = { - "courses": "compare/courses/?id={}&term_id={}", - "books": "compare/books?id={}", -} -LOOKUP_ERRORS = { - 1: "section {1}.", - 2: "instructor {2}.", - 3: "section {1} or instructor {2}.", -} - - -def _construct_query(query: str, *args) -> str: - """Constructs query based on which one is requested - and fills the query in with the given arguments - """ - return QUERIES[query].format(*args) - - -def _validate_term(term: str) -> str: - """Validates term is a string and check if it is valid.""" - if len(term) == 4 and term.isdigit(): - return term - raise ValueError("Invalid term") - - -def _validate_course(course: str) -> str: - """Validates course is a four digit number, - otherwise adds zero(s) to create four digit number or, - raises an exception. - """ - if len(course) > 4 or not course.isdigit(): - raise ValueError("Invalid course number") - elif len(course) == 4: - return course - return "0" * (4 - len(course)) + course - - -def _filter_dictionary(d: dict[Any, Any], keys: list[Any]) -> dict[Any, Any]: - """Creates new dictionary from selecting certain - key value pairs from another dictionary - """ - return dict((k, d[k]) for k in keys if k in d) - - -def _find_item(id_key, data_key, error_item) -> Callable[[dict[Any, Any], Any], Any]: - """Finds a dictionary in a list based on its id key, and - returns a piece of data from the dictionary based on a data key. - """ - - def find(data, value): - for item in data: - if item[id_key] == value: - return item[data_key] - raise LookupError("Can't find {} {}.".format(error_item, str(value))) - - return find +from __future__ import annotations - -_find_sections = _find_item("id", "sections", "course") -_find_course_id_by_instructor = _find_item("instructor", "id", "instructor") -_find_course_id_by_section = _find_item("name", "id", "section") +# grequests must be imported before all other libraries, especially requests, +# because grequests uses gevent, which in turn uses monkey-patching to implement concurrency +import grequests +import warnings + +from dataclasses import dataclass +from requests import ConnectionError +from requests_html import HTMLResponse, HTMLSession +from typing import Any, NamedTuple + +BASE_URL = "https://pitt.verbacompare.com/" + +SUBJECTS_URL = BASE_URL + "compare/departments/?term={term_id}" +COURSES_URL = BASE_URL + "compare/courses/?id={dept_id}&term_id={term_id}" +BOOKS_URL = BASE_URL + "compare/books?id={section_id}" + +CURRENT_TERM_ID = 78104 # Term ID for fall 2024, TODO: figure out how this ID is generated +MAX_REQUEST_ATTEMPTS = 3 + +sess = HTMLSession() +request_headers: dict[str, str] | None = None +subject_map: dict[str, str] | None = None + + +@dataclass # No dataclass slots because they're not supported in Python 3.9 +class CourseInfo: + subject: str + course_num: str + instructor: str | None = None + section_num: str | None = None + + def __post_init__(self) -> None: + if not subject_map: + _update_subject_map() + assert subject_map + + self.subject = self.subject.upper() + if self.subject not in subject_map: + raise LookupError(f"{self.subject} is not a valid subject") + if len(self.course_num) > 4 or not self.course_num.isdigit(): + raise ValueError("Invalid course number") + self.course_num = "0" * (4 - len(self.course_num)) + self.course_num + if self.instructor: + self.instructor = self.instructor.upper() + if self.section_num and (len(self.section_num) != 4 or not self.section_num.isdigit()): + raise ValueError("Invalid section number") + + +class Textbook(NamedTuple): + title: str | None + author: str | None + edition: str | None + isbn: str | None + citation: str | None + + @classmethod + def from_json(cls, json: dict[str, Any]) -> Textbook | None: + parsed_textbook = cls( + title=json.get("title"), + author=json.get("author"), + edition=json.get("edition"), + isbn=json.get("isbn"), + citation=json.get("citation"), + ) + # If all fields are None, then don't bother returning a Textbook object + return parsed_textbook if any(field for field in parsed_textbook) else None + + +def _update_headers() -> None: + for i in range(MAX_REQUEST_ATTEMPTS): + base_response: HTMLResponse = sess.get(BASE_URL) + if base_response.status_code == 200: + break + warnings.warn(f"Attempt {i + 1} to connect to textbook site failed, trying again") + if base_response.status_code != 200: # Request failed too many times + raise ConnectionError(f"Failed to connect to textbook site after {MAX_REQUEST_ATTEMPTS} attempts") + + elements = base_response.html.find("meta") + assert isinstance(elements, list) + for element in elements: + if element.attrs.get("name") == "csrf-token": + csrf_token: str = element.attrs["content"] + global request_headers + request_headers = {"X-CSRF-Token": csrf_token} + return + raise ConnectionError("Unable to find valid request credentials, cannot connect to textbook site") + + +def _update_subject_map() -> None: + if not request_headers: + _update_headers() + + for i in range(MAX_REQUEST_ATTEMPTS): + subject_response = sess.get(SUBJECTS_URL.format(term_id=CURRENT_TERM_ID), headers=request_headers) + if subject_response.status_code == 200: + break + warnings.warn(f"Attempt {i + 1} to retrieve list of subjects failed, trying again") + _update_headers() # Try again with new CSRF token + if subject_response.status_code != 200: # Request failed too many times + raise ConnectionError(f"Failed to retrieve list of subjects after {MAX_REQUEST_ATTEMPTS} attempts") + + subject_json: list[dict[str, str]] = subject_response.json() + global subject_map + subject_map = {entry["name"]: entry["id"] for entry in subject_json} + + +def _find_section_from_json(sections: list[dict[str, str]], instructor: str | None, section_num: str | None) -> str: + if section_num: + for section in sections: + if section["name"] == section_num: + return section["id"] + raise LookupError(f"No section found with given {section_num=}") + if instructor: + for section in sections: + if section["instructor"] == instructor: + return section["id"] + raise LookupError(f"No section found with given {instructor=}") + + # Not enough info provided, so try to deduce the correct section: + # - If there's only 1 section of the course, then the sole section must be the correct one + # - If all sections of the course are taught by the same instructor, then we can assume that all sections will have the + # same textbook, meaning that the exact section doesn't matter + instructors = {section["instructor"] for section in sections} + if len(sections) == 1 or len(instructors) == 1: + return sections[0]["id"] + raise LookupError( + "Cannot determine section ID from given arguments, please provide the instructor's name and/or the section number" + ) -def _extract_id(response, course: str, instructor: str, section: str) -> str: - """Gathers sections from departments and finds course id by - instructor name or section number. - """ - sections = _find_sections(response.json(), course) - error = 0 - try: - if section is not None: - return _find_course_id_by_section(sections, section) - except LookupError: - error += 1 - try: - if instructor is not None: - return _find_course_id_by_instructor(sections, instructor.upper()) - except LookupError: - error += 2 - raise LookupError("Unable to find course by " + LOOKUP_ERRORS[error].format(section, instructor)) - - -def _extract_books(ids: list[str]) -> list[dict[str, str]]: +def _get_textbooks_for_ids(ids: list[str]) -> list[Textbook]: """Fetches a course's textbook information and returns a list of textbooks for the given course. """ - responses = grequests.imap([grequests.get(BASE_URL + _construct_query("books", section_id)) for section_id in ids]) - books = [_filter_dictionary(book, KEYS) for response in responses for book in response.json()] - return books - - -# Meant to force a return of None instead of raising a KeyError -# when using a nonexistent key -class DefaultDict(dict): - def __missing__(self, key): - return None - - -def _fetch_course( - courses: list[dict[str, str]], departments: dict[str, str] -) -> Generator[tuple[str, str, str, str], None, None]: - """Generator for fetching a courses information in order""" - for course in courses: - course = DefaultDict(course) - yield ( - departments[course["department"]], - course["department"] + _validate_course(course["course"]), - course["instructor"], - course["section"], + if not request_headers: + _update_headers() + + responses = grequests.imap(grequests.get(BOOKS_URL.format(section_id=id), headers=request_headers) for id in ids) + books = [] + for response in responses: + for book_json in response.json(): + book = Textbook.from_json(book_json) + if book: + books.append(book) + else: + warnings.warn(f"No textbook info found for {response}") + return [book for book in books if book] # Drop all None values + + +def _get_textbooks_from_json( + course_json: list[dict[str, Any]], subject: str, course_num: str, instructor: str | None, section_num: str | None +) -> list[Textbook]: + for course in course_json: + if course["id"] == subject + course_num: + section_id = _find_section_from_json(course["sections"], instructor, section_num) + return _get_textbooks_for_ids([section_id]) + raise LookupError(f"{subject} {course_num} is not a valid course") + + +def get_textbooks_for_course(course: CourseInfo) -> list[Textbook]: + if not request_headers: + _update_headers() + if not subject_map: + _update_subject_map() + assert subject_map + + for i in range(MAX_REQUEST_ATTEMPTS): + course_response = sess.get( + COURSES_URL.format(dept_id=subject_map[course.subject], term_id=CURRENT_TERM_ID), headers=request_headers ) + if course_response.status_code == 200: + break + warnings.warn(f"Attempt {i} to retrieve list of {course.subject} courses failed, trying again") + _update_headers() # Try again with new CSRF token + if course_response.status_code != 200: # Request failed too many times + raise ConnectionError(f"Failed to retrieve list of {course.subject} courses from textbook site") + + return _get_textbooks_from_json( + course_json=course_response.json(), + subject=course.subject, + course_num=course.course_num, + instructor=course.instructor, + section_num=course.section_num, + ) -def _get_department_number(department_code: str) -> int: - """Temporary solution to finding a department. - There will be a new method to getting department information - at a later time. - """ - department_number = CODES.index(department_code) + 22399 - if department_number > 22462: - department_number += 2 # between codes DSANE and EAS 2 id numbers are skipped. - if department_number > 22580: - department_number += 1 # between codes PUBSRV and REHSCI 1 id number is skipped. - return department_number - - -def get_textbooks(term: str, courses: list[dict[str, str]]) -> list[dict[str, str]]: - """Retrieves textbooks for multiple courses in the same term.""" - departments = {course["department"] for course in courses} - responses = grequests.map( - [ - grequests.get( - BASE_URL + _construct_query("courses", _get_department_number(department), _validate_term(term)), - timeout=10, +def get_textbooks_for_courses(courses_info: list[CourseInfo]) -> list[Textbook]: + if not request_headers: + _update_headers() + if not subject_map: + _update_subject_map() + assert subject_map + + # Precompute list of unique subjects to avoid unnecessary API requests + subjects = {course_info.subject for course_info in courses_info} + courses_for_subjects: dict[str, list[dict[str, Any]]] = {} + for subject in subjects: + for i in range(MAX_REQUEST_ATTEMPTS): + course_response = sess.get( + COURSES_URL.format(dept_id=subject_map[subject], term_id=CURRENT_TERM_ID), headers=request_headers + ) + if course_response.status_code == 200: + break + warnings.warn(f"Attempt {i} to retrieve list {subject} courses failed, trying again") + _update_headers() # Try again with new CSRF token + if course_response.status_code != 200: # Request failed too many times + raise ConnectionError(f"Failed to retrieve list of {subject} courses from textbook site") + + courses_for_subjects[subject] = course_response.json() + + textbooks = [] + for course_info in courses_info: + course_json = courses_for_subjects[course_info.subject] + textbooks.extend( + _get_textbooks_from_json( + course_json=course_json, + subject=course_info.subject, + course_num=course_info.course_num, + instructor=course_info.instructor, + section_num=course_info.section_num, ) - for department in departments - ] - ) - section_ids = [ - _extract_id(*course) - for course in _fetch_course( - courses, - dict( - zip( - sorted(departments), - sorted(responses, key=lambda resp: resp.json()[0]["name"]), - ) - ), ) - ] - return _extract_books(section_ids) - - -def get_textbook(term: str, department: str, course: str, instructor: str = None, section: str = None) -> list[dict[str, str]]: - """Retrieves textbooks for a given course.""" - has_section_or_instructor = (instructor is not None) or (section is not None) - if not has_section_or_instructor: - raise TypeError("get_textbook() is missing a instructor or section argument") - response = requests.get(BASE_URL + _construct_query("courses", _get_department_number(department), _validate_term(term))) - section_id = _extract_id(response, department + _validate_course(course), instructor, section) - return _extract_books([section_id]) + return textbooks diff --git a/tests/samples/textbook_base_page.html b/tests/samples/textbook_base_page.html new file mode 100644 index 0000000..7c342b8 --- /dev/null +++ b/tests/samples/textbook_base_page.html @@ -0,0 +1,238 @@ + + + + + + + + +Compare Textbook Prices Online | University Store on Fifth + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+ + + +
+
+
No Courses Selected!
+
+
+
+ +
+
+ + + + + + + + + + + + diff --git a/tests/samples/textbook_courses_CS.json b/tests/samples/textbook_courses_CS.json index 0ddfbc2..c19c409 100644 --- a/tests/samples/textbook_courses_CS.json +++ b/tests/samples/textbook_courses_CS.json @@ -1 +1,1594 @@ -[{"name":"CS 0004","id":"CS0004","sections":[{"id":"2096252","name":"1070","instructor":"DEVINE"},{"id":"2096253","name":"7010","instructor":"COSTANTINO"}]},{"name":"CS 0007","id":"CS0007","sections":[{"id":"2096254","name":"1050","instructor":"DELANEY"},{"id":"2096255","name":"1060","instructor":null},{"id":"2096256","name":"1090","instructor":null},{"id":"2096257","name":"1100","instructor":"DELANEY"},{"id":"2096258","name":"1105","instructor":null},{"id":"2096259","name":"1110","instructor":null},{"id":"2096260","name":"1200","instructor":"FILIPSKI"},{"id":"2096261","name":"1210","instructor":null},{"id":"2096262","name":"1220","instructor":null},{"id":"2096263","name":"1250","instructor":"FILIPSKI"},{"id":"2096264","name":"1255","instructor":null},{"id":"2096265","name":"1260","instructor":null},{"id":"2096266","name":"1300","instructor":"KREBS"},{"id":"2096267","name":"1310","instructor":"KREBS"},{"id":"2096268","name":"1320","instructor":"KREBS"}]},{"name":"CS 0008","id":"CS0008","sections":[{"id":"2096269","name":"1020","instructor":"MOHAMED KHATTAB"},{"id":"2096270","name":"1030","instructor":null},{"id":"2096271","name":"1040","instructor":null},{"id":"2096272","name":"1230","instructor":"NOVACKY"},{"id":"2096273","name":"1235","instructor":null},{"id":"2096274","name":"1240","instructor":null},{"id":"2096275","name":"1300","instructor":"PRODONOVICH"},{"id":"2096276","name":"1310","instructor":null},{"id":"2096277","name":"1320","instructor":null}]},{"name":"CS 0090","id":"CS0090","sections":[{"id":"2096278","name":"1040","instructor":"DEVINE"}]},{"name":"CS 0134","id":"CS0134","sections":[{"id":"2096279","name":"1060","instructor":"CHANG"},{"id":"2096280","name":"1200","instructor":"BILLINGSLEY"}]},{"name":"CS 0334","id":"CS0334","sections":[{"id":"2096281","name":"1020","instructor":"DENARDIS"}]},{"name":"CS 0401","id":"CS0401","sections":[{"id":"2096282","name":"1030","instructor":"HOFFMAN"},{"id":"2096283","name":"1035","instructor":null},{"id":"2096284","name":"1040","instructor":null},{"id":"2096285","name":"1045","instructor":"HOFFMAN"},{"id":"2096286","name":"1060","instructor":"RAMIREZ"},{"id":"2096287","name":"1070","instructor":null},{"id":"2096288","name":"1080","instructor":null},{"id":"2096289","name":"1090","instructor":null},{"id":"2096290","name":"1100","instructor":"RAMIREZ"},{"id":"2096291","name":"1105","instructor":null},{"id":"2096292","name":"1110","instructor":null},{"id":"2096293","name":"1115","instructor":null},{"id":"2096294","name":"1200","instructor":"HOFFMAN"},{"id":"2096295","name":"1210","instructor":null},{"id":"2096296","name":"1220","instructor":"HOFFMAN"},{"id":"2096297","name":"1300","instructor":"DICK"},{"id":"2096298","name":"1310","instructor":null},{"id":"2096299","name":"1315","instructor":null},{"id":"2096300","name":"1320","instructor":"HOFFMAN"},{"id":"2096301","name":"1325","instructor":null},{"id":"2096302","name":"1330","instructor":null}]},{"name":"CS 0441","id":"CS0441","sections":[{"id":"2096303","name":"1070","instructor":"BONIDIE"},{"id":"2096304","name":"1075","instructor":null},{"id":"2096305","name":"1080","instructor":null},{"id":"2096306","name":"1085","instructor":null},{"id":"2096307","name":"1090","instructor":"BONIDIE"},{"id":"2096308","name":"1095","instructor":null},{"id":"2096309","name":"1100","instructor":null},{"id":"2096310","name":"1101","instructor":null},{"id":"2096311","name":"1110","instructor":"GARRISON III"},{"id":"2096312","name":"1120","instructor":null},{"id":"2096313","name":"1130","instructor":null},{"id":"2096314","name":"1135","instructor":null},{"id":"2096315","name":"1200","instructor":"GARRISON III"},{"id":"2096316","name":"1210","instructor":null},{"id":"2096317","name":"1220","instructor":null},{"id":"2198583","name":"1230","instructor":"GARRISON III"}]},{"name":"CS 0445","id":"CS0445","sections":[{"id":"2096318","name":"1030","instructor":"GARRISON III"},{"id":"2096319","name":"1035","instructor":null},{"id":"2096320","name":"1040","instructor":null},{"id":"2096321","name":"1045","instructor":null},{"id":"2096322","name":"1100","instructor":"GARRISON III"},{"id":"2096323","name":"1105","instructor":null},{"id":"2096324","name":"1110","instructor":null},{"id":"2096325","name":"1115","instructor":null},{"id":"2096326","name":"1200","instructor":"MOHAMED KHATTAB"},{"id":"2096327","name":"1205","instructor":null},{"id":"2096328","name":"1210","instructor":null},{"id":"2096329","name":"1220","instructor":null},{"id":"2106366","name":"1240","instructor":"JAMES"},{"id":"2106367","name":"1242","instructor":null},{"id":"2106368","name":"1244","instructor":null}]},{"name":"CS 0447","id":"CS0447","sections":[{"id":"2096330","name":"1070","instructor":"KOSIYATRAKUL"},{"id":"2096331","name":"1080","instructor":null},{"id":"2096332","name":"1090","instructor":null},{"id":"2096333","name":"1095","instructor":null},{"id":"2096334","name":"1120","instructor":"BILLINGSLEY"},{"id":"2096335","name":"1130","instructor":null},{"id":"2096336","name":"1140","instructor":null},{"id":"2096337","name":"1145","instructor":null},{"id":"2096338","name":"1200","instructor":"ZHANG"},{"id":"2096339","name":"1205","instructor":null},{"id":"2096340","name":"1210","instructor":null},{"id":"2198983","name":"1215","instructor":"KOSIYATRAKUL"}]},{"name":"CS 0449","id":"CS0449","sections":[{"id":"2096341","name":"1070","instructor":"AHN"},{"id":"2096342","name":"1080","instructor":null},{"id":"2096343","name":"1090","instructor":null},{"id":"2096344","name":"1095","instructor":null},{"id":"2096345","name":"1110","instructor":"AHN"},{"id":"2096346","name":"1120","instructor":null},{"id":"2096347","name":"1125","instructor":null},{"id":"2096348","name":"1130","instructor":null},{"id":"2096349","name":"1135","instructor":"MISURDA"},{"id":"2096350","name":"1140","instructor":null},{"id":"2096351","name":"1145","instructor":null}]},{"name":"CS 0590","id":"CS0590","sections":[{"id":"2096352","name":"1020","instructor":"QUIRIN"},{"id":"2096353","name":"1040","instructor":"BRANDON"}]},{"name":"CS 0699","id":"CS0699","sections":[{"id":"2096354","name":"1350","instructor":"FRANOLICH"}]},{"name":"CS 1501","id":"CS1501","sections":[{"id":"2096355","name":"1040","instructor":"FARNAN"},{"id":"2096356","name":"1050","instructor":null},{"id":"2096357","name":"1055","instructor":null},{"id":"2096358","name":"1060","instructor":"FARNAN"},{"id":"2096359","name":"1065","instructor":null},{"id":"2096360","name":"1075","instructor":null},{"id":"2096361","name":"1150","instructor":"FARNAN"},{"id":"2096362","name":"1170","instructor":"FARNAN"},{"id":"2096363","name":"1172","instructor":null},{"id":"2096364","name":"1174","instructor":null},{"id":"2096365","name":"1190","instructor":null},{"id":"2096366","name":"1195","instructor":null},{"id":"2096367","name":"1200","instructor":"WEHAR"},{"id":"2096368","name":"1205","instructor":null},{"id":"2096369","name":"1210","instructor":null},{"id":"2096370","name":"1250","instructor":"WEHAR"},{"id":"2096371","name":"1255","instructor":null},{"id":"2096372","name":"1260","instructor":null},{"id":"2096373","name":"1270","instructor":"FARNAN"},{"id":"2096374","name":"1272","instructor":null},{"id":"2096375","name":"1275","instructor":null},{"id":"2096376","name":"1280","instructor":"FARNAN"},{"id":"2096377","name":"1282","instructor":null},{"id":"2096378","name":"1285","instructor":null},{"id":"2096379","name":"1300","instructor":"FARNAN"},{"id":"2096380","name":"1305","instructor":"FARNAN"},{"id":"2096381","name":"1310","instructor":"ZHANG"},{"id":"2096382","name":"1315","instructor":"ZHANG"},{"id":"2096383","name":"1320","instructor":"ZHANG"},{"id":"2096384","name":"1330","instructor":"ZHANG"},{"id":"2096385","name":"1400","instructor":"WEHAR"},{"id":"2096386","name":"1405","instructor":null},{"id":"2096387","name":"1406","instructor":null},{"id":"2096388","name":"1410","instructor":"WEHAR"},{"id":"2096389","name":"1415","instructor":null},{"id":"2096390","name":"1420","instructor":null},{"id":"2198744","name":"ALL SECTIONS","instructor":"ALL INSTRUCTORS"}]},{"name":"CS 1502","id":"CS1502","sections":[{"id":"2096391","name":"1070","instructor":"MOHAMED KHATTAB"},{"id":"2096392","name":"1075","instructor":null},{"id":"2096393","name":"1080","instructor":null},{"id":"2096394","name":"1085","instructor":null},{"id":"2096395","name":"1200","instructor":"KOSIYATRAKUL"},{"id":"2096396","name":"1210","instructor":null},{"id":"2096397","name":"1220","instructor":null}]},{"name":"CS 1510","id":"CS1510","sections":[{"id":"2096398","name":"1040","instructor":"PRUHS"},{"id":"2096399","name":"1050","instructor":null}]},{"name":"CS 1520","id":"CS1520","sections":[{"id":"2096400","name":"1100","instructor":"BOWYTZ"},{"id":"2096401","name":"1105","instructor":null},{"id":"2096402","name":"1110","instructor":null},{"id":"2106419","name":"1050","instructor":null},{"id":"2106420","name":"1055","instructor":null},{"id":"2106421","name":"1060","instructor":null}]},{"name":"CS 1530","id":"CS1530","sections":[{"id":"2096403","name":"1070","instructor":"LABOON"},{"id":"2096404","name":"1080","instructor":"LABOON"}]},{"name":"CS 1541","id":"CS1541","sections":[{"id":"2096405","name":"1070","instructor":"MELHEM"}]},{"name":"CS 1550","id":"CS1550","sections":[{"id":"2096406","name":"1040","instructor":"MISURDA"},{"id":"2096407","name":"1050","instructor":null},{"id":"2096408","name":"1060","instructor":null},{"id":"2096409","name":"1080","instructor":"MISURDA"},{"id":"2096410","name":"1085","instructor":null},{"id":"2096411","name":"1090","instructor":null}]},{"name":"CS 1555","id":"CS1555","sections":[{"id":"2096412","name":"1100","instructor":"CHRYSANTHIS"},{"id":"2096413","name":"1110","instructor":null},{"id":"2096414","name":"1120","instructor":null}]},{"name":"CS 1566","id":"CS1566","sections":[{"id":"2096415","name":"1100","instructor":"KOSIYATRAKUL"},{"id":"2096416","name":"1105","instructor":null},{"id":"2096417","name":"1110","instructor":null}]},{"name":"CS 1571","id":"CS1571","sections":[{"id":"2096418","name":"1070","instructor":"LITMAN"}]},{"name":"CS 1621","id":"CS1621","sections":[{"id":"2096419","name":"1270","instructor":"ZHANG"}]},{"name":"CS 1622","id":"CS1622","sections":[{"id":"2096420","name":"1010","instructor":"MISURDA"}]},{"name":"CS 1632","id":"CS1632","sections":[{"id":"2096421","name":"1270","instructor":"LABOON"}]},{"name":"CS 1640","id":"CS1640","sections":[{"id":"2096422","name":"1050","instructor":"LABOON"}]},{"name":"CS 1645","id":"CS1645","sections":[{"id":"2096423","name":"1020","instructor":"BIGRIGG"}]},{"name":"CS 1652","id":"CS1652","sections":[{"id":"2096424","name":"1300","instructor":"LANGE"}]},{"name":"CS 1653","id":"CS1653","sections":[{"id":"2096425","name":"1140","instructor":"LEE"}]},{"name":"CS 1656","id":"CS1656","sections":[{"id":"2096426","name":"1130","instructor":"LABRINIDIS"},{"id":"2096427","name":"1140","instructor":null},{"id":"2096428","name":"1145","instructor":null}]},{"name":"CS 1675","id":"CS1675","sections":[{"id":"2096429","name":"1160","instructor":"HAUSKRECHT"}]},{"name":"CS 1699","id":"CS1699","sections":[{"id":"2096430","name":"1280","instructor":"ZNATI"}]},{"name":"CS 1900","id":"CS1900","sections":[{"id":"2096431","name":"1010","instructor":"LABOON"}]},{"name":"CS 1902","id":"CS1902","sections":[{"id":"2096432","name":"1050","instructor":"RAMIREZ"}]},{"name":"CS 1950","id":"CS1950","sections":[{"id":"2096433","name":"1010","instructor":"LABOON"},{"id":"2096434","name":"1020","instructor":"PRUHS"}]},{"name":"CS 1980","id":"CS1980","sections":[{"id":"2096435","name":"1200","instructor":"LABOON"}]},{"name":"CS 1981","id":"CS1981","sections":[{"id":"2096436","name":"1050","instructor":"LABOON"}]},{"name":"CS 2000","id":"CS2000","sections":[{"id":"2096437","name":"1010","instructor":"CHANG"},{"id":"2096438","name":"1020","instructor":null},{"id":"2096439","name":"1030","instructor":"CHILDERS"},{"id":"2096440","name":"1040","instructor":"AHN"},{"id":"2096441","name":"1050","instructor":"CHRYSANTHIS"},{"id":"2096442","name":"1060","instructor":null},{"id":"2096443","name":"1070","instructor":"HAUSKRECHT"},{"id":"2096444","name":"1080","instructor":"HWA"},{"id":"2096445","name":"1090","instructor":"LABRINIDIS"},{"id":"2096446","name":"1100","instructor":"LEE"},{"id":"2096447","name":"1110","instructor":"LITMAN"},{"id":"2096448","name":"1120","instructor":"KOVASHKA"},{"id":"2096449","name":"1130","instructor":"MELHEM"},{"id":"2096450","name":"1140","instructor":"MOSSE"},{"id":"2096451","name":"1150","instructor":"PRUHS"},{"id":"2096452","name":"1160","instructor":"WIEBE"},{"id":"2096453","name":"1170","instructor":"ZHANG"},{"id":"2096454","name":"1180","instructor":"ZNATI"},{"id":"2096455","name":"1190","instructor":"WANG"},{"id":"2096456","name":"1200","instructor":"LANGE"}]},{"name":"CS 2001","id":"CS2001","sections":[{"id":"2096457","name":"1040","instructor":"ZNATI"}]},{"name":"CS 2003","id":"CS2003","sections":[{"id":"2096458","name":"1060","instructor":"LEE"}]},{"name":"CS 2012","id":"CS2012","sections":[{"id":"2096459","name":"1040","instructor":"PRUHS"},{"id":"2096460","name":"1050","instructor":null}]},{"name":"CS 2045","id":"CS2045","sections":[{"id":"2096461","name":"1020","instructor":"BIGRIGG"}]},{"name":"CS 2053","id":"CS2053","sections":[{"id":"2132958","name":"1140","instructor":"LEE"}]},{"name":"CS 2055","id":"CS2055","sections":[{"id":"2096462","name":"1100","instructor":"CHRYSANTHIS"},{"id":"2096463","name":"1110","instructor":null},{"id":"2096464","name":"1120","instructor":null}]},{"name":"CS 2310","id":"CS2310","sections":[{"id":"2096465","name":"1020","instructor":"CHANG"}]},{"name":"CS 2410","id":"CS2410","sections":[{"id":"2096466","name":"1040","instructor":"CHILDERS"}]},{"name":"CS 2510","id":"CS2510","sections":[{"id":"2096467","name":"1040","instructor":"LANGE"}]},{"name":"CS 2610","id":"CS2610","sections":[{"id":"2096468","name":"1140","instructor":"WANG"}]},{"name":"CS 2710","id":"CS2710","sections":[{"id":"2096469","name":"1050","instructor":"HAUSKRECHT"}]},{"name":"CS 2900","id":"CS2900","sections":[{"id":"2096470","name":"1010","instructor":"CHANG"},{"id":"2096471","name":"1020","instructor":null},{"id":"2096472","name":"1030","instructor":"CHILDERS"},{"id":"2096473","name":"1040","instructor":"AHN"},{"id":"2096474","name":"1050","instructor":"CHRYSANTHIS"},{"id":"2096475","name":"1060","instructor":null},{"id":"2096476","name":"1070","instructor":"HAUSKRECHT"},{"id":"2096477","name":"1080","instructor":"HWA"},{"id":"2096478","name":"1090","instructor":"LABRINIDIS"},{"id":"2096479","name":"1100","instructor":"LEE"},{"id":"2096480","name":"1110","instructor":"LITMAN"},{"id":"2096481","name":"1120","instructor":"KOVASHKA"},{"id":"2096482","name":"1130","instructor":"MELHEM"},{"id":"2096483","name":"1140","instructor":"MOSSE"},{"id":"2096484","name":"1150","instructor":"PRUHS"},{"id":"2096485","name":"1160","instructor":"WIEBE"},{"id":"2096486","name":"1170","instructor":"ZHANG"},{"id":"2096487","name":"1180","instructor":"ZNATI"},{"id":"2096488","name":"1190","instructor":"LANGE"},{"id":"2096489","name":"1200","instructor":"WANG"}]},{"name":"CS 2910","id":"CS2910","sections":[{"id":"2096490","name":"1010","instructor":"CHANG"},{"id":"2096491","name":"1020","instructor":null},{"id":"2096492","name":"1030","instructor":"CHILDERS"},{"id":"2096493","name":"1040","instructor":"AHN"},{"id":"2096494","name":"1050","instructor":"CHRYSANTHIS"},{"id":"2096495","name":"1060","instructor":null},{"id":"2096496","name":"1070","instructor":"HAUSKRECHT"},{"id":"2096497","name":"1080","instructor":"HWA"},{"id":"2096498","name":"1090","instructor":"LABRINIDIS"},{"id":"2096499","name":"1100","instructor":"LEE"},{"id":"2096500","name":"1110","instructor":"LITMAN"},{"id":"2096501","name":"1120","instructor":"KOVASHKA"},{"id":"2096502","name":"1130","instructor":"MELHEM"},{"id":"2096503","name":"1140","instructor":"MOSSE"},{"id":"2096504","name":"1150","instructor":"PRUHS"},{"id":"2096505","name":"1160","instructor":"WIEBE"},{"id":"2096506","name":"1170","instructor":"ZHANG"},{"id":"2096507","name":"1180","instructor":"ZNATI"},{"id":"2096508","name":"1190","instructor":"WANG"},{"id":"2096509","name":"1200","instructor":"LANGE"}]},{"name":"CS 2990","id":"CS2990","sections":[{"id":"2096510","name":"1010","instructor":"CHANG"},{"id":"2096511","name":"1015","instructor":"WIEBE"},{"id":"2096512","name":"1020","instructor":null},{"id":"2096513","name":"1030","instructor":"CHILDERS"},{"id":"2096514","name":"1040","instructor":"AHN"},{"id":"2096515","name":"1050","instructor":"CHRYSANTHIS"},{"id":"2096516","name":"1060","instructor":null},{"id":"2096517","name":"1070","instructor":"HAUSKRECHT"},{"id":"2096518","name":"1080","instructor":"HWA"},{"id":"2096519","name":"1090","instructor":"LABRINIDIS"},{"id":"2096520","name":"1100","instructor":"LEE"},{"id":"2096521","name":"1110","instructor":"LITMAN"},{"id":"2096522","name":"1120","instructor":"KOVASHKA"},{"id":"2096523","name":"1130","instructor":"MELHEM"},{"id":"2096524","name":"1140","instructor":"MOSSE"},{"id":"2096525","name":"1150","instructor":"PRUHS"},{"id":"2096526","name":"1160","instructor":"ZHANG"},{"id":"2096527","name":"1170","instructor":"ZNATI"},{"id":"2096528","name":"1180","instructor":"WANG"},{"id":"2096529","name":"1190","instructor":"LANGE"},{"id":"2096530","name":"1200","instructor":null}]},{"name":"CS 3000","id":"CS3000","sections":[{"id":"2096531","name":"1010","instructor":"CHANG"},{"id":"2096532","name":"1020","instructor":null},{"id":"2096533","name":"1030","instructor":"CHILDERS"},{"id":"2096534","name":"1040","instructor":"AHN"},{"id":"2096535","name":"1050","instructor":"CHRYSANTHIS"},{"id":"2096536","name":"1060","instructor":null},{"id":"2096537","name":"1070","instructor":"HAUSKRECHT"},{"id":"2096538","name":"1080","instructor":"HWA"},{"id":"2096539","name":"1090","instructor":"LABRINIDIS"},{"id":"2096540","name":"1100","instructor":"LEE"},{"id":"2096541","name":"1110","instructor":"LITMAN"},{"id":"2096542","name":"1120","instructor":"KOVASHKA"},{"id":"2096543","name":"1130","instructor":"MELHEM"},{"id":"2096544","name":"1140","instructor":"MOSSE"},{"id":"2096545","name":"1150","instructor":"PRUHS"},{"id":"2096546","name":"1160","instructor":"WIEBE"},{"id":"2096547","name":"1170","instructor":"ZHANG"},{"id":"2096548","name":"1180","instructor":"ZNATI"},{"id":"2096549","name":"1190","instructor":"LANGE"},{"id":"2096550","name":"1200","instructor":"WANG"}]},{"name":"CS 3580","id":"CS3580","sections":[{"id":"2096551","name":"1010","instructor":"MELHEM"}]},{"name":"CS 3900","id":"CS3900","sections":[{"id":"2096552","name":"1010","instructor":"CHANG"},{"id":"2096553","name":"1020","instructor":null},{"id":"2096554","name":"1030","instructor":"CHILDERS"},{"id":"2096555","name":"1040","instructor":"AHN"},{"id":"2096556","name":"1050","instructor":"CHRYSANTHIS"},{"id":"2096557","name":"1060","instructor":null},{"id":"2096558","name":"1070","instructor":"HAUSKRECHT"},{"id":"2096559","name":"1080","instructor":"HWA"},{"id":"2096560","name":"1090","instructor":"LABRINIDIS"},{"id":"2096561","name":"1100","instructor":"LEE"},{"id":"2096562","name":"1110","instructor":"LITMAN"},{"id":"2096563","name":"1120","instructor":"KOVASHKA"},{"id":"2096564","name":"1130","instructor":"MELHEM"},{"id":"2096565","name":"1140","instructor":"MOSSE"},{"id":"2096566","name":"1150","instructor":"PRUHS"},{"id":"2096567","name":"1160","instructor":"WIEBE"},{"id":"2096568","name":"1170","instructor":"ZHANG"},{"id":"2096569","name":"1180","instructor":"ZNATI"},{"id":"2096570","name":"1190","instructor":"LANGE"},{"id":"2096571","name":"1200","instructor":"WANG"}]}] +[ + { + "name": "CS 0007", + "id": "CS0007", + "sections": [ + { + "id": "4558013", + "name": "1050", + "instructor": "HOFFMAN" + }, + { + "id": "4558014", + "name": "1061", + "instructor": "DEVINE" + }, + { + "id": "4558015", + "name": "1100", + "instructor": "BONIDIE" + }, + { + "id": "4558016", + "name": "1200", + "instructor": "DEVINE" + }, + { + "id": "4558017", + "name": "1250", + "instructor": "HOFFMAN" + }, + { + "id": "4558018", + "name": "1300", + "instructor": "XU" + } + ] + }, + { + "name": "CS 0011", + "id": "CS0011", + "sections": [ + { + "id": "4558019", + "name": "1000", + "instructor": "DE LIMA BARBOSA" + }, + { + "id": "4558020", + "name": "1010", + "instructor": "HEDGES" + }, + { + "id": "4558021", + "name": "1035", + "instructor": "SHARMA" + } + ] + }, + { + "name": "CS 0012", + "id": "CS0012", + "sections": [ + { + "id": "4558022", + "name": "1005", + "instructor": "DE LIMA BARBOSA" + }, + { + "id": "4558023", + "name": "1010", + "instructor": "DENARDIS" + }, + { + "id": "4558024", + "name": "8020", + "instructor": "YURASITS" + } + ] + }, + { + "name": "CS 0134", + "id": "CS0134", + "sections": [ + { + "id": "4558025", + "name": "1060", + "instructor": "DENARDIS" + }, + { + "id": "4558026", + "name": "8085", + "instructor": "LAYE" + }, + { + "id": "4558027", + "name": "8155", + "instructor": "SOLTER" + } + ] + }, + { + "name": "CS 0207", + "id": "CS0207", + "sections": [ + { + "id": "4558028", + "name": "1100", + "instructor": "RAMIREZ" + } + ] + }, + { + "name": "CS 0441", + "id": "CS0441", + "sections": [ + { + "id": "4558029", + "name": "1090", + "instructor": "BONIDIE" + }, + { + "id": "4558030", + "name": "1230", + "instructor": "FARNAN" + }, + { + "id": "4558031", + "name": "1245", + "instructor": "GARRISON III" + }, + { + "id": "4558032", + "name": "1250", + "instructor": "MURRUGARRA LLERENA" + }, + { + "id": "4637596", + "name": "1150", + "instructor": "MURRUGARRA LLERENA" + } + ] + }, + { + "name": "CS 0445", + "id": "CS0445", + "sections": [ + { + "id": "4558033", + "name": "1100", + "instructor": "BARSKY" + }, + { + "id": "4558034", + "name": "1200", + "instructor": "MOHAMED KHATTAB" + }, + { + "id": "4558035", + "name": "1400", + "instructor": "ELLIS" + }, + { + "id": "4637597", + "name": "1240", + "instructor": "JAMES" + } + ] + }, + { + "name": "CS 0447", + "id": "CS0447", + "sections": [ + { + "id": "4558036", + "name": "1070", + "instructor": "BILLINGSLEY" + }, + { + "id": "4558037", + "name": "1200", + "instructor": "MOHAMED KHATTAB" + }, + { + "id": "4637598", + "name": "1215", + "instructor": "BILLINGSLEY" + }, + { + "id": "4637599", + "name": "1400", + "instructor": "BILLINGSLEY" + } + ] + }, + { + "name": "CS 0449", + "id": "CS0449", + "sections": [ + { + "id": "4558038", + "name": "1110", + "instructor": "NUNES QUARESMA DE OL" + }, + { + "id": "4558039", + "name": "1135", + "instructor": "NUNES QUARESMA DE OL" + }, + { + "id": "4558040", + "name": "1150", + "instructor": "NUNES QUARESMA DE OL" + } + ] + }, + { + "name": "CS 0590", + "id": "CS0590", + "sections": [ + { + "id": "4558041", + "name": "1020", + "instructor": "QUIRIN" + }, + { + "id": "4558042", + "name": "1090", + "instructor": "QUIRIN" + }, + { + "id": "4558043", + "name": "1300", + "instructor": "QUIRIN" + } + ] + }, + { + "name": "CS 1501", + "id": "CS1501", + "sections": [ + { + "id": "4558044", + "name": "1060", + "instructor": "FARNAN" + }, + { + "id": "4558045", + "name": "1065", + "instructor": "MOHAMED KHATTAB" + }, + { + "id": "4558046", + "name": "1150", + "instructor": "MOHAMED KHATTAB" + }, + { + "id": "4558047", + "name": "1200", + "instructor": "FARNAN" + } + ] + }, + { + "name": "CS 1502", + "id": "CS1502", + "sections": [ + { + "id": "4558048", + "name": "1070", + "instructor": "KOSIYATRAKUL" + }, + { + "id": "4558049", + "name": "1200", + "instructor": "KOSIYATRAKUL" + }, + { + "id": "4558050", + "name": "1210", + "instructor": "KOSIYATRAKUL" + } + ] + }, + { + "name": "CS 1503", + "id": "CS1503", + "sections": [ + { + "id": "4558051", + "name": "1200", + "instructor": "BARSKY" + }, + { + "id": "4558052", + "name": "1250", + "instructor": "BARSKY" + } + ] + }, + { + "name": "CS 1510", + "id": "CS1510", + "sections": [ + { + "id": "4558053", + "name": "1040", + "instructor": "PRUHS" + }, + { + "id": "4558054", + "name": "1050", + "instructor": "PRUHS" + } + ] + }, + { + "name": "CS 1520", + "id": "CS1520", + "sections": [ + { + "id": "4558055", + "name": "1050", + "instructor": "FERREIRA" + }, + { + "id": "4558056", + "name": "1100", + "instructor": "FERREIRA" + } + ] + }, + { + "name": "CS 1530", + "id": "CS1530", + "sections": [ + { + "id": "4558057", + "name": "1070", + "instructor": "VON FRANKENBERG UND" + } + ] + }, + { + "name": "CS 1550", + "id": "CS1550", + "sections": [ + { + "id": "4558058", + "name": "1040", + "instructor": "MISURDA" + }, + { + "id": "4558059", + "name": "1080", + "instructor": "MISURDA" + }, + { + "id": "4558060", + "name": "1095", + "instructor": "MISURDA" + } + ] + }, + { + "name": "CS 1555", + "id": "CS1555", + "sections": [ + { + "id": "4558061", + "name": "1100", + "instructor": "NIXON" + } + ] + }, + { + "name": "CS 1566", + "id": "CS1566", + "sections": [ + { + "id": "4558062", + "name": "1100", + "instructor": "KOSIYATRAKUL" + } + ] + }, + { + "name": "CS 1571", + "id": "CS1571", + "sections": [ + { + "id": "4637600", + "name": "1010", + "instructor": "SINGH" + } + ] + }, + { + "name": "CS 1622", + "id": "CS1622", + "sections": [ + { + "id": "4558063", + "name": "1200", + "instructor": "AHN" + } + ] + }, + { + "name": "CS 1632", + "id": "CS1632", + "sections": [ + { + "id": "4558064", + "name": "1080", + "instructor": "AHN" + }, + { + "id": "4558065", + "name": "1270", + "instructor": "AHN" + } + ] + }, + { + "name": "CS 1635", + "id": "CS1635", + "sections": [ + { + "id": "4558066", + "name": "1010", + "instructor": "BIEHL" + } + ] + }, + { + "name": "CS 1640", + "id": "CS1640", + "sections": [ + { + "id": "4637601", + "name": "1050", + "instructor": "JORDAN" + } + ] + }, + { + "name": "CS 1645", + "id": "CS1645", + "sections": [ + { + "id": "4599605", + "name": "1010", + "instructor": "TANG" + } + ] + }, + { + "name": "CS 1651", + "id": "CS1651", + "sections": [ + { + "id": "4558067", + "name": "1200", + "instructor": "NUNES QUARESMA DE OL" + } + ] + }, + { + "name": "CS 1653", + "id": "CS1653", + "sections": [ + { + "id": "4558068", + "name": "1140", + "instructor": "GARRISON III" + } + ] + }, + { + "name": "CS 1656", + "id": "CS1656", + "sections": [ + { + "id": "4558069", + "name": "1130", + "instructor": "JIA" + }, + { + "id": "4558070", + "name": "1200", + "instructor": "YU" + } + ] + }, + { + "name": "CS 1660", + "id": "CS1660", + "sections": [ + { + "id": "4558071", + "name": "1300", + "instructor": "MAHONEY" + } + ] + }, + { + "name": "CS 1666", + "id": "CS1666", + "sections": [ + { + "id": "4558072", + "name": "1040", + "instructor": "FARNAN" + } + ] + }, + { + "name": "CS 1674", + "id": "CS1674", + "sections": [ + { + "id": "4649455", + "name": "1010", + "instructor": "MURRUGARRA LLERENA" + } + ] + }, + { + "name": "CS 1675", + "id": "CS1675", + "sections": [ + { + "id": "4558073", + "name": "1100", + "instructor": "SKEBA" + } + ] + }, + { + "name": "CS 1678", + "id": "CS1678", + "sections": [ + { + "id": "4639988", + "name": "1000", + "instructor": "JORDAN" + } + ] + }, + { + "name": "CS 1684", + "id": "CS1684", + "sections": [ + { + "id": "4647043", + "name": "1010", + "instructor": "LIU" + } + ] + }, + { + "name": "CS 1699", + "id": "CS1699", + "sections": [ + { + "id": "4637602", + "name": "1200", + "instructor": "SHI" + } + ] + }, + { + "name": "CS 1900", + "id": "CS1900", + "sections": [ + { + "id": "4558074", + "name": "1010", + "instructor": "AHN" + }, + { + "id": "4558075", + "name": "1015", + "instructor": "AHN" + } + ] + }, + { + "name": "CS 1901", + "id": "CS1901", + "sections": [ + { + "id": "4558076", + "name": "1300", + "instructor": "RAMIREZ" + } + ] + }, + { + "name": "CS 1906", + "id": "CS1906", + "sections": [ + { + "id": "4558077", + "name": "1050", + "instructor": "RAMIREZ" + } + ] + }, + { + "name": "CS 1950", + "id": "CS1950", + "sections": [ + { + "id": "4558078", + "name": "1010", + "instructor": "AHN" + }, + { + "id": "4558079", + "name": "1015", + "instructor": "AHN" + } + ] + }, + { + "name": "CS 1951", + "id": "CS1951", + "sections": [ + { + "id": "4558080", + "name": "1300", + "instructor": "RAMIREZ" + } + ] + }, + { + "name": "CS 1980", + "id": "CS1980", + "sections": [ + { + "id": "4637603", + "name": "1200", + "instructor": "JORDAN" + } + ] + }, + { + "name": "CS 2000", + "id": "CS2000", + "sections": [ + { + "id": "4558081", + "name": "1010", + "instructor": "WALKER" + }, + { + "id": "4558082", + "name": "1030", + "instructor": "CHILDERS" + }, + { + "id": "4558083", + "name": "1040", + "instructor": "CHRYSANTHIS" + }, + { + "id": "4558084", + "name": "1050", + "instructor": "HAUSKRECHT" + }, + { + "id": "4558085", + "name": "1060", + "instructor": "HWA" + }, + { + "id": "4558086", + "name": "1070", + "instructor": "KOVASHKA" + }, + { + "id": "4558087", + "name": "1080", + "instructor": "LABRINIDIS" + }, + { + "id": "4558088", + "name": "1090", + "instructor": "LANGE" + }, + { + "id": "4558089", + "name": "1100", + "instructor": "LEE" + }, + { + "id": "4558090", + "name": "1110", + "instructor": "LITMAN" + }, + { + "id": "4558091", + "name": "1130", + "instructor": "MOSSE" + }, + { + "id": "4558092", + "name": "1140", + "instructor": "PRUHS" + }, + { + "id": "4558093", + "name": "1170", + "instructor": "ZHANG" + }, + { + "id": "4558094", + "name": "1200", + "instructor": "SHANGGUAN" + }, + { + "id": "4558095", + "name": "1210", + "instructor": "BIEHL" + }, + { + "id": "4599606", + "name": "1020", + "instructor": "LI" + }, + { + "id": "4637604", + "name": "1220", + "instructor": "AHN" + }, + { + "id": "4637605", + "name": "1230", + "instructor": "JIA" + } + ] + }, + { + "name": "CS 2001", + "id": "CS2001", + "sections": [ + { + "id": "4558096", + "name": "1040", + "instructor": "BIEHL" + } + ] + }, + { + "name": "CS 2003", + "id": "CS2003", + "sections": [ + { + "id": "4558097", + "name": "1060", + "instructor": "BIEHL" + } + ] + }, + { + "name": "CS 2012", + "id": "CS2012", + "sections": [ + { + "id": "4558098", + "name": "1040", + "instructor": "PRUHS" + }, + { + "id": "4558099", + "name": "1050", + "instructor": "PRUHS" + } + ] + }, + { + "name": "CS 2035", + "id": "CS2035", + "sections": [ + { + "id": "4558100", + "name": "1200", + "instructor": "BIEHL" + } + ] + }, + { + "name": "CS 2045", + "id": "CS2045", + "sections": [ + { + "id": "4599607", + "name": "1010", + "instructor": "TANG" + } + ] + }, + { + "name": "CS 2051", + "id": "CS2051", + "sections": [ + { + "id": "4558101", + "name": "1200", + "instructor": "NUNES QUARESMA DE OL" + } + ] + }, + { + "name": "CS 2053", + "id": "CS2053", + "sections": [ + { + "id": "4558102", + "name": "1140", + "instructor": "GARRISON III" + } + ] + }, + { + "name": "CS 2055", + "id": "CS2055", + "sections": [ + { + "id": "4558103", + "name": "1100", + "instructor": "NIXON" + } + ] + }, + { + "name": "CS 2056", + "id": "CS2056", + "sections": [ + { + "id": "4558104", + "name": "1050", + "instructor": "JIA" + }, + { + "id": "4558105", + "name": "1100", + "instructor": "JIA" + }, + { + "id": "4558106", + "name": "1200", + "instructor": "YU" + } + ] + }, + { + "name": "CS 2060", + "id": "CS2060", + "sections": [ + { + "id": "4558107", + "name": "1300", + "instructor": "MAHONEY" + } + ] + }, + { + "name": "CS 2074", + "id": "CS2074", + "sections": [ + { + "id": "4649456", + "name": "1010", + "instructor": "MURRUGARRA LLERENA" + } + ] + }, + { + "name": "CS 2075", + "id": "CS2075", + "sections": [ + { + "id": "4558108", + "name": "1100", + "instructor": "SKEBA" + } + ] + }, + { + "name": "CS 2078", + "id": "CS2078", + "sections": [ + { + "id": "4639989", + "name": "1000", + "instructor": "JORDAN" + } + ] + }, + { + "name": "CS 2084", + "id": "CS2084", + "sections": [ + { + "id": "4647044", + "name": "1010", + "instructor": "LIU" + } + ] + }, + { + "name": "CS 2210", + "id": "CS2210", + "sections": [ + { + "id": "4558109", + "name": "1000", + "instructor": "ZHANG" + } + ] + }, + { + "name": "CS 2520", + "id": "CS2520", + "sections": [ + { + "id": "4558110", + "name": "1000", + "instructor": "SHANGGUAN" + } + ] + }, + { + "name": "CS 2637", + "id": "CS2637", + "sections": [ + { + "id": "4558111", + "name": "1010", + "instructor": "GAUTAM" + } + ] + }, + { + "name": "CS 2710", + "id": "CS2710", + "sections": [ + { + "id": "4637606", + "name": "1050", + "instructor": "SKEBA" + } + ] + }, + { + "name": "CS 2731", + "id": "CS2731", + "sections": [ + { + "id": "4558112", + "name": "1210", + "instructor": "YODER" + } + ] + }, + { + "name": "CS 2756", + "id": "CS2756", + "sections": [ + { + "id": "4637607", + "name": "1010", + "instructor": "JIA" + } + ] + }, + { + "name": "CS 2900", + "id": "CS2900", + "sections": [ + { + "id": "4558113", + "name": "1000", + "instructor": "ZHANG" + }, + { + "id": "4558114", + "name": "1020", + "instructor": "BABAY" + }, + { + "id": "4558115", + "name": "1030", + "instructor": "BIEHL" + }, + { + "id": "4558116", + "name": "1050", + "instructor": "CHILDERS" + }, + { + "id": "4558117", + "name": "1060", + "instructor": "CHRYSANTHIS" + }, + { + "id": "4558118", + "name": "1070", + "instructor": "COHEN" + }, + { + "id": "4558119", + "name": "1080", + "instructor": "HAUSKRECHT" + }, + { + "id": "4558120", + "name": "1100", + "instructor": "SHANGGUAN" + }, + { + "id": "4558121", + "name": "1110", + "instructor": "JIA" + }, + { + "id": "4558122", + "name": "1120", + "instructor": "KOVASHKA" + }, + { + "id": "4558123", + "name": "1130", + "instructor": "LABRINIDIS" + }, + { + "id": "4558124", + "name": "1170", + "instructor": "LEE" + }, + { + "id": "4558125", + "name": "1180", + "instructor": "LEE" + }, + { + "id": "4558126", + "name": "1200", + "instructor": "LITMAN" + }, + { + "id": "4558127", + "name": "1210", + "instructor": "MOSSE" + }, + { + "id": "4558128", + "name": "1220", + "instructor": "PRUHS" + }, + { + "id": "4558129", + "name": "1230", + "instructor": "TANG" + }, + { + "id": "4558130", + "name": "1240", + "instructor": "ZHANG" + }, + { + "id": "4599608", + "name": "1010", + "instructor": "LI" + }, + { + "id": "4599609", + "name": "1040", + "instructor": "SHI" + }, + { + "id": "4637608", + "name": "1090", + "instructor": "AHN" + }, + { + "id": "4637609", + "name": "1140", + "instructor": "GAUTAM" + } + ] + }, + { + "name": "CS 2905", + "id": "CS2905", + "sections": [ + { + "id": "4558131", + "name": "1300", + "instructor": "BIEHL" + } + ] + }, + { + "name": "CS 2910", + "id": "CS2910", + "sections": [ + { + "id": "4558132", + "name": "1000", + "instructor": "ZHANG" + }, + { + "id": "4558133", + "name": "1020", + "instructor": "BABAY" + }, + { + "id": "4558134", + "name": "1030", + "instructor": "BIEHL" + }, + { + "id": "4558135", + "name": "1050", + "instructor": "CHILDERS" + }, + { + "id": "4558136", + "name": "1060", + "instructor": "CHRYSANTHIS" + }, + { + "id": "4558137", + "name": "1070", + "instructor": "COHEN" + }, + { + "id": "4558138", + "name": "1080", + "instructor": "HAUSKRECHT" + }, + { + "id": "4558139", + "name": "1100", + "instructor": "SHANGGUAN" + }, + { + "id": "4558140", + "name": "1110", + "instructor": "JIA" + }, + { + "id": "4558141", + "name": "1120", + "instructor": "KOVASHKA" + }, + { + "id": "4558142", + "name": "1130", + "instructor": "LABRINIDIS" + }, + { + "id": "4558143", + "name": "1170", + "instructor": "LEE" + }, + { + "id": "4558144", + "name": "1180", + "instructor": "LEE" + }, + { + "id": "4558145", + "name": "1200", + "instructor": "LITMAN" + }, + { + "id": "4558146", + "name": "1210", + "instructor": "MOSSE" + }, + { + "id": "4558147", + "name": "1220", + "instructor": "PRUHS" + }, + { + "id": "4558148", + "name": "1230", + "instructor": "TANG" + }, + { + "id": "4558149", + "name": "1240", + "instructor": "ZHANG" + }, + { + "id": "4599610", + "name": "1010", + "instructor": "LI" + }, + { + "id": "4637610", + "name": "1040", + "instructor": "NUNES QUARESMA DE OL" + } + ] + }, + { + "name": "CS 2990", + "id": "CS2990", + "sections": [ + { + "id": "4558150", + "name": "1000", + "instructor": "ZHANG" + }, + { + "id": "4558151", + "name": "1010", + "instructor": "GAUTAM" + }, + { + "id": "4558152", + "name": "1020", + "instructor": "BABAY" + }, + { + "id": "4558153", + "name": "1030", + "instructor": "BIEHL" + }, + { + "id": "4558154", + "name": "1040", + "instructor": "WALKER" + }, + { + "id": "4558155", + "name": "1050", + "instructor": "CHILDERS" + }, + { + "id": "4558156", + "name": "1060", + "instructor": "CHRYSANTHIS" + }, + { + "id": "4558157", + "name": "1070", + "instructor": "COHEN" + }, + { + "id": "4558158", + "name": "1080", + "instructor": "HAUSKRECHT" + }, + { + "id": "4558159", + "name": "1100", + "instructor": "SHANGGUAN" + }, + { + "id": "4558160", + "name": "1110", + "instructor": "JIA" + }, + { + "id": "4558161", + "name": "1120", + "instructor": "KOVASHKA" + }, + { + "id": "4558162", + "name": "1130", + "instructor": "LABRINIDIS" + }, + { + "id": "4558163", + "name": "1170", + "instructor": "LEE" + }, + { + "id": "4558164", + "name": "1180", + "instructor": "LEE" + }, + { + "id": "4558165", + "name": "1200", + "instructor": "LITMAN" + }, + { + "id": "4558166", + "name": "1210", + "instructor": "MOSSE" + }, + { + "id": "4558167", + "name": "1220", + "instructor": "PRUHS" + }, + { + "id": "4558168", + "name": "1230", + "instructor": "TANG" + }, + { + "id": "4558169", + "name": "1240", + "instructor": "ZHANG" + }, + { + "id": "4599611", + "name": "1090", + "instructor": "LI" + }, + { + "id": "4637611", + "name": "1140", + "instructor": "NUNES QUARESMA DE OL" + } + ] + }, + { + "name": "CS 3000", + "id": "CS3000", + "sections": [ + { + "id": "4558170", + "name": "1000", + "instructor": "WALKER" + }, + { + "id": "4558171", + "name": "1020", + "instructor": "BABAY" + }, + { + "id": "4558172", + "name": "1030", + "instructor": "BIEHL" + }, + { + "id": "4558173", + "name": "1050", + "instructor": "CHILDERS" + }, + { + "id": "4558174", + "name": "1060", + "instructor": "CHRYSANTHIS" + }, + { + "id": "4558175", + "name": "1070", + "instructor": "COHEN" + }, + { + "id": "4558176", + "name": "1080", + "instructor": "HAUSKRECHT" + }, + { + "id": "4558177", + "name": "1100", + "instructor": "SHANGGUAN" + }, + { + "id": "4558178", + "name": "1110", + "instructor": "JIA" + }, + { + "id": "4558179", + "name": "1120", + "instructor": "KOVASHKA" + }, + { + "id": "4558180", + "name": "1130", + "instructor": "LABRINIDIS" + }, + { + "id": "4558181", + "name": "1170", + "instructor": "LEE" + }, + { + "id": "4558182", + "name": "1180", + "instructor": "LEE" + }, + { + "id": "4558183", + "name": "1200", + "instructor": "LITMAN" + }, + { + "id": "4558184", + "name": "1210", + "instructor": "MOSSE" + }, + { + "id": "4558185", + "name": "1220", + "instructor": "PRUHS" + }, + { + "id": "4558186", + "name": "1230", + "instructor": "TANG" + }, + { + "id": "4558187", + "name": "1240", + "instructor": "ZHANG" + }, + { + "id": "4599612", + "name": "1010", + "instructor": "LI" + }, + { + "id": "4637612", + "name": "1140", + "instructor": "NUNES QUARESMA DE OL" + } + ] + }, + { + "name": "CS 3550", + "id": "CS3550", + "sections": [ + { + "id": "4558188", + "name": "1200", + "instructor": "LABRINIDIS" + } + ] + }, + { + "name": "CS 3551", + "id": "CS3551", + "sections": [ + { + "id": "4558189", + "name": "1100", + "instructor": "BABAY" + } + ] + }, + { + "name": "CS 3750", + "id": "CS3750", + "sections": [ + { + "id": "4558190", + "name": "1100", + "instructor": "JIA" + } + ] + }, + { + "name": "CS 3800", + "id": "CS3800", + "sections": [ + { + "id": "4639990", + "name": "1000", + "instructor": "LIU" + } + ] + }, + { + "name": "CS 3900", + "id": "CS3900", + "sections": [ + { + "id": "4558191", + "name": "1000", + "instructor": "ZHANG" + }, + { + "id": "4558192", + "name": "1020", + "instructor": "BABAY" + }, + { + "id": "4558193", + "name": "1030", + "instructor": "BIEHL" + }, + { + "id": "4558194", + "name": "1040", + "instructor": "WALKER" + }, + { + "id": "4558195", + "name": "1050", + "instructor": "CHILDERS" + }, + { + "id": "4558196", + "name": "1060", + "instructor": "CHRYSANTHIS" + }, + { + "id": "4558197", + "name": "1070", + "instructor": "COHEN" + }, + { + "id": "4558198", + "name": "1080", + "instructor": "HAUSKRECHT" + }, + { + "id": "4558199", + "name": "1090", + "instructor": "NUNES QUARESMA DE OL" + }, + { + "id": "4558200", + "name": "1100", + "instructor": "SHANGGUAN" + }, + { + "id": "4558201", + "name": "1110", + "instructor": "JIA" + }, + { + "id": "4558202", + "name": "1120", + "instructor": "KOVASHKA" + }, + { + "id": "4558203", + "name": "1130", + "instructor": "LABRINIDIS" + }, + { + "id": "4558204", + "name": "1140", + "instructor": "LANGE" + }, + { + "id": "4558205", + "name": "1170", + "instructor": "LEE" + }, + { + "id": "4558206", + "name": "1180", + "instructor": "LEE" + }, + { + "id": "4558207", + "name": "1200", + "instructor": "LITMAN" + }, + { + "id": "4558208", + "name": "1210", + "instructor": "MOSSE" + }, + { + "id": "4558209", + "name": "1220", + "instructor": "PRUHS" + }, + { + "id": "4558210", + "name": "1230", + "instructor": "TANG" + }, + { + "id": "4558211", + "name": "1240", + "instructor": "ZHANG" + }, + { + "id": "4599613", + "name": "1010", + "instructor": "LI" + } + ] + } +] diff --git a/tests/samples/textbook_courses_MATH.json b/tests/samples/textbook_courses_MATH.json new file mode 100644 index 0000000..08cbacf --- /dev/null +++ b/tests/samples/textbook_courses_MATH.json @@ -0,0 +1,870 @@ +[ + { + "name": "MATH 0010", + "id": "MATH0010", + "sections": [ + { + "id": "4650882", + "name": "1070", + "instructor": null + } + ] + }, + { + "name": "MATH 0025", + "id": "MATH0025", + "sections": [ + { + "id": "4561831", + "name": "7510", + "instructor": "DEMATEO" + }, + { + "id": "4631046", + "name": "1100", + "instructor": "VARGSON" + } + ] + }, + { + "name": "MATH 0031", + "id": "MATH0031", + "sections": [ + { + "id": "4561832", + "name": "7510", + "instructor": "O'MARA" + }, + { + "id": "4631047", + "name": "1010", + "instructor": "VARGSON" + }, + { + "id": "4631048", + "name": "1030", + "instructor": "ISLAM" + }, + { + "id": "4631049", + "name": "1070", + "instructor": "ISLAM" + }, + { + "id": "4650927", + "name": "1050", + "instructor": null + }, + { + "id": "4650929", + "name": "1110", + "instructor": null + }, + { + "id": "4650930", + "name": "1130", + "instructor": null + }, + { + "id": "4650931", + "name": "1240", + "instructor": null + } + ] + }, + { + "name": "MATH 0120", + "id": "MATH0120", + "sections": [ + { + "id": "4631050", + "name": "1060", + "instructor": "GILTON" + }, + { + "id": "4631051", + "name": "1096", + "instructor": "POPESCU" + }, + { + "id": "4631052", + "name": "1110", + "instructor": "EVEREST" + }, + { + "id": "4631053", + "name": "1250", + "instructor": "GILTON" + } + ] + }, + { + "name": "MATH 0125", + "id": "MATH0125", + "sections": [ + { + "id": "4631054", + "name": "1040", + "instructor": "VARGSON" + } + ] + }, + { + "name": "MATH 0200", + "id": "MATH0200", + "sections": [ + { + "id": "4631055", + "name": "1100", + "instructor": "HOCKENSMITH" + }, + { + "id": "4631056", + "name": "1200", + "instructor": "DEBLOIS" + }, + { + "id": "4631057", + "name": "1300", + "instructor": "BAO" + } + ] + }, + { + "name": "MATH 0220", + "id": "MATH0220", + "sections": [ + { + "id": "4631058", + "name": "1025", + "instructor": "TROFIMOV" + }, + { + "id": "4631059", + "name": "1060", + "instructor": "XIONG" + }, + { + "id": "4631060", + "name": "1080", + "instructor": "YUST" + }, + { + "id": "4631061", + "name": "1100", + "instructor": "HOCKENSMITH" + }, + { + "id": "4631062", + "name": "1116", + "instructor": "WANG" + }, + { + "id": "4631063", + "name": "1120", + "instructor": "XIONG" + }, + { + "id": "4631064", + "name": "1191", + "instructor": "KAVEH" + }, + { + "id": "4631065", + "name": "1195", + "instructor": "MORGAN" + }, + { + "id": "4631066", + "name": "1210", + "instructor": "YUST" + }, + { + "id": "4631067", + "name": "1230", + "instructor": "SPARLING" + }, + { + "id": "4631068", + "name": "1239", + "instructor": "MORGAN" + }, + { + "id": "4631069", + "name": "1250", + "instructor": "KAVEH" + }, + { + "id": "4631070", + "name": "1300", + "instructor": "WANG" + }, + { + "id": "4631071", + "name": "1350", + "instructor": "FEDOROV" + }, + { + "id": "4631072", + "name": "1500", + "instructor": "YAO" + } + ] + }, + { + "name": "MATH 0230", + "id": "MATH0230", + "sections": [ + { + "id": "4631073", + "name": "1010", + "instructor": "TROFIMOV" + }, + { + "id": "4631074", + "name": "1015", + "instructor": "EVEREST" + }, + { + "id": "4631075", + "name": "1036", + "instructor": "EVEREST" + }, + { + "id": "4631076", + "name": "1040", + "instructor": "WHEELER" + }, + { + "id": "4631077", + "name": "1050", + "instructor": "POPESCU" + }, + { + "id": "4631078", + "name": "1060", + "instructor": "POPESCU" + } + ] + }, + { + "name": "MATH 0235", + "id": "MATH0235", + "sections": [ + { + "id": "4634384", + "name": "1040", + "instructor": "WHEELER" + } + ] + }, + { + "name": "MATH 0240", + "id": "MATH0240", + "sections": [ + { + "id": "4631079", + "name": "1015", + "instructor": "STREIPERT" + }, + { + "id": "4631080", + "name": "1105", + "instructor": "HAJLASZ" + }, + { + "id": "4631081", + "name": "1160", + "instructor": "HAJLASZ" + }, + { + "id": "4631082", + "name": "1300", + "instructor": "SPARLING" + }, + { + "id": "4631083", + "name": "1400", + "instructor": "POGGI CEVALLOS" + }, + { + "id": "4631084", + "name": "1420", + "instructor": "PAN" + } + ] + }, + { + "name": "MATH 0280", + "id": "MATH0280", + "sections": [ + { + "id": "4631085", + "name": "1030", + "instructor": "CHEN" + }, + { + "id": "4631086", + "name": "1041", + "instructor": "GARTSIDE" + }, + { + "id": "4631087", + "name": "1045", + "instructor": "GARTSIDE" + }, + { + "id": "4631088", + "name": "1200", + "instructor": "WOJTOWYTSCH" + } + ] + }, + { + "name": "MATH 0290", + "id": "MATH0290", + "sections": [ + { + "id": "4631089", + "name": "1040", + "instructor": "BAO" + }, + { + "id": "4631090", + "name": "1050", + "instructor": "STREIPERT" + }, + { + "id": "4631091", + "name": "1090", + "instructor": "TRENCHEA" + }, + { + "id": "4631092", + "name": "1100", + "instructor": "BAO" + } + ] + }, + { + "name": "MATH 0413", + "id": "MATH0413", + "sections": [ + { + "id": "4631093", + "name": "1030", + "instructor": "LAYTON" + }, + { + "id": "4631094", + "name": "1060", + "instructor": "HALES" + }, + { + "id": "4631095", + "name": "1100", + "instructor": "KAVEH" + } + ] + }, + { + "name": "MATH 0420", + "id": "MATH0420", + "sections": [ + { + "id": "4631096", + "name": "1020", + "instructor": "CHEN" + } + ] + }, + { + "name": "MATH 0430", + "id": "MATH0430", + "sections": [ + { + "id": "4631097", + "name": "1070", + "instructor": "PAN" + } + ] + }, + { + "name": "MATH 0470", + "id": "MATH0470", + "sections": [ + { + "id": "4631098", + "name": "1120", + "instructor": "XIONG" + } + ] + }, + { + "name": "MATH 0480", + "id": "MATH0480", + "sections": [ + { + "id": "4631099", + "name": "1000", + "instructor": "ION" + }, + { + "id": "4631100", + "name": "1200", + "instructor": "MOAKHER" + } + ] + }, + { + "name": "MATH 1010", + "id": "MATH1010", + "sections": [ + { + "id": "4631101", + "name": "1010", + "instructor": "LEWICKA" + } + ] + }, + { + "name": "MATH 1050", + "id": "MATH1050", + "sections": [ + { + "id": "4631102", + "name": "1040", + "instructor": "CONSTANTINE" + } + ] + }, + { + "name": "MATH 1070", + "id": "MATH1070", + "sections": [ + { + "id": "4631103", + "name": "1040", + "instructor": "LAYTON" + }, + { + "id": "4631104", + "name": "1050", + "instructor": "YOTOV" + } + ] + }, + { + "name": "MATH 1101", + "id": "MATH1101", + "sections": [ + { + "id": "4631105", + "name": "1030", + "instructor": "WHEELER" + }, + { + "id": "4631106", + "name": "1300", + "instructor": "CHOU" + } + ] + }, + { + "name": "MATH 1119", + "id": "MATH1119", + "sections": [ + { + "id": "4631107", + "name": "1010", + "instructor": "YUST" + } + ] + }, + { + "name": "MATH 1122", + "id": "MATH1122", + "sections": [ + { + "id": "4631108", + "name": "1000", + "instructor": "CHADAM" + } + ] + }, + { + "name": "MATH 1126", + "id": "MATH1126", + "sections": [ + { + "id": "4631109", + "name": "1010", + "instructor": "CHADAM" + } + ] + }, + { + "name": "MATH 1180", + "id": "MATH1180", + "sections": [ + { + "id": "4631110", + "name": "1030", + "instructor": "SWIGON" + }, + { + "id": "4631111", + "name": "1050", + "instructor": "CHEN" + } + ] + }, + { + "name": "MATH 1185", + "id": "MATH1185", + "sections": [ + { + "id": "4634385", + "name": "1040", + "instructor": "GILTON" + } + ] + }, + { + "name": "MATH 1270", + "id": "MATH1270", + "sections": [ + { + "id": "4631112", + "name": "1040", + "instructor": "SWIGON" + }, + { + "id": "4631113", + "name": "1050", + "instructor": "VAINCHTEIN" + } + ] + }, + { + "name": "MATH 1290", + "id": "MATH1290", + "sections": [ + { + "id": "4631114", + "name": "1100", + "instructor": "DEBLOIS" + } + ] + }, + { + "name": "MATH 1360", + "id": "MATH1360", + "sections": [ + { + "id": "4631115", + "name": "1040", + "instructor": "ERMENTROUT" + } + ] + }, + { + "name": "MATH 1470", + "id": "MATH1470", + "sections": [ + { + "id": "4631116", + "name": "1070", + "instructor": "CHEN" + } + ] + }, + { + "name": "MATH 1530", + "id": "MATH1530", + "sections": [ + { + "id": "4590464", + "name": "1060", + "instructor": "HAJLASZ" + }, + { + "id": "4631117", + "name": "1230", + "instructor": "SCHIKORRA" + } + ] + }, + { + "name": "MATH 1550", + "id": "MATH1550", + "sections": [ + { + "id": "4631118", + "name": "1040", + "instructor": "LENNARD" + } + ] + }, + { + "name": "MATH 1900", + "id": "MATH1900", + "sections": [ + { + "id": "4561833", + "name": "1100", + "instructor": "DEBLOIS" + } + ] + }, + { + "name": "MATH 1902", + "id": "MATH1902", + "sections": [ + { + "id": "4561834", + "name": "1030", + "instructor": "HALES" + }, + { + "id": "4561835", + "name": "1040", + "instructor": "SCHIKORRA" + }, + { + "id": "4561836", + "name": "1050", + "instructor": "LAYTON" + }, + { + "id": "4561837", + "name": "1060", + "instructor": "GILTON" + }, + { + "id": "4561838", + "name": "1070", + "instructor": "DEBLOIS" + }, + { + "id": "4561839", + "name": "1080", + "instructor": "CONSTANTINE" + } + ] + }, + { + "name": "MATH 2000", + "id": "MATH2000", + "sections": [ + { + "id": "4561840", + "name": "1010", + "instructor": "NEILAN" + } + ] + }, + { + "name": "MATH 2060", + "id": "MATH2060", + "sections": [ + { + "id": "4590465", + "name": "1200", + "instructor": "CONSTANTINE" + } + ] + }, + { + "name": "MATH 2070", + "id": "MATH2070", + "sections": [ + { + "id": "4590466", + "name": "1040", + "instructor": "NEILAN" + }, + { + "id": "4631119", + "name": "1030", + "instructor": "NEILAN" + } + ] + }, + { + "name": "MATH 2090", + "id": "MATH2090", + "sections": [ + { + "id": "4590467", + "name": "1100", + "instructor": "TRENCHEA" + } + ] + }, + { + "name": "MATH 2301", + "id": "MATH2301", + "sections": [ + { + "id": "4590468", + "name": "1040", + "instructor": "LENNARD" + } + ] + }, + { + "name": "MATH 2303", + "id": "MATH2303", + "sections": [ + { + "id": "4590469", + "name": "1060", + "instructor": "LEWICKA" + } + ] + }, + { + "name": "MATH 2370", + "id": "MATH2370", + "sections": [ + { + "id": "4590470", + "name": "1040", + "instructor": "WOJTOWYTSCH" + } + ] + }, + { + "name": "MATH 2500", + "id": "MATH2500", + "sections": [ + { + "id": "4590471", + "name": "1100", + "instructor": "WANG ERICKSON" + } + ] + }, + { + "name": "MATH 2505", + "id": "MATH2505", + "sections": [ + { + "id": "4590472", + "name": "1200", + "instructor": "ION" + } + ] + }, + { + "name": "MATH 2603", + "id": "MATH2603", + "sections": [ + { + "id": "4590473", + "name": "1200", + "instructor": "YOTOV" + } + ] + }, + { + "name": "MATH 2800", + "id": "MATH2800", + "sections": [ + { + "id": "4590474", + "name": "1100", + "instructor": "FEDOROV" + } + ] + }, + { + "name": "MATH 2900", + "id": "MATH2900", + "sections": [ + { + "id": "4590475", + "name": "1000", + "instructor": "VAINCHTEIN" + } + ] + }, + { + "name": "MATH 2920", + "id": "MATH2920", + "sections": [ + { + "id": "4590476", + "name": "1070", + "instructor": "RUBIN" + } + ] + }, + { + "name": "MATH 2990", + "id": "MATH2990", + "sections": [ + { + "id": "4561841", + "name": "1010", + "instructor": "NEILAN" + } + ] + }, + { + "name": "MATH 3000", + "id": "MATH3000", + "sections": [ + { + "id": "4561842", + "name": "1010", + "instructor": "NEILAN" + } + ] + }, + { + "name": "MATH 3225", + "id": "MATH3225", + "sections": [ + { + "id": "4590477", + "name": "1010", + "instructor": "YAO" + } + ] + }, + { + "name": "MATH 3900", + "id": "MATH3900", + "sections": [ + { + "id": "4561843", + "name": "1010", + "instructor": "NEILAN" + } + ] + }, + { + "name": "MATH 3902", + "id": "MATH3902", + "sections": [ + { + "id": "4561844", + "name": "1010", + "instructor": "NEILAN" + } + ] + }, + { + "name": "MATH 3923", + "id": "MATH3923", + "sections": [ + { + "id": "4585743", + "name": "1200", + "instructor": "GALDI" + } + ] + } +] diff --git a/tests/samples/textbook_courses_STAT.json b/tests/samples/textbook_courses_STAT.json deleted file mode 100644 index 1e5c3e4..0000000 --- a/tests/samples/textbook_courses_STAT.json +++ /dev/null @@ -1 +0,0 @@ -[{"name":"STAT 0200","id":"STAT0200","sections":[{"id":"2105836","name":"1030","instructor":"QUESEN"},{"id":"2105837","name":"1035","instructor":null},{"id":"2105838","name":"1040","instructor":null},{"id":"2105839","name":"1045","instructor":null},{"id":"2105840","name":"1050","instructor":null},{"id":"2105841","name":"1055","instructor":"STRASSER"},{"id":"2105842","name":"1060","instructor":null},{"id":"2105843","name":"1065","instructor":null},{"id":"2105844","name":"1070","instructor":null},{"id":"2105845","name":"1075","instructor":null},{"id":"2105846","name":"1080","instructor":"STRASSER"},{"id":"2105847","name":"1085","instructor":null},{"id":"2105848","name":"1090","instructor":null},{"id":"2105849","name":"1095","instructor":null},{"id":"2105850","name":"1100","instructor":null},{"id":"2105851","name":"1105","instructor":"LUTZ"},{"id":"2105852","name":"1110","instructor":null},{"id":"2105853","name":"1115","instructor":null},{"id":"2105854","name":"1120","instructor":null},{"id":"2105855","name":"1125","instructor":null},{"id":"2105856","name":"1130","instructor":"PERRY"},{"id":"2105857","name":"1135","instructor":null},{"id":"2105858","name":"1140","instructor":null},{"id":"2105859","name":"1145","instructor":null},{"id":"2105860","name":"1150","instructor":null},{"id":"2105861","name":"7000","instructor":"HARMON"},{"id":"2105862","name":"7010","instructor":"KIM"},{"id":"2105863","name":"7020","instructor":"KIM"},{"id":"2105864","name":"7030","instructor":"KIM"},{"id":"2105865","name":"7510","instructor":"HARMON"}]},{"name":"STAT 0800","id":"STAT0800","sections":[{"id":"2105866","name":"1030","instructor":"NOROSKI"}]},{"name":"STAT 1000","id":"STAT1000","sections":[{"id":"2105867","name":"1040","instructor":"PFENNING"},{"id":"2105868","name":"1050","instructor":null},{"id":"2105869","name":"1060","instructor":null},{"id":"2105870","name":"1070","instructor":null},{"id":"2105871","name":"1080","instructor":null},{"id":"2105872","name":"1100","instructor":"WANG"},{"id":"2105873","name":"1110","instructor":null},{"id":"2105874","name":"1120","instructor":null},{"id":"2105875","name":"1130","instructor":null},{"id":"2105876","name":"1140","instructor":null},{"id":"2105877","name":"1200","instructor":"PFENNING"},{"id":"2105878","name":"1210","instructor":null},{"id":"2105879","name":"1260","instructor":"KENKEL"},{"id":"2105880","name":"1270","instructor":null},{"id":"2105881","name":"1280","instructor":null},{"id":"2105882","name":"1290","instructor":null},{"id":"2105883","name":"1300","instructor":null},{"id":"2105884","name":"1320","instructor":"YI"},{"id":"2105885","name":"1330","instructor":null},{"id":"2105886","name":"1340","instructor":null},{"id":"2105887","name":"1350","instructor":null},{"id":"2105888","name":"1360","instructor":null},{"id":"2105889","name":"1370","instructor":"WANG"},{"id":"2105890","name":"1380","instructor":null},{"id":"2105891","name":"1390","instructor":null},{"id":"2105892","name":"1400","instructor":null},{"id":"2105893","name":"1410","instructor":null},{"id":"2105894","name":"1420","instructor":"NELSON"},{"id":"2105895","name":"1425","instructor":null},{"id":"2105896","name":"1430","instructor":null},{"id":"2105897","name":"1435","instructor":null},{"id":"2105898","name":"1440","instructor":null}]},{"name":"STAT 1100","id":"STAT1100","sections":[{"id":"2105899","name":"1010","instructor":"NELSON"},{"id":"2105900","name":"1015","instructor":null},{"id":"2105901","name":"1020","instructor":null},{"id":"2105902","name":"1025","instructor":null},{"id":"2105903","name":"1030","instructor":null},{"id":"2105904","name":"1035","instructor":"NELSON"},{"id":"2105905","name":"1040","instructor":null},{"id":"2105906","name":"1045","instructor":null},{"id":"2105907","name":"1050","instructor":null},{"id":"2105908","name":"1055","instructor":null},{"id":"2105909","name":"1060","instructor":"NELSON"},{"id":"2105910","name":"1065","instructor":null},{"id":"2105911","name":"1070","instructor":null},{"id":"2105912","name":"1075","instructor":null},{"id":"2105913","name":"1080","instructor":null},{"id":"2105914","name":"1085","instructor":"BILONICK"},{"id":"2105915","name":"1090","instructor":null},{"id":"2105916","name":"1095","instructor":null},{"id":"2105917","name":"1100","instructor":null},{"id":"2105918","name":"1105","instructor":null},{"id":"2105919","name":"7510","instructor":"CHIAPPETTA"}]},{"name":"STAT 1151","id":"STAT1151","sections":[{"id":"2105920","name":"1055","instructor":"BODENSCHATZ"},{"id":"2105921","name":"1060","instructor":"BODENSCHATZ"}]},{"name":"STAT 1211","id":"STAT1211","sections":[{"id":"2105922","name":"1010","instructor":"BILONICK"}]},{"name":"STAT 1291","id":"STAT1291","sections":[{"id":"2105923","name":"1010","instructor":"JUNG"},{"id":"2105924","name":"1015","instructor":"JUNG"},{"id":"2105925","name":"1020","instructor":"JUNG"}]},{"name":"STAT 1321","id":"STAT1321","sections":[{"id":"2105926","name":"1060","instructor":"STOFFER"}]},{"name":"STAT 1631","id":"STAT1631","sections":[{"id":"2105927","name":"1030","instructor":"REN"}]},{"name":"STAT 1900","id":"STAT1900","sections":[{"id":"2105928","name":"1010","instructor":"IYENGAR"},{"id":"2105929","name":"1020","instructor":"CHENG"},{"id":"2105930","name":"1025","instructor":"SAMPSON"},{"id":"2105931","name":"1035","instructor":null},{"id":"2105932","name":"1040","instructor":"STOFFER"},{"id":"2105933","name":"1045","instructor":"JUNG"},{"id":"2105934","name":"1050","instructor":"CHEN"},{"id":"2105935","name":"1060","instructor":"REN"},{"id":"2105936","name":"1070","instructor":"MENTCH"}]},{"name":"STAT 1902","id":"STAT1902","sections":[{"id":"2105937","name":"1015","instructor":"IYENGAR"},{"id":"2105938","name":"1030","instructor":"CHENG"},{"id":"2105939","name":"1035","instructor":"SAMPSON"},{"id":"2105940","name":"1045","instructor":"MENTCH"},{"id":"2105941","name":"1050","instructor":"STOFFER"},{"id":"2105942","name":"1055","instructor":"JUNG"},{"id":"2105943","name":"1060","instructor":"CHEN"},{"id":"2105944","name":"1070","instructor":"REN"},{"id":"2105945","name":"1080","instructor":"MENTCH"}]},{"name":"STAT 2001","id":"STAT2001","sections":[{"id":"2105946","name":"1010","instructor":"IYENGAR"},{"id":"2105947","name":"1025","instructor":"CHENG"},{"id":"2105948","name":"1030","instructor":"SAMPSON"},{"id":"2105949","name":"1040","instructor":null},{"id":"2105950","name":"1045","instructor":"STOFFER"},{"id":"2105951","name":"1050","instructor":"JUNG"},{"id":"2105952","name":"1055","instructor":"CHEN"},{"id":"2105953","name":"1060","instructor":"REN"},{"id":"2105954","name":"1070","instructor":"MENTCH"}]},{"name":"STAT 2020","id":"STAT2020","sections":[{"id":"2105955","name":"1010","instructor":"PFENNING"}]},{"name":"STAT 2131","id":"STAT2131","sections":[{"id":"2105956","name":"1030","instructor":"CHEN"}]},{"name":"STAT 2210","id":"STAT2210","sections":[{"id":"2105957","name":"1010","instructor":"BILONICK"}]},{"name":"STAT 2320","id":"STAT2320","sections":[{"id":"2105958","name":"1060","instructor":"STOFFER"}]},{"name":"STAT 2321","id":"STAT2321","sections":[{"id":"2105959","name":"1210","instructor":"STOFFER"}]},{"name":"STAT 2381","id":"STAT2381","sections":[{"id":"2105960","name":"1030","instructor":"SAMPSON"}]},{"name":"STAT 2391","id":"STAT2391","sections":[{"id":"2105961","name":"1010","instructor":"IYENGAR"}]},{"name":"STAT 2392","id":"STAT2392","sections":[{"id":"2105962","name":"1010","instructor":null}]},{"name":"STAT 2630","id":"STAT2630","sections":[{"id":"2105963","name":"1030","instructor":"REN"}]},{"name":"STAT 2631","id":"STAT2631","sections":[{"id":"2105964","name":"1060","instructor":"JUNG"}]},{"name":"STAT 2661","id":"STAT2661","sections":[{"id":"2147518","name":"1010","instructor":"SAMPSON"}]},{"name":"STAT 2691","id":"STAT2691","sections":[{"id":"2105965","name":"1010","instructor":"REN"}]},{"name":"STAT 2711","id":"STAT2711","sections":[{"id":"2105966","name":"1020","instructor":"IYENGAR"}]},{"name":"STAT 2900","id":"STAT2900","sections":[{"id":"2105967","name":"1010","instructor":"IYENGAR"},{"id":"2105968","name":"1020","instructor":"CHENG"},{"id":"2105969","name":"1025","instructor":"SAMPSON"},{"id":"2105970","name":"1035","instructor":null},{"id":"2105971","name":"1040","instructor":"STOFFER"},{"id":"2105972","name":"1045","instructor":"JUNG"},{"id":"2105973","name":"1050","instructor":"CHEN"},{"id":"2105974","name":"1060","instructor":"REN"},{"id":"2105975","name":"1070","instructor":"MENTCH"}]},{"name":"STAT 2991","id":"STAT2991","sections":[{"id":"2105976","name":"1010","instructor":"IYENGAR"},{"id":"2105977","name":"1025","instructor":"CHENG"},{"id":"2105978","name":"1030","instructor":"SAMPSON"},{"id":"2105979","name":"1040","instructor":null},{"id":"2105980","name":"1045","instructor":"STOFFER"},{"id":"2105981","name":"1050","instructor":"JUNG"},{"id":"2105982","name":"1055","instructor":"CHEN"},{"id":"2105983","name":"1060","instructor":"REN"},{"id":"2105984","name":"1070","instructor":"MENTCH"}]},{"name":"STAT 3001","id":"STAT3001","sections":[{"id":"2105985","name":"1010","instructor":"IYENGAR"},{"id":"2105986","name":"1025","instructor":"CHENG"},{"id":"2105987","name":"1030","instructor":"SAMPSON"},{"id":"2105988","name":"1040","instructor":null},{"id":"2105989","name":"1045","instructor":"STOFFER"},{"id":"2105990","name":"1050","instructor":"JUNG"},{"id":"2105991","name":"1055","instructor":"CHEN"},{"id":"2105992","name":"1060","instructor":"REN"},{"id":"2105993","name":"1070","instructor":"MENTCH"}]},{"name":"STAT 3901","id":"STAT3901","sections":[{"id":"2105994","name":"1030","instructor":"IYENGAR"},{"id":"2105995","name":"1060","instructor":"CHENG"},{"id":"2105996","name":"1070","instructor":"SAMPSON"},{"id":"2105997","name":"1090","instructor":null},{"id":"2105998","name":"1100","instructor":"STOFFER"},{"id":"2105999","name":"1105","instructor":"JUNG"},{"id":"2106000","name":"1110","instructor":"CHEN"},{"id":"2106001","name":"1120","instructor":"REN"},{"id":"2106002","name":"1130","instructor":"MENTCH"}]}] diff --git a/tests/samples/textbook_subjects.json b/tests/samples/textbook_subjects.json new file mode 100644 index 0000000..1a87a52 --- /dev/null +++ b/tests/samples/textbook_subjects.json @@ -0,0 +1,674 @@ +[ + { + "id": "98224", + "name": "*GOGGLES" + }, + { + "id": "98225", + "name": "*LABCOATS" + }, + { + "id": "98226", + "name": "*NUR KITS" + }, + { + "id": "22401", + "name": "AFRCNA" + }, + { + "id": "22403", + "name": "ANTH" + }, + { + "id": "22404", + "name": "ARABIC" + }, + { + "id": "28594", + "name": "ARC" + }, + { + "id": "22405", + "name": "ARTSC" + }, + { + "id": "22406", + "name": "ASL" + }, + { + "id": "22407", + "name": "ASTRON" + }, + { + "id": "22408", + "name": "ATHLTR" + }, + { + "id": "22409", + "name": "BACC" + }, + { + "id": "22410", + "name": "BCHS" + }, + { + "id": "80803", + "name": "BCMS" + }, + { + "id": "22411", + "name": "BECN" + }, + { + "id": "22412", + "name": "BFIN" + }, + { + "id": "22414", + "name": "BIND" + }, + { + "id": "22415", + "name": "BIOENG" + }, + { + "id": "22416", + "name": "BIOETH" + }, + { + "id": "22417", + "name": "BIOINF" + }, + { + "id": "22418", + "name": "BIOSC" + }, + { + "id": "22419", + "name": "BIOST" + }, + { + "id": "22420", + "name": "BMIS" + }, + { + "id": "22421", + "name": "BMKT" + }, + { + "id": "22424", + "name": "BQOM" + }, + { + "id": "22425", + "name": "BSEO" + }, + { + "id": "22426", + "name": "BSPP" + }, + { + "id": "22428", + "name": "BUSACC" + }, + { + "id": "22430", + "name": "BUSBIS" + }, + { + "id": "22431", + "name": "BUSECN" + }, + { + "id": "22432", + "name": "BUSENV" + }, + { + "id": "22433", + "name": "BUSERV" + }, + { + "id": "22434", + "name": "BUSFIN" + }, + { + "id": "22435", + "name": "BUSHRM" + }, + { + "id": "22436", + "name": "BUSMKT" + }, + { + "id": "22437", + "name": "BUSORG" + }, + { + "id": "22438", + "name": "BUSQOM" + }, + { + "id": "22439", + "name": "BUSSCM" + }, + { + "id": "22440", + "name": "BUSSPP" + }, + { + "id": "22441", + "name": "CDACCT" + }, + { + "id": "22442", + "name": "CDENT" + }, + { + "id": "22443", + "name": "CEE" + }, + { + "id": "22444", + "name": "CGS" + }, + { + "id": "22445", + "name": "CHE" + }, + { + "id": "22446", + "name": "CHEM" + }, + { + "id": "22447", + "name": "CHIN" + }, + { + "id": "22448", + "name": "CLASS" + }, + { + "id": "22449", + "name": "CLRES" + }, + { + "id": "22452", + "name": "CMPBIO" + }, + { + "id": "32207", + "name": "CMPINF" + }, + { + "id": "22456", + "name": "COMMRC" + }, + { + "id": "26403", + "name": "COUN" + }, + { + "id": "22457", + "name": "CS" + }, + { + "id": "22458", + "name": "CSD" + }, + { + "id": "22459", + "name": "DENHYG" + }, + { + "id": "22463", + "name": "DSPHL" + }, + { + "id": "22466", + "name": "ECE" + }, + { + "id": "22467", + "name": "ECON" + }, + { + "id": "22468", + "name": "EDUC" + }, + { + "id": "44670", + "name": "EFOP" + }, + { + "id": "22469", + "name": "ELI" + }, + { + "id": "22470", + "name": "EM" + }, + { + "id": "22472", + "name": "ENGCMP" + }, + { + "id": "22473", + "name": "ENGFLM" + }, + { + "id": "22474", + "name": "ENGLIT" + }, + { + "id": "22475", + "name": "ENGR" + }, + { + "id": "22476", + "name": "ENGSCI" + }, + { + "id": "22477", + "name": "ENGWRT" + }, + { + "id": "22479", + "name": "EOH" + }, + { + "id": "22480", + "name": "EPIDEM" + }, + { + "id": "22481", + "name": "FACDEV" + }, + { + "id": "26401", + "name": "FMST" + }, + { + "id": "22484", + "name": "FP" + }, + { + "id": "22485", + "name": "FR" + }, + { + "id": "22491", + "name": "GEOL" + }, + { + "id": "22492", + "name": "GER" + }, + { + "id": "22493", + "name": "GERON" + }, + { + "id": "22494", + "name": "GREEK" + }, + { + "id": "22495", + "name": "GREEKM" + }, + { + "id": "22496", + "name": "GSWS" + }, + { + "id": "22497", + "name": "HAA" + }, + { + "id": "23736", + "name": "HEBREW" + }, + { + "id": "61591", + "name": "HHD" + }, + { + "id": "26198", + "name": "HI" + }, + { + "id": "22499", + "name": "HINDI" + }, + { + "id": "22500", + "name": "HIST" + }, + { + "id": "22501", + "name": "HONORS" + }, + { + "id": "22503", + "name": "HPM" + }, + { + "id": "22504", + "name": "HPS" + }, + { + "id": "22505", + "name": "HRS" + }, + { + "id": "22506", + "name": "HUGEN" + }, + { + "id": "27731", + "name": "HUN" + }, + { + "id": "22508", + "name": "IE" + }, + { + "id": "22511", + "name": "INFSCI" + }, + { + "id": "22512", + "name": "INTBP" + }, + { + "id": "22513", + "name": "IRISH" + }, + { + "id": "22516", + "name": "ITAL" + }, + { + "id": "22517", + "name": "JPNSE" + }, + { + "id": "22518", + "name": "JS" + }, + { + "id": "22519", + "name": "KOREAN" + }, + { + "id": "22520", + "name": "LATIN" + }, + { + "id": "22521", + "name": "LAW" + }, + { + "id": "61465", + "name": "LCJS" + }, + { + "id": "22522", + "name": "LCTL" + }, + { + "id": "22523", + "name": "LDRSHP" + }, + { + "id": "22525", + "name": "LING" + }, + { + "id": "22526", + "name": "LIS" + }, + { + "id": "22528", + "name": "MATH" + }, + { + "id": "22529", + "name": "ME" + }, + { + "id": "22531", + "name": "MEDEDU" + }, + { + "id": "22532", + "name": "MEMS" + }, + { + "id": "22535", + "name": "MSCBIO" + }, + { + "id": "22537", + "name": "MSCMP" + }, + { + "id": "22538", + "name": "MSE" + }, + { + "id": "22544", + "name": "MSNBIO" + }, + { + "id": "22545", + "name": "MUSIC" + }, + { + "id": "22548", + "name": "NROSCI" + }, + { + "id": "64465", + "name": "NUCE" + }, + { + "id": "22549", + "name": "NUR" + }, + { + "id": "22550", + "name": "NURCNS" + }, + { + "id": "22552", + "name": "NURNP" + }, + { + "id": "22553", + "name": "NURSAN" + }, + { + "id": "22554", + "name": "NURSP" + }, + { + "id": "22555", + "name": "NUTR" + }, + { + "id": "22560", + "name": "OT" + }, + { + "id": "22561", + "name": "PAS" + }, + { + "id": "22565", + "name": "PERS" + }, + { + "id": "22566", + "name": "PETE" + }, + { + "id": "22567", + "name": "PHARM" + }, + { + "id": "22568", + "name": "PHIL" + }, + { + "id": "22569", + "name": "PHYS" + }, + { + "id": "22570", + "name": "PIA" + }, + { + "id": "37740", + "name": "PITT" + }, + { + "id": "22571", + "name": "POLISH" + }, + { + "id": "22572", + "name": "PORT" + }, + { + "id": "22574", + "name": "PS" + }, + { + "id": "22575", + "name": "PSY" + }, + { + "id": "22578", + "name": "PT" + }, + { + "id": "22579", + "name": "PUBHLT" + }, + { + "id": "22580", + "name": "PUBSRV" + }, + { + "id": "22582", + "name": "REHSCI" + }, + { + "id": "22583", + "name": "REL" + }, + { + "id": "22584", + "name": "RELGST" + }, + { + "id": "22585", + "name": "RESTD" + }, + { + "id": "63601", + "name": "RT" + }, + { + "id": "22586", + "name": "RUSS" + }, + { + "id": "22587", + "name": "SA" + }, + { + "id": "22589", + "name": "SLAV" + }, + { + "id": "22590", + "name": "SLOVAK" + }, + { + "id": "22591", + "name": "SOC" + }, + { + "id": "22592", + "name": "SOCWRK" + }, + { + "id": "22593", + "name": "SPAN" + }, + { + "id": "22594", + "name": "STAT" + }, + { + "id": "22595", + "name": "SWAHIL" + }, + { + "id": "22596", + "name": "SWBEH" + }, + { + "id": "22598", + "name": "SWCOSA" + }, + { + "id": "22599", + "name": "SWE" + }, + { + "id": "22600", + "name": "SWGEN" + }, + { + "id": "22601", + "name": "SWINT" + }, + { + "id": "22602", + "name": "SWRES" + }, + { + "id": "22603", + "name": "SWWEL" + }, + { + "id": "22604", + "name": "TELCOM" + }, + { + "id": "22605", + "name": "THEA" + }, + { + "id": "61590", + "name": "TLL" + }, + { + "id": "22606", + "name": "TURKSH" + }, + { + "id": "22607", + "name": "UKRAIN" + }, + { + "id": "22608", + "name": "URBNST" + }, + { + "id": "38025", + "name": "ZZZ" + } +] diff --git a/tests/samples/textbook_textbooks_CS_0441_garrison.json b/tests/samples/textbook_textbooks_CS_0441_garrison.json new file mode 100644 index 0000000..ac59d62 --- /dev/null +++ b/tests/samples/textbook_textbooks_CS_0441_garrison.json @@ -0,0 +1,69 @@ +[ + { + "id": "4558031_BSZWEWZWMZYJ_0", + "isbn": "BSZWEWZWMZYJ", + "pf_id": null, + "new_item_id": "2603309:N", + "used_item_id": null, + "required": "Inclusive Access", + "sort_order": [ + "0", + "2", + "Ia Canvas Content" + ], + "cover_image_url": "//vst-coverimages.verbacompete.com/no_image.jpg", + "title": "Ia Canvas Content", + "author": "Redshelf Ia", + "notes": null, + "citation": "Ia Canvas Content by Redshelf Ia. (ISBN: BSZWEWZWMZYJ).", + "metadata": { + "section_id": "4558031", + "section_code": "22840", + "term_name": "Fall 24" + }, + "offers": [ + { + "isbn": "BSZWEWZWMZYJ", + "retailer": "bookstore", + "item_id": "2603309:N", + "condition": "new", + "title": null, + "currency": null, + "merchant": "bookstore", + "rental_days": null, + "retailer_name": "University Store", + "metadata": null, + "in_cart": null, + "selected": null, + "total": "45.75", + "retailer_order": 0, + "lowest_price": null, + "highest_price": null, + "comments": "Books Arriving Every Day-Check Frequently!", + "special_comment": "Pick it up or ship it!", + "store_branded": true, + "data_source": "bookstore", + "description": "From University Store on Fifth", + "seller_rating": null, + "fcondition": "New", + "fprice": "$45.75", + "discounted_shipping": null, + "can_checkout": false, + "feedback_count": null, + "location": null, + "price": "45.75", + "shipping": 0, + "variant": "new", + "source": "default", + "id": "bookstore_BSZWEWZWMZYJ_new_2603309:N" + } + ], + "section_id": null, + "db_section_id": null, + "inclusive_access": false, + "catalog_id": null, + "edition": null, + "copyright_year": null, + "binding": null + } +] diff --git a/tests/samples/textbook_textbooks_MATH_0430_pan.json b/tests/samples/textbook_textbooks_MATH_0430_pan.json new file mode 100644 index 0000000..38df6bf --- /dev/null +++ b/tests/samples/textbook_textbooks_MATH_0430_pan.json @@ -0,0 +1,104 @@ +[ + { + "id": "4631097_9780201763904_0", + "isbn": "9780201763904", + "pf_id": null, + "new_item_id": "1445331:N", + "used_item_id": "1445331:U", + "required": "Required", + "sort_order": [ + "0", + "1", + "First Course In Abstract Algebra" + ], + "cover_image_url": "//vst-coverimages.verbacompete.com/33f017cc-33d1-5c4c-b8e0-122cb182fa8a.jpg", + "title": "First Course In Abstract Algebra", + "author": "Fraleigh", + "notes": null, + "citation": "\u003cem\u003eFirst Course In Abstract Algebra\u003c/em\u003e by Fraleigh. Pearson Education, 7th Edition, 2002. (ISBN: 9780201763904).", + "metadata": { + "section_id": "4631097", + "section_code": "11295", + "term_name": "Fall 24" + }, + "offers": [ + { + "isbn": "9780201763904", + "retailer": "bookstore", + "item_id": "1445331:N", + "condition": "new", + "title": null, + "currency": null, + "merchant": "bookstore", + "rental_days": null, + "retailer_name": "University Store", + "metadata": null, + "in_cart": null, + "selected": null, + "total": "206.65", + "retailer_order": 0, + "lowest_price": null, + "highest_price": null, + "comments": "Books Arriving Every Day-Check Frequently!", + "special_comment": "Pick it up or ship it!", + "store_branded": true, + "data_source": "bookstore", + "description": "From University Store on Fifth", + "seller_rating": null, + "fcondition": "New", + "fprice": "$206.65", + "discounted_shipping": null, + "can_checkout": false, + "feedback_count": null, + "location": null, + "price": "206.65", + "shipping": 0, + "variant": "new", + "source": "default", + "id": "bookstore_9780201763904_new_1445331:N" + }, + { + "isbn": "9780201763904", + "retailer": "bookstore", + "item_id": "1445331:U", + "condition": "used", + "title": null, + "currency": null, + "merchant": "bookstore", + "rental_days": null, + "retailer_name": "University Store", + "metadata": null, + "in_cart": null, + "selected": null, + "total": "155.0", + "retailer_order": 0, + "lowest_price": null, + "highest_price": null, + "comments": "Pick it up or ship it!", + "special_comment": "Pick it up or ship it!", + "store_branded": true, + "data_source": "bookstore", + "description": "From University Store on Fifth", + "seller_rating": null, + "fcondition": "Used", + "fprice": "$155.00", + "discounted_shipping": null, + "can_checkout": true, + "feedback_count": null, + "location": null, + "price": "155.0", + "shipping": 0, + "variant": "used", + "source": "default", + "id": "bookstore_9780201763904_used_1445331:U" + } + ], + "section_id": null, + "db_section_id": null, + "inclusive_access": false, + "catalog_id": null, + "edition": "7", + "copyright_year": "2003", + "binding": null + } +] diff --git a/tests/textbook_test.py b/tests/textbook_test.py index 5f8b2b7..88b95a7 100644 --- a/tests/textbook_test.py +++ b/tests/textbook_test.py @@ -17,192 +17,406 @@ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. """ +from pittapi import textbook + import responses import json import unittest from pathlib import Path +from pytest import mark +from requests import ConnectionError +from typing import Any -from pittapi import textbook - -TERM = "1000" SAMPLE_PATH = Path() / "tests" / "samples" - -print(SAMPLE_PATH.absolute()) +CSRF_TOKEN = "1MTtTVOcQCCXDjKNKTqkfiwp0lmLWz1RvFy2ed65XeyGO4on-8zWsQpEAt4cjiH0glx9CIyjhAOKpXhIqDK_vg" +CS_SUBJECT_ID = "22457" +MATH_SUBJECT_ID = "22528" +CS_0441_GARRISON_SECTION_ID = "4558031" +MATH_0430_PAN_SECTION_ID = "4631097" class TextbookTest(unittest.TestCase): def __init__(self, *args, **kwargs): unittest.TestCase.__init__(self, *args, **kwargs) - self.validate_term = textbook._validate_term - self.validate_course = textbook._validate_course + with (SAMPLE_PATH / "textbook_base_page.html").open() as f: + self.html_text = f.read() + with (SAMPLE_PATH / "textbook_subjects.json").open() as f: + self.subjects_data = json.load(f) with (SAMPLE_PATH / "textbook_courses_CS.json").open() as f: self.cs_data = json.load(f) - with (SAMPLE_PATH / "textbook_courses_STAT.json").open() as f: - self.stat_data = json.load(f) + with (SAMPLE_PATH / "textbook_courses_MATH.json").open() as f: + self.math_data = json.load(f) + with (SAMPLE_PATH / "textbook_textbooks_CS_0441_garrison.json").open() as f: + self.cs_0441_textbook_data: list[dict[str, Any]] = json.load(f) + with (SAMPLE_PATH / "textbook_textbooks_MATH_0430_pan.json").open() as f: + self.math_0430_textbook_data: list[dict[str, Any]] = json.load(f) - @responses.activate - def test_textbook_get_textbook(self): + def setUp(self): + textbook.request_headers = None + textbook.subject_map = None + responses.start() + + def tearDown(self): + responses.stop() + responses.reset() + + def mock_base_site_success(self): + responses.add(responses.GET, "https://pitt.verbacompare.com/", body=self.html_text) + + def mock_base_site_failure(self): + responses.add(responses.GET, "https://pitt.verbacompare.com/", status=400) + + def mock_subject_map_success(self): responses.add( responses.GET, - "http://pitt.verbacompare.com/compare/courses/?id=22457&term_id=1000", - json=self.cs_data, - status=200, + f"https://pitt.verbacompare.com/compare/departments/?term={textbook.CURRENT_TERM_ID}", + json=self.subjects_data, ) - instructor_test = textbook.get_textbook(term=TERM, department="CS", course="445", instructor="GARRISON III") - instructor_test = textbook.get_textbook(term=TERM, department="CS", course="445", instructor="GARRISON III") - section_test = textbook.get_textbook(term=TERM, department="CS", course="445", section="1030") - self.assertIsInstance(instructor_test, list) - self.assertIsInstance(section_test, list) + def mock_subject_map_failure(self): + responses.add( + responses.GET, f"https://pitt.verbacompare.com/compare/departments/?term={textbook.CURRENT_TERM_ID}", status=400 + ) - @responses.activate - def test_textbook_get_textbooks(self): + def mock_cs_courses_success(self): responses.add( responses.GET, - "http://pitt.verbacompare.com/compare/courses/?id=22457&term_id=1000", + f"https://pitt.verbacompare.com/compare/courses/?id={CS_SUBJECT_ID}&term_id={textbook.CURRENT_TERM_ID}", json=self.cs_data, - status=200, ) + + def mock_cs_courses_failure(self): responses.add( responses.GET, - "http://pitt.verbacompare.com/compare/courses/?id=22594&term_id=1000", - json=self.stat_data, - status=200, + f"https://pitt.verbacompare.com/compare/courses/?id={CS_SUBJECT_ID}&term_id={textbook.CURRENT_TERM_ID}", + status=400, ) - multi_book_test = textbook.get_textbooks( - term=TERM, - courses=[ - {"department": "STAT", "course": "1000", "instructor": "WANG"}, - {"department": "CS", "course": "445", "section": "1030"}, - ], - ) - self.assertIsInstance(multi_book_test, list) - @responses.activate - def test_get_textbook_invalid_term(self): + def mock_cs_0441_garrison_books_success(self): responses.add( responses.GET, - "http://pitt.verbacompare.com/compare/courses/?id=22457&term_id=1000", - json=self.cs_data, - status=200, + f"https://pitt.verbacompare.com/compare/books?id={CS_0441_GARRISON_SECTION_ID}", + json=self.cs_0441_textbook_data, ) - self.assertRaises(TypeError, textbook.get_textbook, "0000", "CS", "401") - @responses.activate - def test_get_textbook_invalid_subject(self): - # TODO(@azharichenko): Added better subject verification in future update + def mock_cs_0441_garrison_books_none(self): + responses.add(responses.GET, f"https://pitt.verbacompare.com/compare/books?id={CS_0441_GARRISON_SECTION_ID}", json=[]) + + def mock_math_courses_success(self): responses.add( responses.GET, - "http://pitt.verbacompare.com/compare/courses/?id=22457&term_id=1000", - json=self.cs_data, - status=200, - ) - self.assertRaises( - ValueError, - textbook.get_textbook, - TERM, - "Computer Science", - "000", - "EXIST", - None, + f"https://pitt.verbacompare.com/compare/courses/?id={MATH_SUBJECT_ID}&term_id={textbook.CURRENT_TERM_ID}", + json=self.math_data, ) - @responses.activate - def test_get_textbook_invalid_instructor(self): + def mock_math_courses_failure(self): responses.add( responses.GET, - "http://pitt.verbacompare.com/compare/courses/?id=22457&term_id=1000", - json=self.cs_data, - status=200, + f"https://pitt.verbacompare.com/compare/courses/?id={MATH_SUBJECT_ID}&term_id={textbook.CURRENT_TERM_ID}", + status=400, ) - self.assertRaises(LookupError, textbook.get_textbook, TERM, "CS", "447", "EXIST", None) - @responses.activate - def test_get_textbook_invalid_section(self): + def mock_math_0430_pan_books_success(self): responses.add( responses.GET, - "http://pitt.verbacompare.com/compare/courses/?id=22457&term_id=1000", - json=self.cs_data, - status=200, + f"https://pitt.verbacompare.com/compare/books?id={MATH_0430_PAN_SECTION_ID}", + json=self.math_0430_textbook_data, ) - self.assertRaises(LookupError, textbook.get_textbook, TERM, "CS", "401", None, "9999") + def test_course_info(self): + self.mock_base_site_success() + self.mock_subject_map_success() + subject, course_num, instructor, section_num = "CS", "0441", "GARRISON III", "1245" + + course = textbook.CourseInfo(subject, course_num, instructor, section_num) + + self.assertEqual(course.subject, subject) + self.assertEqual(course.course_num, course_num) + self.assertEqual(course.instructor, instructor) + self.assertEqual(course.section_num, section_num) + + def test_course_info_convert_input(self): + self.mock_base_site_success() + self.mock_subject_map_success() + subject, course_num, instructor, section_num = "cs", "441", "garrison iii", "1245" + + course = textbook.CourseInfo(subject, course_num, instructor, section_num) + + self.assertEqual(course.subject, "CS") + self.assertEqual(course.course_num, "0441") + self.assertEqual(course.instructor, "GARRISON III") + self.assertEqual(course.section_num, section_num) + + def test_course_info_missing_instructor_and_section_num(self): + self.mock_base_site_success() + self.mock_subject_map_success() + subject, course_num = "cs", "0441" + + course = textbook.CourseInfo(subject, course_num) + + self.assertEqual(course.subject, "CS") + self.assertEqual(course.course_num, "0441") + self.assertIsNone(course.instructor) + self.assertIsNone(course.section_num) + + def test_course_info_invalid_subject(self): + self.mock_base_site_success() + self.mock_subject_map_success() + subject, course_num, instructor, section_num = "fake_subject", "0441", "GARRISON III", "1245" + + self.assertRaises(LookupError, textbook.CourseInfo, subject, course_num, instructor, section_num) + + def test_course_info_invalid_course_num(self): + self.mock_base_site_success() + self.mock_subject_map_success() + subject, course_num, instructor, section_num = "cs", "abc", "GARRISON III", "1245" + + self.assertRaises(ValueError, textbook.CourseInfo, subject, course_num, instructor, section_num) + + course_num = "44111" + + self.assertRaises(ValueError, textbook.CourseInfo, subject, course_num, instructor, section_num) + + def test_course_info_invalid_section_num(self): + self.mock_base_site_success() + self.mock_subject_map_success() + subject, course_num, instructor, section_num = "cs", "0441", "GARRISON III", "12456" + + self.assertRaises(ValueError, textbook.CourseInfo, subject, course_num, instructor, section_num) + + @mark.filterwarnings("ignore:Attempt") @responses.activate - def test_get_textbook_invalid_section_and_instructor(self): - responses.add( - responses.GET, - "http://pitt.verbacompare.com/compare/courses/?id=22457&term_id=1000", - json=self.cs_data, - status=200, + def test_course_info_failing_header_requests(self): + self.mock_base_site_failure() + + self.assertRaises(ConnectionError, textbook.CourseInfo, "CS", "0441", instructor="GARRISON III") + + @responses.activate + def test_course_info_no_headers(self): + responses.add(responses.GET, "https://pitt.verbacompare.com/", body="") + + self.assertRaises(ConnectionError, textbook.CourseInfo, "CS", "0441", instructor="GARRISON III") + + @mark.filterwarnings("ignore:Attempt") + @responses.activate + def test_course_info_failing_subject_map_requests(self): + self.mock_base_site_success() + self.mock_subject_map_failure() + + self.assertRaises(ConnectionError, textbook.CourseInfo, "CS", "0441", instructor="GARRISON III") + + def test_textbook_from_json(self): + self.assertEqual(len(self.cs_0441_textbook_data), 1) + + textbook_info = textbook.Textbook.from_json(self.cs_0441_textbook_data[0]) + + self.assertIsNotNone(textbook_info) + self.assertEqual(textbook_info.title, "Ia Canvas Content") + self.assertEqual(textbook_info.author, "Redshelf Ia") + self.assertIsNone(textbook_info.edition) + self.assertEqual(textbook_info.isbn, "BSZWEWZWMZYJ") + self.assertEqual(textbook_info.citation, "Ia Canvas Content by Redshelf Ia. (ISBN: BSZWEWZWMZYJ).") + + def test_textbook_from_json_all_empty(self): + emptied_data: dict[str, Any] = self.cs_0441_textbook_data[0].copy() + emptied_data.pop("title") + emptied_data.pop("author") + emptied_data.pop("edition") + emptied_data.pop("isbn") + emptied_data.pop("citation") + + textbook_info = textbook.Textbook.from_json(emptied_data) + + self.assertIsNone(textbook_info) + + @responses.activate + def test_get_textbooks_for_course_section_num(self): + self.mock_base_site_success() + self.mock_subject_map_success() + self.mock_cs_courses_success() + self.mock_cs_0441_garrison_books_success() + course = textbook.CourseInfo("CS", "0441", section_num="1245") + + textbooks = textbook.get_textbooks_for_course(course) + + self.assertEqual(textbook.request_headers, {"X-CSRF-Token": CSRF_TOKEN}) + self.assertEqual(len(textbook.subject_map), 168) + self.assertEqual(textbook.subject_map["CS"], CS_SUBJECT_ID) + self.assertEqual(len(textbooks), 1) + self.assertEqual(textbooks[0].title, "Ia Canvas Content") + self.assertEqual(textbooks[0].author, "Redshelf Ia") + self.assertIsNone(textbooks[0].edition) + self.assertEqual(textbooks[0].isbn, "BSZWEWZWMZYJ") + self.assertEqual(textbooks[0].citation, "Ia Canvas Content by Redshelf Ia. (ISBN: BSZWEWZWMZYJ).") + + @responses.activate + def test_get_textbooks_for_course_invalid_section_num(self): + self.mock_base_site_success() + self.mock_subject_map_success() + self.mock_cs_courses_success() + self.mock_cs_0441_garrison_books_success() + course = textbook.CourseInfo("CS", "0441", section_num="0000") + + self.assertRaises(LookupError, textbook.get_textbooks_for_course, course) + + @responses.activate + def test_get_textbooks_for_course_instructor(self): + self.mock_base_site_success() + self.mock_subject_map_success() + self.mock_cs_courses_success() + self.mock_cs_0441_garrison_books_success() + course = textbook.CourseInfo("CS", "0441", instructor="GARRISON III") + + textbooks = textbook.get_textbooks_for_course(course) + + self.assertEqual(textbook.request_headers, {"X-CSRF-Token": CSRF_TOKEN}) + self.assertEqual(len(textbook.subject_map), 168) + self.assertEqual(textbook.subject_map["CS"], CS_SUBJECT_ID) + self.assertEqual(len(textbooks), 1) + self.assertEqual(textbooks[0].title, "Ia Canvas Content") + self.assertEqual(textbooks[0].author, "Redshelf Ia") + self.assertIsNone(textbooks[0].edition) + self.assertEqual(textbooks[0].isbn, "BSZWEWZWMZYJ") + self.assertEqual(textbooks[0].citation, "Ia Canvas Content by Redshelf Ia. (ISBN: BSZWEWZWMZYJ).") + + @responses.activate + def test_get_textbooks_for_course_invalid_instructor(self): + self.mock_base_site_success() + self.mock_subject_map_success() + self.mock_cs_courses_success() + self.mock_cs_0441_garrison_books_success() + course = textbook.CourseInfo("CS", "0441", instructor="RAMIREZ") + + self.assertRaises(LookupError, textbook.get_textbooks_for_course, course) + + @responses.activate + def test_get_textbooks_for_course_invalid_course(self): + self.mock_base_site_success() + self.mock_subject_map_success() + self.mock_cs_courses_success() + course = textbook.CourseInfo("CS", "0000") + + self.assertRaises(LookupError, textbook.get_textbooks_for_course, course) + + @responses.activate + def test_get_textbooks_for_course_deduce_section(self): + self.mock_base_site_success() + self.mock_subject_map_success() + self.mock_math_courses_success() + self.mock_math_0430_pan_books_success() + course = textbook.CourseInfo("MATH", "0430") + + textbooks = textbook.get_textbooks_for_course(course) + + self.assertEqual(len(textbooks), 1) + self.assertEqual(textbooks[0].title, "First Course In Abstract Algebra") + self.assertEqual(textbooks[0].author, "Fraleigh") + self.assertEqual(textbooks[0].edition, "7") + self.assertEqual(textbooks[0].isbn, "9780201763904") + self.assertEqual( + textbooks[0].citation, + "\u003cem\u003eFirst Course In Abstract Algebra\u003c/em\u003e by Fraleigh. " + "Pearson Education, 7th Edition, 2002. (ISBN: 9780201763904).", ) - self.assertRaises(TypeError, textbook.get_textbook, TERM, "CS", "401", None, None) - - def test_term_validation(self): - self.assertEqual(self.validate_term(TERM), TERM) - - def test_term_validation_invalid(self): - self.assertRaises(ValueError, self.validate_term, "1") - self.assertRaises(ValueError, self.validate_term, "a") - self.assertRaises(ValueError, self.validate_term, "100") - self.assertRaises(ValueError, self.validate_term, "10000") - - def test_validate_course_correct_input(self): - self.assertEqual(self.validate_course("0000"), "0000") - self.assertEqual(self.validate_course("0001"), "0001") - self.assertEqual(self.validate_course("0012"), "0012") - self.assertEqual(self.validate_course("0123"), "0123") - self.assertEqual(self.validate_course("1234"), "1234") - self.assertEqual(self.validate_course("9999"), "9999") - - def test_validate_course_improper_input(self): - self.assertEqual(self.validate_course("0"), "0000") - self.assertEqual(self.validate_course("1"), "0001") - self.assertEqual(self.validate_course("12"), "0012") - self.assertEqual(self.validate_course("123"), "0123") - - def test_validate_course_incorrect_input(self): - self.assertRaises(ValueError, self.validate_course, "") - self.assertRaises(ValueError, self.validate_course, "00000") - self.assertRaises(ValueError, self.validate_course, "11111") - self.assertRaises(ValueError, self.validate_course, "hi") - - def test_construct_query(self): - construct = textbook._construct_query - course_query = "compare/courses/?id=9999&term_id=1111" - book_query = "compare/books?id=9999" - - self.assertEqual(construct("courses", "9999", "1111"), course_query) - self.assertEqual(construct("books", "9999"), book_query) - - def test_find_item(self): - find = textbook._find_item("id", "key", "test") - test_data = [ - {"id": 1, "key": 1}, - {"id": 2, "key": 4}, - {"id": 3, "key": 9}, - {"id": 4, "key": 16}, - {"id": 5, "key": 25}, - ] - for i in range(1, 6): - self.assertEqual(find(test_data, i), i**2) + @responses.activate + def test_get_textbooks_for_course_not_enough_info(self): + self.mock_base_site_success() + self.mock_subject_map_success() + self.mock_cs_courses_success() + course = textbook.CourseInfo("CS", "0441") + + self.assertRaises(LookupError, textbook.get_textbooks_for_course, course) + + @mark.filterwarnings("ignore:Attempt") + @responses.activate + def test_get_textbooks_for_course_failing_courses_requests(self): + self.mock_base_site_success() + self.mock_subject_map_success() + self.mock_cs_courses_failure() + course = textbook.CourseInfo("CS", "0441", instructor="GARRISON III") - self.assertRaises(LookupError, find, test_data, 6) + self.assertRaises(ConnectionError, textbook.get_textbooks_for_course, course) @responses.activate - def test_extract_id(self): + def test_get_textbooks_for_course_no_textbook(self): + self.mock_base_site_success() + self.mock_subject_map_success() + self.mock_cs_courses_success() + self.mock_cs_0441_garrison_books_none() + course = textbook.CourseInfo("CS", "0441", instructor="GARRISON III") + + textbooks = textbook.get_textbooks_for_course(course) + + self.assertEqual(len(textbooks), 0) + + @mark.filterwarnings("ignore:No textbook info found") + @responses.activate + def test_get_textbooks_for_course_textbook_no_info(self): + emptied_data: dict[str, Any] = self.cs_0441_textbook_data[0].copy() + emptied_data.pop("title") + emptied_data.pop("author") + emptied_data.pop("edition") + emptied_data.pop("isbn") + emptied_data.pop("citation") + self.mock_base_site_success() + self.mock_subject_map_success() + self.mock_cs_courses_success() responses.add( - responses.GET, - "http://pitt.verbacompare.com/compare/courses/?id=22457&term_id=1000", - json=self.cs_data, - status=201, + responses.GET, f"https://pitt.verbacompare.com/compare/books?id={CS_0441_GARRISON_SECTION_ID}", json=[emptied_data] ) + course = textbook.CourseInfo("CS", "0441", instructor="GARRISON III") - def test_filter_dictionary(self): - test_dict = {"a": 1, "b": 2, "c": 3} - test_key = ["a", "c"] - self.assertEqual(textbook._filter_dictionary(test_dict, test_key), {"a": 1, "c": 3}) + textbook_info = textbook.get_textbooks_for_course(course) + + self.assertEqual(len(textbook_info), 0) + + @responses.activate + def test_get_textbooks_for_courses(self): + self.mock_base_site_success() + self.mock_subject_map_success() + self.mock_cs_courses_success() + self.mock_math_courses_success() + self.mock_cs_0441_garrison_books_success() + self.mock_math_0430_pan_books_success() + courses = [ + textbook.CourseInfo("CS", "0441", instructor="GARRISON III"), + textbook.CourseInfo("MATH", "0430", instructor="PAN"), + ] + + textbooks = textbook.get_textbooks_for_courses(courses) + + self.assertEqual(len(textbooks), 2) + # Sort to guarantee output order, since the textbook requests are async + textbooks.sort(key=lambda x: x.author if x.author else "") + + self.assertEqual(textbooks[0].title, "First Course In Abstract Algebra") + self.assertEqual(textbooks[0].author, "Fraleigh") + self.assertEqual(textbooks[0].edition, "7") + self.assertEqual(textbooks[0].isbn, "9780201763904") + self.assertEqual( + textbooks[0].citation, + "\u003cem\u003eFirst Course In Abstract Algebra\u003c/em\u003e by Fraleigh. " + "Pearson Education, 7th Edition, 2002. (ISBN: 9780201763904).", + ) + + self.assertEqual(textbooks[1].title, "Ia Canvas Content") + self.assertEqual(textbooks[1].author, "Redshelf Ia") + self.assertIsNone(textbooks[1].edition) + self.assertEqual(textbooks[1].isbn, "BSZWEWZWMZYJ") + self.assertEqual(textbooks[1].citation, "Ia Canvas Content by Redshelf Ia. (ISBN: BSZWEWZWMZYJ).") + + @mark.filterwarnings("ignore:Attempt") + @responses.activate + def test_get_textbooks_for_courses_failing_courses_requests(self): + self.mock_base_site_success() + self.mock_subject_map_success() + self.mock_cs_courses_failure() + self.mock_math_courses_failure() + courses = [ + textbook.CourseInfo("CS", "0441", instructor="GARRISON III"), + textbook.CourseInfo("MATH", "0430", instructor="PAN"), + ] - def test_invalid_department_code(self): - self.assertRaises(ValueError, textbook.get_textbook, TERM, "TEST", "000", "EXIST", None) + self.assertRaises(ConnectionError, textbook.get_textbooks_for_courses, courses)