diff --git a/openedx/core/djangoapps/agreements/models.py b/openedx/core/djangoapps/agreements/models.py index 0376442ea7fe..a871245d1c25 100644 --- a/openedx/core/djangoapps/agreements/models.py +++ b/openedx/core/djangoapps/agreements/models.py @@ -71,6 +71,7 @@ class ProctoringPIISignature(TimeStampedModel): class Meta: app_label = 'agreements' + class UserAgreementRecord(models.Model): """ This model stores the agreements a user has accepted or acknowledged. diff --git a/openedx/core/djangoapps/agreements/serializers.py b/openedx/core/djangoapps/agreements/serializers.py index 9915f01575c2..3c6e9f78c201 100644 --- a/openedx/core/djangoapps/agreements/serializers.py +++ b/openedx/core/djangoapps/agreements/serializers.py @@ -32,6 +32,7 @@ class Meta: model = LTIPIISignature fields = ('username', 'course_id', 'lti_tools', 'created_at') + class UserAgreementsSerializer(serializers.Serializer): """ Serializer for UserAgreementRecord model diff --git a/openedx/core/djangoapps/agreements/tests/test_api.py b/openedx/core/djangoapps/agreements/tests/test_api.py index c66065789939..82bc12cf9f58 100644 --- a/openedx/core/djangoapps/agreements/tests/test_api.py +++ b/openedx/core/djangoapps/agreements/tests/test_api.py @@ -2,7 +2,10 @@ Tests for the Agreements API """ import logging +from datetime import datetime, timedelta +from django.test import TestCase +from opaque_keys.edx.keys import CourseKey from testfixtures import LogCapture from common.djangoapps.student.tests.factories import UserFactory @@ -12,15 +15,17 @@ get_integrity_signatures_for_course, get_pii_receiving_lti_tools, create_lti_pii_signature, - get_lti_pii_signature + get_lti_pii_signature, + create_user_agreement_record, + get_user_agreements, + get_user_agreement_record, ) from openedx.core.djangolib.testing.utils import skip_unless_lms -from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order -from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order +from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase +from xmodule.modulestore.tests.factories import CourseFactory from ..models import ( LTIPIITool, ) -from opaque_keys.edx.keys import CourseKey LOGGER_NAME = "openedx.core.djangoapps.agreements.api" @@ -186,3 +191,37 @@ def _assert_ltitools(self, lti_list): Helper function to assert the returned list has the correct tools """ self.assertEqual(self.lti_tools, lti_list) + + +@skip_unless_lms +class UserAgreementsTests(TestCase): + """ + Tests for the python APIs related to user agreements. + """ + def setUp(self): + self.user = UserFactory() + + def test_get_user_agreements(self, ): + result = list(get_user_agreements(self.user)) + assert len(result) == 0 + + record = create_user_agreement_record(self.user, 'test_type') + result = list(get_user_agreements(self.user)) + + assert len(result) == 1 + assert result[0].agreement_type == 'test_type' + assert result[0].username == self.user.username + assert result[0].accepted_at == record.accepted_at + + def test_get_user_agreement_record(self): + record = create_user_agreement_record(self.user, 'test_type') + result = get_user_agreement_record(self.user, 'test_type') + + assert result == record + + result = get_user_agreement_record(self.user, 'test_type', datetime.now() + timedelta(days=1)) + + assert result is None + + def tearDown(self): + self.user.delete() diff --git a/openedx/core/djangoapps/agreements/tests/test_views.py b/openedx/core/djangoapps/agreements/tests/test_views.py index 4c52e5853f05..c2cf30a6cae5 100644 --- a/openedx/core/djangoapps/agreements/tests/test_views.py +++ b/openedx/core/djangoapps/agreements/tests/test_views.py @@ -2,25 +2,26 @@ Tests for agreements views """ +import json from datetime import datetime, timedelta from unittest.mock import patch from django.conf import settings from django.urls import reverse -from rest_framework.test import APITestCase -from rest_framework import status from freezegun import freeze_time -import json +from rest_framework import status +from rest_framework.test import APITestCase -from common.djangoapps.student.tests.factories import UserFactory, AdminFactory from common.djangoapps.student.roles import CourseStaffRole +from common.djangoapps.student.tests.factories import UserFactory, AdminFactory from openedx.core.djangoapps.agreements.api import ( create_integrity_signature, get_integrity_signatures_for_course, - get_lti_pii_signature + get_lti_pii_signature, create_user_agreement_record ) from openedx.core.djangolib.testing.utils import skip_unless_lms -from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order +from xmodule.modulestore.tests.django_utils import \ + ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order @@ -289,3 +290,49 @@ def test_post_lti_pii_signature(self): signature = get_lti_pii_signature(self.user.username, self.course_id) self.assertEqual(signature.user.username, self.user.username) self.assertEqual(signature.lti_tools, self.lti_tools) + + +@skip_unless_lms +class UserAgreementsViewTests(APITestCase): + """ + Tests for the UserAgreementsView + """ + + def setUp(self): + self.user = UserFactory(username="testuser", password="password") + self.client.login(username="testuser", password="password") + self.url = reverse('user_agreements', kwargs={'agreement_type': 'sample_agreement'}) + + def test_get_user_agreement_record_no_data(self): + response = self.client.get(self.url) + assert response.status_code == status.HTTP_404_NOT_FOUND + + def test_get_user_agreement_record_invalid_date(self): + response = self.client.get(self.url, {'after': 'invalid_date'}) + assert response.status_code == status.HTTP_400_BAD_REQUEST + + def test_get_user_agreement_record(self): + create_user_agreement_record(self.user, 'sample_agreement') + response = self.client.get(self.url) + assert response.status_code == status.HTTP_200_OK + assert 'accepted_at' in response.data + + response = self.client.get(self.url, {"after": str(datetime.now() + timedelta(days=1))}) + assert response.status_code == status.HTTP_404_NOT_FOUND + + def test_post_user_agreement(self): + with freeze_time("2024-11-21 12:00:00"): + response = self.client.post(self.url) + assert response.status_code == status.HTTP_201_CREATED + + response = self.client.get(self.url) + assert response.status_code == status.HTTP_200_OK + + response = self.client.get(self.url, {"after": "2024-11-21T13:00:00Z"}) + assert response.status_code == status.HTTP_404_NOT_FOUND + + response = self.client.post(self.url) + assert response.status_code == status.HTTP_201_CREATED + + response = self.client.get(self.url, {"after": "2024-11-21T13:00:00Z"}) + assert response.status_code == status.HTTP_200_OK diff --git a/openedx/core/djangoapps/agreements/urls.py b/openedx/core/djangoapps/agreements/urls.py index 163aab72a767..202a6f11d6b2 100644 --- a/openedx/core/djangoapps/agreements/urls.py +++ b/openedx/core/djangoapps/agreements/urls.py @@ -4,7 +4,6 @@ from django.conf import settings from django.urls import re_path, path -from django.urls.conf import include from .views import IntegritySignatureView, LTIPIISignatureView, UserAgreementsView