From 88e5b0dde6d389391439e9fb6c3171469bed070a Mon Sep 17 00:00:00 2001 From: Marcos Prieto Date: Tue, 3 Sep 2024 13:54:24 +0200 Subject: [PATCH] Forward auto-grading settings to DL assignments --- lms/views/lti/deep_linking.py | 23 +++++++ tests/unit/lms/views/lti/deep_linking_test.py | 67 ++++++++++++++++++- 2 files changed, 88 insertions(+), 2 deletions(-) diff --git a/lms/views/lti/deep_linking.py b/lms/views/lti/deep_linking.py index dcf1e1588f..eda39bcb68 100644 --- a/lms/views/lti/deep_linking.py +++ b/lms/views/lti/deep_linking.py @@ -42,6 +42,7 @@ import uuid from datetime import datetime, timedelta +from marshmallow import Schema, validate from pyramid.view import view_config, view_defaults from webargs import fields @@ -85,12 +86,30 @@ def deep_linking_launch(context, request): return {} +class _AutoGradingConfigSchema(Schema): + grading_type = fields.Str( + required=True, validate=validate.OneOf(["all_or_nothing", "scaled"]) + ) + activity_calculation = fields.Str( + required=True, validate=validate.OneOf(["cumulative", "separate"]) + ) + + required_annotations = fields.Int(required=True, validate=validate.Range(min=0)) + required_replies = fields.Int( + required=False, allow_none=True, validate=validate.Range(min=0) + ) + + class DeepLinkingFieldsRequestSchema(JSONPyramidRequestSchema): content_item_return_url = fields.Str(required=True) content = fields.Dict(required=True) group_set = fields.Str(required=False, allow_none=True) title = fields.Str(required=False, allow_none=True) + auto_grading_config = fields.Nested( + _AutoGradingConfigSchema, required=False, allow_none=True + ) + class LTI11DeepLinkingFieldsRequestSchema(DeepLinkingFieldsRequestSchema): opaque_data_lti11 = fields.Str(required=False, allow_none=True) @@ -254,6 +273,10 @@ def _get_assignment_configuration(request) -> dict: if title := request.parsed_params.get("title"): params["title"] = title + if auto_grading_config := request.parsed_params.get("auto_grading_config"): + # Custom params must be str, encode these settings as JSON + params["auto_grading_config"] = json.dumps(auto_grading_config) + if content["type"] == "url": params["url"] = content["url"] else: diff --git a/tests/unit/lms/views/lti/deep_linking_test.py b/tests/unit/lms/views/lti/deep_linking_test.py index eb1e1bc380..5cdde710cf 100644 --- a/tests/unit/lms/views/lti/deep_linking_test.py +++ b/tests/unit/lms/views/lti/deep_linking_test.py @@ -5,13 +5,72 @@ import pytest from freezegun import freeze_time from h_matchers import Any +from pyramid.testing import DummyRequest from lms.resources import LTILaunchResource from lms.resources._js_config import JSConfig -from lms.views.lti.deep_linking import DeepLinkingFieldsViews, deep_linking_launch +from lms.validation import ValidationError +from lms.views.lti.deep_linking import ( + DeepLinkingFieldsRequestSchema, + DeepLinkingFieldsViews, + deep_linking_launch, +) from tests import factories +class TestDeepLinkingFieldsRequestSchema: + @pytest.mark.parametrize( + "payload", + [ + { + "content": {"type": "url", "url": "https://example.com"}, + "content_item_return_url": "return_url", + "auto_grading_config": { + "grading_type": "scaled", + "activity_calculation": "separate", + "required_annotations": 10, + "required_replies": 2, + }, + "extra_params": {}, + } + ], + ) + def test_valid_payloads(self, payload): + request = DummyRequest( + body=json.dumps(payload), + headers={"Content-Type": "application/json; charset=UTF-8"}, + ) + request.content_type = request.headers["content-type"] = "application/json" + + DeepLinkingFieldsRequestSchema(request).parse() + + @pytest.mark.parametrize( + "payload", + [ + { + "content": {"type": "url", "url": "https://example.com"}, + "content_item_return_url": "return_url", + "auto_grading_config": { + "grading_type": "scaled", + "activity_calculation": "RANDOM", + "required_annotations": 10, + "required_replies": 2, + }, + "extra_params": {}, + } + ], + ) + def test_invalid_payloads(self, payload): + request = DummyRequest( + body=json.dumps(payload), + headers={"Content-Type": "application/json; charset=UTF-8"}, + ) + request.content_type = request.headers["content-type"] = "application/json" + + with pytest.raises(ValidationError): + DeepLinkingFieldsRequestSchema(request).parse() + + @pytest.mark.usefixtures("application_instance_service", "lti_h_service") class TestDeepLinkingLaunch: def test_it( @@ -211,9 +270,13 @@ def test_it_for_v11( {"group_set": "1", "title": "title"}, {"group_set": "1", "title": "title"}, ), + ( + {"auto_grading_config": {"key": "value"}}, + {"auto_grading_config": '{"key": "value"}'}, + ), ], ) - def test_get_assignment_configuration( + def test__get_assignment_configuration( self, content, expected_from_content, pyramid_request, data, expected, uuid ): pyramid_request.parsed_params.update({"content": content, **data})