Skip to content

feat: improve Canvas grade sync and add test coverage#804

Open
xitij2000 wants to merge 1 commit into
mitodl:mainfrom
open-craft:kshitij/grade-sync-fix
Open

feat: improve Canvas grade sync and add test coverage#804
xitij2000 wants to merge 1 commit into
mitodl:mainfrom
open-craft:kshitij/grade-sync-fix

Conversation

@xitij2000
Copy link
Copy Markdown

@xitij2000 xitij2000 commented May 21, 2026

Description (What does it do?)

This PR enhances the Canvas grade synchronization functionality with improved validation logic and
comprehensive test coverage:

Key Changes:

  1. Assignment Due Date Validation - Added a new validation check to prevent grade
    synchronization for assignments that have already passed their due date. When attempting to sync
    grades for an overdue assignment, the system will now log a warning and skip the synchronization.

  2. Refactored Task Structure - Split the sync_user_grade_with_canvas shared task into two
    components:

    • sync_user_grade_with_canvas() - The Celery shared task decorator
    • _sync_user_grade_with_canvas() - The internal implementation function

    This separation improves testability and allows the implementation logic to be tested
    independently without Celery.

  3. New Test Suite - Added a new test module (tests/test_tasks.py) with 8 test cases
    covering:

    • Successful grade synchronization
    • Missing Canvas course IDs
    • Assignments not present in Canvas
    • Past due date scenarios
    • Missing Canvas student IDs
    • Grade lookup failures
    • API response error handling
  4. Test Configuration - Added pytest configuration:

    • conftest.py - Test setup with Django settings initialization for bulk email configuration
    • pyproject.toml pytest settings - Configured with database reuse and deprecation warning
      filters

How Can This Be Tested?

Unit Tests

Run the new test suite using the standard test process. Or you can mount this pacakge in the Tutor
openedx container, cd into the directory and run pytest.

Manual Testing

To manually test the functionality:

  1. Create a grade record in Open edX for a Canvas-integrated course
  2. Verify that grades for current assignments sync successfully with Canvas. You can do this through the shell:
       from ol_openedx_canvas_integration.tasks import _sync_user_grade_with_canvas
       from lms.djangoapps.grades.models import PersistentSubsectionGrade
       # Look up a grade record's id
       _sync_user_grade_with_canvas(grade_id)
         
  3. Set an assignment's due date to the past in Canvas and trigger a grade sync - confirm it's skipped with a
    warning log

@xitij2000 xitij2000 force-pushed the kshitij/grade-sync-fix branch from 9907c22 to 19d42f7 Compare May 21, 2026 14:03
Copy link
Copy Markdown
Contributor

@tecoholic tecoholic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xitij2000 I tested the changes with an actualy Canvas instance and Open edX instance. They work as expected and I can verify it from the logs as well.

I think we can improve the tests a bit and then it should be ready.

USER_MODEL = get_user_model()


@override_settings(BULK_EMAIL_DEFAULT_RETRY_DELAY=10, BULK_EMAIL_MAX_RETRIES=5)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These settings are added in conftest.py for compatibility and are not actually used in any of the tested functionality, right? Do we need to override them here?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, this is from before i added them to the test config.

Comment on lines +113 to +127
def test_assignment_not_synced(
self,
mock_client_class,
):
mock_client = mock_client_class.return_value
mock_client.get_canvas_assignments.return_value = {
"dummy-key": {
"id": self.canvas_assignment_id,
"due_at": "",
}
}

_sync_user_grade_with_canvas(self.grade_id)

mock_client.update_assignment_grades.assert_not_called()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this not synced? It would be clear if the reason is in the test case name itself, or at least a docstring explaining it. From the test, it looks like canvas returns an assignment with no due date, which should ideally be syncing the grades. Why is it not?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated the test. I too forgot why this shouldn't sync, so it definitely needs clarification!


_sync_user_grade_with_canvas(self.grade_id)

mock_client.update_assignment_grades.assert_not_called()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really sure what's going on in this test. Test function name says, it's "sync error", the docstring says "test successful grade sync", finally it's asserted as "not called". What's this test actually checking?

I am guessing it's this block?

   try:
        grade = grade_dict[grade_instance.full_usage_key][openedx_user]
        payload = dict([update_grade_payload_kv(canvas_user_id, grade.percent_graded)])
    except (KeyError, AttributeError):

Can we make the docstring a bit more informative about why this condition occurs?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry about that! I basically copied the success case and then started introducing failures to test all possible failures which left in docstring. I've updated this.

To be honest, I don't know why this would occur. This code is only run if the a grade is generated, so why would it not be returned from here, I'm not sure of the whole grading mechanism to know why we'd generate an object but still have the grading API not return a grade. If you know why this could happen, do tell and I'll update the docstring.

@xitij2000 xitij2000 force-pushed the kshitij/grade-sync-fix branch from 19d42f7 to 7ef8445 Compare May 22, 2026 12:24
- Added validation to skip grade sync for assignments past their due date.
- Added missing test suite for Canvas grade sync tasks under `tests/`.
- Minor code cleanup in `api.py` and `client.py`.
@xitij2000 xitij2000 force-pushed the kshitij/grade-sync-fix branch from 7ef8445 to 701bf00 Compare May 22, 2026 12:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants