Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/gradescopeapi/classes/_helpers/_assignment_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,13 +178,12 @@ def get_submission_files(
ASSIGNMENT_ENDPOINT = (
f"{gradescope_base_url}/courses/{course_id}/assignments/{assignment_id}"
)

aws_links = []
file_info_link = f"{ASSIGNMENT_ENDPOINT}/submissions/{submission_id}.json?content=react&only_keys[]=text_files&only_keys[]=file_comments"
file_info_resp = session.get(file_info_link)
if file_info_resp.status_code == requests.codes.ok:
file_info_json = json.loads(file_info_resp.text)
if file_info_json.get("text_files"):
aws_links = []
for file_data in file_info_json["text_files"]:
aws_links.append(file_data["file"]["url"])
else:
Expand Down
61 changes: 59 additions & 2 deletions src/gradescopeapi/classes/extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import dateutil.parser
import requests
from bs4 import BeautifulSoup
from requests_toolbelt.multipart.encoder import MultipartEncoder

from gradescopeapi import DEFAULT_GRADESCOPE_BASE_URL

Expand Down Expand Up @@ -226,6 +227,62 @@ def add_to_body(extension_name: str, extension_datetime: datetime.datetime):

def remove_student_extension(
session: requests.Session,
delete_path: str,
course_id: str,
assignment_id: str,
user_id: str,
gradescope_base_url: str = DEFAULT_GRADESCOPE_BASE_URL,
) -> bool:
raise NotImplementedError("Not implemented yet")
"""Removes the extension for a student on an assignment.

If the user currently has an extension, this will remove their
current extension. If the user does not have an extension, this
will return a ValueError.

Args:
session (requests.Session): The session to use for the request
course_id (str): The ID of the course on Gradescope.
assignment_id (str): The ID of the assignment on Gradescope.
user_id (str): The ID of the user on Gradescope.

Returns:
bool: True if the extension was successfully deleted, False otherwise

Raises:
ValueError: If the user_id does not have an extension for the given assignment_id
"""
# Get delete path
extensions_dict = get_extensions(
session, course_id, assignment_id, DEFAULT_GRADESCOPE_BASE_URL
)

try:
extension: Extension = extensions_dict[user_id]
delete_path: str = extension.delete_path
except Exception:
raise ValueError("No extension was found for the given user_id")

GS_EXTENSIONS_ENDPOINT = f"{gradescope_base_url}/courses/{course_id}/assignments/{assignment_id}/extensions"

# Get auth token
response = session.get(GS_EXTENSIONS_ENDPOINT)
soup = BeautifulSoup(response.text, "html.parser")
auth_token = soup.find("meta", {"name": "csrf-token"})["content"]

# Setup multipart form data
fields = [
("authenticity_token", auth_token),
("_method", "delete"),
]

multipart = MultipartEncoder(fields=fields)

headers = {
"Content-Type": multipart.content_type,
"Referer": GS_EXTENSIONS_ENDPOINT,
}

response = session.post(
gradescope_base_url + delete_path, data=multipart, headers=headers
)

return response.status_code == 200
6 changes: 3 additions & 3 deletions tests/integration/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@


def test_add_or_edit_student_extension(create_connection):
course_id = "753413"
assignment_id = "4330410"
release_date = datetime(2024, 4, 15)
course_id = "1302606"
assignment_id = "8043535"
release_date = datetime(2026, 1, 1)
due_date = release_date + timedelta(days=1)
late_due_date = due_date + timedelta(days=1)

Expand Down
2 changes: 1 addition & 1 deletion tests/test_courses.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def test_membership():
# fetch instructor account
account = get_account("instructor")

course_id = "753413"
course_id = "1302606"

# get course members
members = account.get_course_users(course_id)
Expand Down
34 changes: 20 additions & 14 deletions tests/test_edit_assignment.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import pytest

from datetime import datetime, timedelta

from gradescopeapi.classes.assignments import (
Expand All @@ -15,9 +17,9 @@ def test_valid_change_assignment(create_session):
# create test session
test_session = create_session("instructor")

course_id = "753413"
assignment_id = "4436170"
release_date = datetime(2024, 4, 15)
course_id = "1302606"
assignment_id = "8043535"
release_date = datetime(2026, 1, 1)
due_date = release_date + timedelta(days=1)
late_due_date = due_date + timedelta(days=1)

Expand All @@ -36,8 +38,8 @@ def test_boundary_date_assignment(create_session):
"""Test updating assignment with boundary date values."""
test_session = create_session("instructor")

course_id = "753413"
assignment_id = "4436170"
course_id = "1302606"
assignment_id = "8043535"
boundary_date = datetime(1900, 1, 1) # Very old date

result = update_assignment_date(
Expand All @@ -55,9 +57,9 @@ def test_update_assignment_date_invalid_session(create_session):
"""Test updating assignment with student session."""
test_session = create_session("student")

course_id = "753413"
assignment_id = "4436170"
release_date = datetime(2024, 4, 15)
course_id = "1302606"
assignment_id = "8043535"
release_date = datetime(2026, 1, 1)
due_date = release_date + timedelta(days=1)
late_due_date = due_date + timedelta(days=1)

Expand All @@ -75,6 +77,7 @@ def test_update_assignment_date_invalid_session(create_session):
assert e.response.status_code == 401 # HTTP 401 Not Authorized


@pytest.mark.skip(reason="Not using autograder")
def test_autograder_valid_image_name(create_session):
"""Test updating assignment with valid image name."""
test_session = create_session("instructor")
Expand All @@ -92,6 +95,7 @@ def test_autograder_valid_image_name(create_session):
assert result, "Failed to update autograder image name"


@pytest.mark.skip(reason="Not using autograder")
def test_autograder_invalid_image_name(create_session):
"""Test updating assignment with invalid image name."""
test_session = create_session("instructor")
Expand All @@ -109,6 +113,7 @@ def test_autograder_invalid_image_name(create_session):
assert not result, "Incorrectly updated to invalid autograder image name"


@pytest.mark.skip(reason="Not using autograder")
def test_autograder_invalid_session(create_session):
"""Test updating assignment with student session."""
test_session = create_session("student")
Expand All @@ -129,6 +134,7 @@ def test_autograder_invalid_session(create_session):
assert e.response.status_code == 401 # HTTP 401 Not Authorized


@pytest.mark.skip(reason="Not using autograder")
def test_autograder_invalid_assignment_type(create_session):
"""Test updating assignment with invalid assignment type."""
test_session = create_session("instructor")
Expand All @@ -153,8 +159,8 @@ def test_update_assignment_title_valid_random_title(create_session):
"""Test updating assignment with random name."""
test_session = create_session("instructor")

course_id = "753413"
assignment_id = "7332839"
course_id = "1302606"
assignment_id = "8043535"
new_assignment_name = f"Test Rename - {uuid.uuid4()}"

result = update_assignment_title(
Expand All @@ -170,8 +176,8 @@ def test_update_assignment_title_invalid_title_whitespace(create_session):
"""Test updating assignment with invalid name containing only whitespace."""
test_session = create_session("instructor")

course_id = "753413"
assignment_id = "7193007"
course_id = "1302606"
assignment_id = "8043535"
new_assignment_name = " " # whitespace only not allowed

try:
Expand All @@ -190,8 +196,8 @@ def test_update_assignment_title_invalid_session(create_session):
"""Test updating assignment with student session."""
test_session = create_session("student")

course_id = "753413"
assignment_id = "7332839"
course_id = "1302606"
assignment_id = "8043535"
new_assignment_name = f"Test Rename - {uuid.uuid4()}"

try:
Expand Down
72 changes: 57 additions & 15 deletions tests/test_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,20 @@

import pytest

from gradescopeapi.classes.extensions import get_extensions, update_student_extension
from gradescopeapi.classes.extensions import (
get_extensions,
update_student_extension,
remove_student_extension,
)


def test_get_extensions(create_session):
"""Test fetching extensions for an assignment."""
# create test session
test_session = create_session("instructor")

course_id = "753413"
assignment_id = "4330410"
course_id = "1302606"
assignment_id = "8043535"

extensions = get_extensions(test_session, course_id, assignment_id)
assert len(extensions) > 0, (
Expand All @@ -24,10 +28,10 @@ def test_valid_change_extension(create_session):
# create test session
test_session = create_session("instructor")

course_id = "753413"
assignment_id = "4330410"
user_id = "6515875"
release_date = datetime(2024, 4, 15)
course_id = "1302606"
assignment_id = "8043535"
user_id = "9629996"
release_date = datetime(2026, 1, 1)
due_date = release_date + timedelta(days=1)
late_due_date = due_date + timedelta(days=1)

Expand All @@ -48,10 +52,10 @@ def test_invalid_change_extension(create_session):
# create test session
test_session = create_session("instructor")

course_id = "753413"
assignment_id = "4330410"
user_id = "6515875"
release_date = datetime(2024, 4, 15)
course_id = "1302606"
assignment_id = "8043535"
user_id = "9629996"
release_date = datetime(2026, 1, 1)
due_date = release_date + timedelta(days=-1)
late_due_date = due_date + timedelta(days=-1)

Expand All @@ -74,8 +78,8 @@ def test_invalid_user_id(create_session):
"""Test granting an invalid extension for a student due to invalid user ID."""
test_session = create_session("instructor")

course_id = "753413"
assignment_id = "4330410"
course_id = "1302606"
assignment_id = "8043535"
invalid_user_id = "9999999" # Assuming this is an invalid ID

# Attempt to change the extension with an invalid user ID
Expand All @@ -96,7 +100,7 @@ def test_invalid_user_id(create_session):
def test_invalid_assignment_id(create_session):
"""Test extension handling with an invalid assignment ID."""
test_session = create_session("instructor")
course_id = "753413"
course_id = "1302606"
invalid_assignment_id = "9999999"

# Attempt to fetch extensions with an invalid assignment ID
Expand All @@ -111,4 +115,42 @@ def test_invalid_course_id(create_session):

# Attempt to fetch or modify extensions with an invalid course ID
with pytest.raises(RuntimeError, match="Failed to get extensions"):
get_extensions(test_session, invalid_course_id, "4330410")
get_extensions(test_session, invalid_course_id, "8043535")


def test_valid_remove_extension(create_session):
"""Test remove extension with existing extension."""

# Create extension for user
test_valid_change_extension(create_session)

test_session = create_session("instructor")
course_id = "1302606"
assignment_id = "8043535"
user_id = "9629996"

# Attempt to remove student extension
result = remove_student_extension(test_session, course_id, assignment_id, user_id)

assert result, "Failed to remove student extension"


def test_invalid_remove_extension(create_session):
"""Test remove extension with nonexistent extension."""

# Create extension for user
test_valid_change_extension(create_session)

test_session = create_session("instructor")
course_id = "1302606"
assignment_id = "8043535"
user_id = "9629996"

# Attempt to remove student extension
remove_student_extension(test_session, course_id, assignment_id, user_id)

# Attempt to remove student extension again
with pytest.raises(
ValueError, match="No extension was found for the given user_id"
):
remove_student_extension(test_session, course_id, assignment_id, user_id)
4 changes: 4 additions & 0 deletions tests/test_graders.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import pytest

from gradescopeapi.classes.account import Account


@pytest.mark.skip(reason="Not testing graders")
def test_get_assignment_graders_non_empty(create_session):
"""Test getting graders for a question that has been graded."""
# create test session
Expand All @@ -14,6 +17,7 @@ def test_get_assignment_graders_non_empty(create_session):
assert len(graders) > 0, "Should have at least 1 grader"


@pytest.mark.skip(reason="Not testing graders")
def test_get_assignment_graders_empty(create_session):
"""Test getting graders for a question that has not been graded."""
# create test session
Expand Down
Loading
Loading