from unittest.mock import patch from django.urls import reverse from rest_framework.test import APITestCase from vbv_lernwelt.course.creators.test_utils import ( add_course_session_user, create_circle, create_course, create_course_session, create_learning_unit, create_performance_criteria_page, create_user, ) from vbv_lernwelt.course.models import CourseCompletionStatus, CourseSessionUser from vbv_lernwelt.course.services import mark_course_completion from vbv_lernwelt.learning_mentor.models import AgentParticipantRelation from vbv_lernwelt.self_evaluation_feedback.models import ( CourseCompletionFeedback, SelfEvaluationFeedback, ) def create_self_evaluation_feedback( learning_unit, feedback_requester_user, feedback_provider_user ): return SelfEvaluationFeedback.objects.create( learning_unit=learning_unit, feedback_requester_user=feedback_requester_user, feedback_provider_user=feedback_provider_user, ) class SelfEvaluationFeedbackAPI(APITestCase): def setUp(self) -> None: self.member = create_user("member") self.mentor = create_user("mentor") self.course, self.course_page = create_course("Test Course") self.course_session = create_course_session( course=self.course, title="Test Bern 2022 a" ) member_csu = add_course_session_user( course_session=self.course_session, user=self.member, role=CourseSessionUser.Role.MEMBER, ) self.circle, _ = create_circle( title="Test Circle", course_page=self.course_page ) AgentParticipantRelation.objects.create( agent=self.mentor, participant=member_csu, role="LEARNING_MENTOR" ) @patch( "vbv_lernwelt.notify.services.NotificationService.send_self_evaluation_feedback_request_feedback_notification" ) def test_start_self_evaluation_feedback(self, mock_notification_service_send): # GIVEN learning_unit = create_learning_unit(course=self.course, circle=self.circle) pc = create_performance_criteria_page( course=self.course, course_page=self.course_page, circle=self.circle, learning_unit=learning_unit, ) mark_course_completion( page=pc, user=self.member, course_session=self.course_session, completion_status="SUCCESS", ) self.client.force_login(self.member) # WHEN response = self.client.post( reverse( "start_self_evaluation_feedback", args=[learning_unit.id], ), { "feedback_provider_user_id": self.mentor.id, }, ) # make sure re-starting is a no-op self.client.post( reverse( "start_self_evaluation_feedback", args=[learning_unit.id], ), { "feedback_provider_user_id": self.mentor.id, }, ) # shall be idempotent self.client.post( reverse( "start_self_evaluation_feedback", args=[learning_unit.id], ), { "feedback_provider_user_id": self.mentor.id, }, ) # THEN self.assertEqual(response.status_code, 200) self.assertEqual(response.data["success"], True) self.assertEqual( SelfEvaluationFeedback.objects.count(), 1, ) self_evaluation_feedback = SelfEvaluationFeedback.objects.first() self.assertEqual(self_evaluation_feedback.feedback_requester_user, self.member) self.assertEqual(self_evaluation_feedback.feedback_provider_user, self.mentor) self.assertEqual(self_evaluation_feedback.learning_unit, learning_unit) mock_notification_service_send.assert_called_once_with( self_evaluation_feedback=self_evaluation_feedback ) def test_start_self_evaluation_feedback_not_allowed_user(self): # GIVEN learning_unit = create_learning_unit(course=self.course, circle=self.circle) not_a_mentor = create_user("not_a_mentor") self.client.force_login(self.member) # WHEN response = self.client.post( reverse("start_self_evaluation_feedback", args=[learning_unit.id]), { "feedback_provider_user_id": not_a_mentor.id, }, ) # THEN self.assertEqual(response.status_code, 403) def test_get_self_evaluation_feedback_as_requester(self): """Tests endpoint of feedback REQUESTER""" # GIVEN learning_unit = create_learning_unit( # noqa course=self.course, circle=self.circle ) performance_criteria_1 = create_performance_criteria_page( course=self.course, course_page=self.course_page, circle=self.circle, learning_unit=learning_unit, ) create_performance_criteria_page( course=self.course, course_page=self.course_page, circle=self.circle, learning_unit=learning_unit, ) completion = mark_course_completion( page=performance_criteria_1, user=self.member, course_session=self.course_session, completion_status=CourseCompletionStatus.SUCCESS.value, ) self_evaluation_feedback = create_self_evaluation_feedback( learning_unit=learning_unit, feedback_requester_user=self.member, feedback_provider_user=self.mentor, ) CourseCompletionFeedback.objects.create( feedback=self_evaluation_feedback, course_completion=completion, feedback_assessment=CourseCompletionStatus.FAIL.value, ) self.client.force_login(self.member) # WHEN response = self.client.get( reverse( "get_self_evaluation_feedback_as_requester", args=[learning_unit.id], ) ) # THEN self.assertEqual(response.status_code, 200) feedback = response.data self.assertEqual(feedback["learning_unit_id"], learning_unit.id) self.assertFalse(feedback["feedback_submitted"]) self.assertEqual(feedback["circle_name"], self.circle.title) # noqa provider_user = feedback["feedback_provider_user"] self.assertEqual(provider_user["id"], str(self.mentor.id)) self.assertEqual(provider_user["first_name"], self.mentor.first_name) self.assertEqual(provider_user["last_name"], self.mentor.last_name) self.assertEqual(provider_user["avatar_url"], self.mentor.avatar_url) requester_user = feedback["feedback_requester_user"] self.assertEqual(requester_user["id"], str(self.member.id)) # noqa self.assertEqual(requester_user["first_name"], self.member.first_name) self.assertEqual(requester_user["last_name"], self.member.last_name) self.assertEqual(requester_user["avatar_url"], self.member.avatar_url) self.assertEqual(len(feedback["criteria"]), 2) first_criteria = feedback["criteria"][0] self.assertEqual(first_criteria["course_completion_id"], completion.id) self.assertEqual(first_criteria["title"], performance_criteria_1.title) self.assertEqual( first_criteria["self_assessment"], CourseCompletionStatus.SUCCESS.value, ) self.assertEqual( first_criteria["feedback_assessment"], CourseCompletionStatus.FAIL.value ) second_criteria = feedback["criteria"][1] self.assertEqual(second_criteria["course_completion_id"], None) self.assertEqual(second_criteria["title"], performance_criteria_1.title) self.assertEqual( second_criteria["self_assessment"], CourseCompletionStatus.UNKNOWN.value ) self.assertEqual( second_criteria["feedback_assessment"], CourseCompletionStatus.UNKNOWN.value, ) def test_feedbacks_with_mixed_completion_statuses(self): """Case: CourseCompletion AND feedbacks with mixed completion statuses""" # GIVEN learning_unit = create_learning_unit( course=self.course, circle=self.circle, ) feedback = create_self_evaluation_feedback( learning_unit=learning_unit, feedback_requester_user=self.member, feedback_provider_user=self.mentor, ) feedback.feedback_submitted = True feedback.save() for status in [ CourseCompletionStatus.SUCCESS, CourseCompletionStatus.FAIL, CourseCompletionStatus.UNKNOWN, ]: criteria_page = create_performance_criteria_page( course=self.course, course_page=self.course_page, circle=self.circle, learning_unit=learning_unit, ) # self assessment completion = mark_course_completion( page=criteria_page, user=self.member, course_session=self.course_session, completion_status=status.value, ) # feedback assessment CourseCompletionFeedback.objects.create( feedback=feedback, course_completion=completion, feedback_assessment=status.value, ) self.client.force_login(self.member) # WHEN response = self.client.get( reverse( "get_course_session_user_feedback_summaries", args=[self.course_session.id, self.member.id], ) ) # THEN self.assertEqual(response.status_code, 200) result = response.data["results"][0] self_assessment = result["self_assessment"] self.assertEqual(self_assessment["counts"]["pass"], 1) self.assertEqual(self_assessment["counts"]["fail"], 1) self.assertEqual(self_assessment["counts"]["unknown"], 1) feedback_assessment = result["feedback_assessment"] self.assertEqual(feedback_assessment["counts"]["pass"], 1) self.assertEqual(feedback_assessment["counts"]["fail"], 1) self.assertEqual(feedback_assessment["counts"]["unknown"], 1) self.assertTrue(feedback_assessment["submitted_by_provider"]) self.assertEqual( feedback_assessment["provider_user"]["id"], str(self.mentor.id) ) aggregate = response.data["aggregates"] self.assertEqual(aggregate["self_assessment"]["pass"], 1) self.assertEqual(aggregate["self_assessment"]["fail"], 1) self.assertEqual(aggregate["self_assessment"]["unknown"], 1) self.assertEqual(aggregate["feedback_assessment"]["pass"], 1) self.assertEqual(aggregate["feedback_assessment"]["fail"], 1) self.assertEqual(aggregate["feedback_assessment"]["unknown"], 1) def test_no_feedbacks_but_with_completion_status(self): """Case: CourseCompletion but NO feedback""" # GIVEN learning_unit_with_success_feedback = create_learning_unit( course=self.course, circle=self.circle, ) performance_criteria_page = create_performance_criteria_page( course=self.course, course_page=self.course_page, circle=self.circle, learning_unit=learning_unit_with_success_feedback, ) # IMPORTANT: CourseCompletion but NO feedback! mark_course_completion( page=performance_criteria_page, user=self.member, course_session=self.course_session, completion_status=CourseCompletionStatus.SUCCESS.value, ) self.client.force_login(self.member) # WHEN response = self.client.get( reverse( "get_course_session_user_feedback_summaries", args=[self.course_session.id, self.member.id], ) ) # THEN self.assertEqual(response.status_code, 200) result = response.data["results"][0] counts = result["self_assessment"]["counts"] self.assertEqual(counts["pass"], 1) self.assertEqual(counts["fail"], 0) self.assertEqual(counts["unknown"], 0) def test_feedbacks_not_started(self): """Case: Learning unit with no completion status and no feedback""" # GIVEN learning_unit = create_learning_unit( # noqa course=self.course, circle=self.circle, ) create_performance_criteria_page( course=self.course, course_page=self.course_page, circle=self.circle, learning_unit=learning_unit, ) self.client.force_login(self.member) # WHEN response = self.client.get( reverse( "get_course_session_user_feedback_summaries", args=[self.course_session.id, self.member.id], ) ) # THEN self.assertEqual(response.status_code, 200) result = response.data["results"][0] self.assertEqual(result["self_assessment"]["counts"]["pass"], 0) self.assertEqual(result["self_assessment"]["counts"]["fail"], 0) self.assertEqual(result["self_assessment"]["counts"]["unknown"], 1) def test_permission_feedback_summaries(self): # GIVEN learning_unit = create_learning_unit( # noqa course=self.course, circle=self.circle, ) create_performance_criteria_page( course=self.course, course_page=self.course_page, circle=self.circle, learning_unit=learning_unit, ) expert = create_user("expert") add_course_session_user( course_session=self.course_session, user=expert, role=CourseSessionUser.Role.EXPERT, ) fellow_member = create_user("fellow_member") add_course_session_user( course_session=self.course_session, user=fellow_member, role=CourseSessionUser.Role.MEMBER, ) # WHEN / THEN test_cases = [ # principle_user wants to access target_user # -> expected_status_code (self.member, self.member, 200), (self.mentor, self.member, 200), (expert, self.member, 200), (fellow_member, self.member, 403), ] for principle_user, target_user, expected_status_code in test_cases: with self.subTest(principle_user=principle_user, target_user=target_user): self.client.force_login(principle_user) response = self.client.get( reverse( "get_course_session_user_feedback_summaries", args=[self.course_session.id, target_user.id], ) ) self.assertEqual(response.status_code, expected_status_code) def test_feedbacks_metadata(self): # GIVEN learning_unit = create_learning_unit( # noqa course=self.course, circle=self.circle, ) create_performance_criteria_page( course=self.course, course_page=self.course_page, circle=self.circle, learning_unit=learning_unit, ) self.client.force_login(self.member) # WHEN response = self.client.get( reverse( "get_course_session_user_feedback_summaries", args=[self.course_session.id, self.member.id], ) ) # THEN self.assertEqual(response.status_code, 200) result = response.data["results"][0] self.assertEqual(result["title"], learning_unit.title) self.assertEqual(result["id"], learning_unit.id) self.assertEqual(result["circle_id"], self.circle.id) self.assertEqual(result["circle_title"], self.circle.title) self.assertEqual(result["detail_url"], learning_unit.get_evaluate_url()) circles = response.data["circles"] self.assertEqual(len(circles), 1) self.assertEqual(circles[0]["id"], self.circle.id) self.assertEqual(circles[0]["title"], self.circle.title) def test_get_self_evaluation_feedback_as_provider(self): """Tests endpoint of feedback PROVIDER""" # GIVEN learning_unit = create_learning_unit( # noqa course=self.course, circle=self.circle ) performance_criteria_1 = create_performance_criteria_page( course=self.course, course_page=self.course_page, circle=self.circle, learning_unit=learning_unit, ) create_performance_criteria_page( course=self.course, course_page=self.course_page, circle=self.circle, learning_unit=learning_unit, ) completion = mark_course_completion( page=performance_criteria_1, user=self.member, course_session=self.course_session, completion_status=CourseCompletionStatus.SUCCESS.value, ) self_evaluation_feedback = create_self_evaluation_feedback( learning_unit=learning_unit, feedback_requester_user=self.member, feedback_provider_user=self.mentor, ) CourseCompletionFeedback.objects.create( feedback=self_evaluation_feedback, course_completion=completion, feedback_assessment=CourseCompletionStatus.FAIL.value, ) self.client.force_login(self.mentor) # WHEN response = self.client.get( reverse( "get_self_evaluation_feedback_as_provider", args=[self_evaluation_feedback.learning_unit.id], ) ) # THEN self.assertEqual(response.status_code, 200) feedback = response.data self.assertEqual(feedback["learning_unit_id"], learning_unit.id) self.assertEqual(feedback["title"], learning_unit.title) self.assertEqual(feedback["feedback_submitted"], False) self.assertEqual(feedback["circle_name"], self.circle.title) # noqa provider_user = feedback["feedback_provider_user"] self.assertEqual(provider_user["id"], str(self.mentor.id)) # noqa self.assertEqual(provider_user["first_name"], self.mentor.first_name) self.assertEqual(provider_user["last_name"], self.mentor.last_name) self.assertEqual(provider_user["avatar_url"], self.mentor.avatar_url) requester_user = feedback["feedback_requester_user"] self.assertEqual(requester_user["id"], str(self.member.id)) # noqa self.assertEqual(requester_user["first_name"], self.member.first_name) self.assertEqual(requester_user["last_name"], self.member.last_name) self.assertEqual(requester_user["avatar_url"], self.member.avatar_url) self.assertEqual(len(feedback["criteria"]), 2) first_criteria = feedback["criteria"][0] self.assertEqual(first_criteria["course_completion_id"], completion.id) self.assertEqual(first_criteria["title"], performance_criteria_1.title) self.assertEqual( first_criteria["self_assessment"], CourseCompletionStatus.SUCCESS.value, ) self.assertEqual( first_criteria["feedback_assessment"], CourseCompletionStatus.FAIL.value ) second_criteria = feedback["criteria"][1] self.assertEqual(second_criteria["course_completion_id"], None) self.assertEqual(second_criteria["title"], performance_criteria_1.title) self.assertEqual( second_criteria["self_assessment"], CourseCompletionStatus.UNKNOWN.value ) self.assertEqual( second_criteria["feedback_assessment"], CourseCompletionStatus.UNKNOWN.value, ) def test_self_evaluation_feedback_assessment(self): # GIVEN learning_unit = create_learning_unit(course=self.course, circle=self.circle) performance_criteria_1 = create_performance_criteria_page( course=self.course, course_page=self.course_page, circle=self.circle, learning_unit=learning_unit, ) completion = mark_course_completion( page=performance_criteria_1, user=self.member, course_session=self.course_session, completion_status=CourseCompletionStatus.SUCCESS.value, ) self_evaluation_feedback = create_self_evaluation_feedback( learning_unit=learning_unit, feedback_requester_user=self.member, feedback_provider_user=self.mentor, ) self.client.force_login(self.mentor) # WHEN response = self.client.put( reverse( "add_self_evaluation_feedback_assessment", args=[self_evaluation_feedback.id], ), { "course_completion_id": completion.id, "feedback_assessment": CourseCompletionStatus.FAIL.value, }, ) # THEN self.assertEqual(response.status_code, 200) self.assertEqual(response.data["success"], True) feedback = CourseCompletionFeedback.objects.get( feedback=self_evaluation_feedback, course_completion=completion, ) self.assertEqual( feedback.feedback_assessment, CourseCompletionStatus.FAIL.value ) @patch( "vbv_lernwelt.notify.services.NotificationService.send_self_evaluation_feedback_received_notification" ) def test_release_self_evaluation_feedback(self, mock_notification_service_send): # GIVEN learning_unit = create_learning_unit(course=self.course, circle=self.circle) self_evaluation_feedback = create_self_evaluation_feedback( learning_unit=learning_unit, feedback_requester_user=self.member, feedback_provider_user=self.mentor, ) self.assertEqual(self_evaluation_feedback.feedback_submitted, False) self.client.force_login(self.mentor) # WHEN self.client.put( reverse( "release_self_evaluation_feedback", args=[self_evaluation_feedback.id] ), ) # shall be idempotent response = self.client.put( reverse( "release_self_evaluation_feedback", args=[self_evaluation_feedback.id] ), ) # THEN self.assertEqual(response.status_code, 200) self.assertEqual(response.data["success"], True) self.assertEqual( SelfEvaluationFeedback.objects.get( id=self_evaluation_feedback.id ).feedback_submitted, True, ) mock_notification_service_send.assert_called_once_with( self_evaluation_feedback=self_evaluation_feedback ) def test_get_self_evaluation_feedback_frontend_urls(self): """Makes sure that the frontend urls are correct (used in notifications)""" # GIVEN learning_unit = create_learning_unit(course=self.course, circle=self.circle) cut = create_self_evaluation_feedback( learning_unit=learning_unit, feedback_requester_user=self.member, feedback_provider_user=self.mentor, ) # WHEN requester_url = cut.feedback_requester_results_url provider_url = cut.feedback_provider_evaluation_url # THEN _course, _circle, _learning_unit = learning_unit.get_frontend_url_parts() # 0 -> no completions so step=0 is correct _step = len(learning_unit.performancecriteria_set.all()) self.assertEqual( requester_url, f"/course/{_course}/learn/{_circle}/evaluate/{_learning_unit}?step={_step}", ) self.assertEqual( provider_url, f"/course/{_course}/learning-mentor/self-evaluation-feedback/{learning_unit.id}", )