diff --git a/.github/workflows/live-tests.yml b/.github/workflows/live-tests.yml new file mode 100644 index 0000000..d011641 --- /dev/null +++ b/.github/workflows/live-tests.yml @@ -0,0 +1,30 @@ +name: Run live provider tests + +on: + pull_request: + branches: [ "dev" ] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: live-tests-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + run-live-tests: + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - uses: actions/checkout@v4 + - name: Install uv and Python + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 + with: + python-version: "3.13" + enable-cache: true + - name: Install dependencies + run: uv sync --locked + - name: Test live provider contracts + run: uv run pytest -m live live_tests/ diff --git a/live_tests/test_academic_providers.py b/live_tests/test_academic_providers.py new file mode 100644 index 0000000..32e5ed1 --- /dev/null +++ b/live_tests/test_academic_providers.py @@ -0,0 +1,44 @@ +import pytest + +from pittapi.cal import CalendarClient, Event +from pittapi.course import CourseClient +from pittapi.textbook import TextbookClient + +pytestmark = pytest.mark.live + + +@pytest.mark.parametrize( + "method_name", + [ + "get_academic_calendar", + "get_grades_calendar", + "get_enrollment_calendar", + "get_course_calendar", + "get_graduation_calendar", + ], +) +def test_calendar_feed(method_name): + with CalendarClient() as calendar: + events = getattr(calendar, method_name)() + + assert isinstance(events, tuple) + assert all(isinstance(event, Event) for event in events) + + +def test_course_catalog(): + with CourseClient() as courses: + subject = courses.get_subject_courses("CS") + + assert subject.subject_code == "CS" + assert subject.courses + assert all(course.course_id for course in subject.courses) + + +def test_textbook_terms_and_subjects(): + with TextbookClient() as textbooks: + terms = textbooks.get_terms() + term = next(term for term in terms if term.inquiry_enabled) + textbooks.select_term(term) + textbooks.initialize_subjects() + + assert textbooks.subject_ids diff --git a/live_tests/test_campus_providers.py b/live_tests/test_campus_providers.py new file mode 100644 index 0000000..f20b2f1 --- /dev/null +++ b/live_tests/test_campus_providers.py @@ -0,0 +1,63 @@ +import pytest +import requests + +from pittapi.dining import DiningClient +from pittapi.gym import GymClient +from pittapi.lab import LabClient +from pittapi.laundry import LaundryClient +from pittapi.shuttle import ShuttleClient + +pytestmark = pytest.mark.live + + +def test_dining_locations(): + try: + with DiningClient() as dining: + locations = dining.get_locations() + except requests.HTTPError as error: + if error.response is not None and error.response.status_code == 403: + pytest.xfail("DineOnCampus blocks requests from some CI environments") + raise + + assert locations + assert all(location.id and location.name for location in locations) + + +def test_gym_occupancy(): + with GymClient() as gyms: + occupancy = gyms.get_all_gyms_info() + + assert occupancy + assert all(gym.location_id and gym.facility_id for gym in occupancy) + assert all(gym.current_count >= 0 and gym.total_capacity >= 0 for gym in occupancy) + + +def test_lab_discovery_and_status(): + with LabClient() as labs: + locations = labs.get_locations() + status = labs.get_status(locations[0]) + + counted_computers = ( + status.available_computers + status.off_computers + status.in_use_computers + status.out_of_service_computers + ) + assert locations + assert status.total_computers == counted_computers + + +def test_laundry_discovery_and_machines(): + with LaundryClient() as laundry: + locations = laundry.get_locations() + machines = laundry.get_machine_statuses(locations[0]) + + assert locations + assert isinstance(machines, tuple) + + +def test_shuttle_configuration_and_routes(): + with ShuttleClient() as shuttle: + configuration = shuttle.get_configuration() + routes = shuttle.get_routes(configuration) + + assert configuration.api_key + assert routes + assert all(route.route_id for route in routes) diff --git a/live_tests/test_content_providers.py b/live_tests/test_content_providers.py new file mode 100644 index 0000000..f3c782d --- /dev/null +++ b/live_tests/test_content_providers.py @@ -0,0 +1,36 @@ +import pytest + +from pittapi.library import LibraryClient +from pittapi.news import NewsClient +from pittapi.people import PeopleClient + +pytestmark = pytest.mark.live + + +def test_library_catalog_and_hillman_reservations(): + with LibraryClient() as library: + documents = library.get_documents("computer science") + reservation_total = library.hillman_total_reserved() + + assert documents.num_results > 0 + assert documents.documents + assert reservation_total >= 0 + + +def test_pittwire_topics_and_articles(): + with NewsClient() as news: + topics = news.get_topics() + articles = news.get_articles_by_topic(topics[0], max_num_results=1) + + assert topics + assert articles + assert articles[0].url.startswith("https://www.pittwire.pitt.edu/") + + +def test_people_directory_service_account(): + with PeopleClient() as people: + matches = people.get_person("Technology Help Desk") + + assert matches + assert matches[0].name == "Help Desk, Technology" + assert any(field.name == "Email" for field in matches[0].fields) diff --git a/live_tests/test_status_and_sports_providers.py b/live_tests/test_status_and_sports_providers.py new file mode 100644 index 0000000..de0741d --- /dev/null +++ b/live_tests/test_status_and_sports_providers.py @@ -0,0 +1,23 @@ +import pytest + +from pittapi.sports import FOOTBALL_URL, MENS_BASKETBALL_URL, SportsClient +from pittapi.status import StatusClient + +pytestmark = pytest.mark.live + + +@pytest.mark.parametrize("url", [FOOTBALL_URL, MENS_BASKETBALL_URL]) +def test_espn_team_feed(url): + with SportsClient() as sports: + data = sports.get_team_data(url) + + assert data["team"]["id"] + assert data["team"]["displayName"] + + +def test_pitt_service_status(): + with StatusClient() as status: + summary = status.get_status() + + assert summary.components + assert all(component.name and component.status for component in summary.components) diff --git a/pyproject.toml b/pyproject.toml index 4c3fea7..50e6200 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,6 +45,11 @@ dev = [ line-length = 127 target-version = ["py313"] +[tool.pytest.ini_options] +markers = [ + "live: makes requests to live provider services", +] + [tool.setuptools.packages.find] exclude = ["docs*", "tests*"] namespaces = false