Skip to content
Draft
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
53 changes: 52 additions & 1 deletion edx_when/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
API for retrieving and setting dates.
"""

from dataclasses import dataclass
from datetime import datetime, timedelta
import logging
from datetime import timedelta

from django.core.exceptions import ValidationError
from django.db import transaction
Expand Down Expand Up @@ -506,6 +507,56 @@ def get_schedules_with_due_date(course_id, assignment_date):
return schedules


@dataclass
class _Assignment:
"""
Represents an assignment with a title, date, block key, and assignment type.
"""
title: str
date: datetime
block_key: UsageKey
assignment_type: str

def __post_init__(self):
if not isinstance(self.date, datetime):
raise TypeError("date must be a datetime object")
if not isinstance(self.block_key, UsageKey):
raise TypeError("block_key must be a UsageKey object")


def update_or_create_assignments_due_dates(course_key, assignments: list[_Assignment]):
"""
Update or create assignment due dates for a course.
"""
course_key_str = str(course_key)
for assignment in assignments:
log.info(
"Updating assignment '%s' with due date '%s' for course %s",
assignment.title,
assignment.date,
course_key_str
)
if not all((assignment.date, assignment.title)):
log.warning(
"Skipping assignment '%s' for course %s because it has no date or title",
assignment,
course_key_str
)
continue
models.ContentDate.objects.update_or_create(
course_id=course_key,
location=assignment.block_key,
field='due',
block_type=assignment.assignment_type,
defaults={
'policy': models.DatePolicy.objects.get_or_create(abs_date=assignment.date)[0],
'assignment_title': assignment.title,
'course_name': course_key.course,
'subsection_name': assignment.title
}
)


class BaseWhenException(Exception):
pass

Expand Down
Empty file added edx_when/tests/__init__.py
Empty file.
166 changes: 166 additions & 0 deletions edx_when/tests/test_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
"""
Tests for course date signals tasks.
"""
from unittest.mock import Mock, patch
from django.test import TestCase
from opaque_keys.edx.keys import CourseKey, UsageKey
from django.contrib.auth import get_user_model
from datetime import datetime, timezone

from edx_when.api import update_or_create_assignments_due_dates
from edx_when.models import ContentDate, DatePolicy

User = get_user_model()


class TestUpdateAssignmentDatesForCourse(TestCase):
"""
Tests for the update_assignment_dates_for_course task.
"""

def setUp(self):
self.course_key = CourseKey.from_string('course-v1:edX+DemoX+Demo_Course')
self.course_key_str = str(self.course_key)
self.staff_user = User.objects.create_user(
username='staff_user',
email='[email protected]',
is_staff=True
)
self.block_key = UsageKey.from_string(
'block-v1:edX+DemoX+Demo_Course+type@sequential+block@test1'
)
self.due_date = datetime(2024, 12, 31, 23, 59, 59, tzinfo=timezone.utc)
self.assignments = [
Mock(
title='Test Assignment',
date=self.due_date,
block_key=self.block_key,
assignment_type='Homework'
)
]

def test_update_assignment_dates_new_records(self):
"""
Test inserting new records when missing.
"""
update_or_create_assignments_due_dates(self.course_key, self.assignments)

content_date = ContentDate.objects.get(
course_id=self.course_key,
location=self.block_key
)
self.assertEqual(content_date.assignment_title, 'Test Assignment')
self.assertEqual(content_date.block_type, 'Homework')
self.assertEqual(content_date.policy.abs_date, self.due_date)

def test_update_assignment_dates_existing_records(self):
"""
Test updating existing records when values differ.
"""
existing_policy = DatePolicy.objects.create(
abs_date=datetime(2024, 6, 1, tzinfo=timezone.utc)
)
ContentDate.objects.create(
course_id=self.course_key,
location=self.block_key,
field='due',
block_type='Homework',
policy=existing_policy,
assignment_title='Old Title',
course_name=self.course_key.course,
subsection_name='Old Title'
)
new_assignment = Mock(
title='Updated Assignment',
date=self.due_date,
block_key=self.block_key,
assignment_type='Homework'
)

update_or_create_assignments_due_dates(self.course_key, [new_assignment])

content_date = ContentDate.objects.get(
course_id=self.course_key,
location=self.block_key
)
self.assertEqual(content_date.assignment_title, 'Updated Assignment')
self.assertEqual(content_date.policy.abs_date, self.due_date)

def test_assignment_with_null_date(self):
"""
Test handling assignments with null dates.
"""
null_date_assignment = Mock(
title='Null Date Assignment',
date=None,
block_key=self.block_key,
assignment_type='Homework'
)
update_or_create_assignments_due_dates(self.course_key, [null_date_assignment])

content_date_exists = ContentDate.objects.filter(
course_id=self.course_key,
location=self.block_key
).exists()
self.assertFalse(content_date_exists)

def test_assignment_with_missing_metadata(self):
"""
Test handling assignments with missing metadata.
"""
assignment = Mock(
date=self.due_date,
block_key=self.block_key,
)
update_or_create_assignments_due_dates(self.course_key, [assignment])

content_date_exists = ContentDate.objects.filter(
course_id=self.course_key,
location=self.block_key
).exists()
self.assertFalse(content_date_exists)

def test_multiple_assignments(self, mock_get_assignments):
"""
Test processing multiple assignments.
"""
assignment1 = Mock(
title='Assignment 1',
date=self.due_date,
block_key=self.block_key,
assignment_type='Gradeable'
)

assignment2 = Mock(
title='Assignment 2',
date=datetime(2025, 1, 15, tzinfo=timezone.utc),
block_key=UsageKey.from_string(
'block-v1:edX+DemoX+Demo_Course+type@sequential+block@test2'
),
assignment_type='Homework'
)
update_or_create_assignments_due_dates(self.course_key, [assignment1, assignment2])
self.assertEqual(ContentDate.objects.count(), 2)

def test_empty_assignments_list(self, mock_get_assignments):
"""
Test handling empty assignments list.
"""
update_or_create_assignments_due_dates(self.course_key, [])
self.assertEqual(ContentDate.objects.count(), 0)

@patch('edx_when.models.DatePolicy.objects.get_or_create')
def test_date_policy_creation_exception(self, mock_policy_create, mock_get_assignments):
"""
Test handling exception during DatePolicy creation.
"""
assignment = Mock(
title='Test Assignment',
date=self.due_date,
block_key=self.block_key,
assignment_type='problem'
)
mock_policy_create.side_effect = Exception('Database Error')

with self.assertRaises(Exception):
update_or_create_assignments_due_dates(self.course_key, [assignment])
Loading