from datetime import datetime from typing import Tuple from graphene_django.utils import GraphQLTestCase from vbv_lernwelt.assignment.models import ( AssignmentType, Assignment, ) from vbv_lernwelt.course.models import CourseSessionUser, CourseSession from vbv_lernwelt.course_session.models import ( CourseSessionAssignment, CourseSessionEdoniqTest, ) from vbv_lernwelt.dashboard.tests.graphql.utils import ( add_course_session_user, create_circle, create_course, create_course_session, create_user, create_assignment, create_assignment_completion, create_assignment_learning_content, create_course_session_edoniq_test, create_course_session_assignment, ) from vbv_lernwelt.learnpath.models import Circle class AssignmentTestCase(GraphQLTestCase): GRAPHQL_URL = "/server/graphql/" GRAPHQL_QUERY = f"""query($course_id: ID) {{ course_dashboard(course_id: $course_id) {{ assignments{{ summary{{ completed_count average_passed }} records{{ course_session_id course_session_assignment_id circle_id generation assignment_title assignment_type_translation_key details_url deadline metrics {{ passed_count failed_count unranked_count ranking_completed average_passed }} }} }} }} }}""" def setUp(self): self.course, self.course_page = create_course("Test Course") self.course_session = create_course_session(course=self.course, title=":)") self.circle, _ = create_circle(title="Circle", course_page=self.course_page) self.supervisor = create_user("supervisor") add_course_session_user( course_session=self.course_session, user=self.supervisor, role=CourseSessionUser.Role.SESSION_SUPERVISOR, ) self.m1 = create_user("member_1") add_course_session_user( course_session=self.course_session, user=self.m1, role=CourseSessionUser.Role.MEMBER, ) self.m2 = create_user("member_2") add_course_session_user( course_session=self.course_session, user=self.m2, role=CourseSessionUser.Role.MEMBER, ) self.m3 = create_user("member_3") add_course_session_user( course_session=self.course_session, user=self.m3, role=CourseSessionUser.Role.MEMBER, ) self.e1 = create_user("expert_1") add_course_session_user( course_session=self.course_session, user=self.e1, role=CourseSessionUser.Role.EXPERT, ) self.client.force_login(self.supervisor) def test_dashboard_contains_casework(self): self._test_assignment_type_dashboard_details( assignment_type=AssignmentType.CASEWORK ) def test_dashboard_contains_prep_assignments(self): self._test_assignment_type_dashboard_details( assignment_type=AssignmentType.PREP_ASSIGNMENT ) def test_dashboard_contains_edoniq_tests(self): self._test_assignment_type_dashboard_details( assignment_type=AssignmentType.EDONIQ_TEST ) def test_dashboard_not_contains_unsupported_types(self): """ Since everything is mixed in the same table, we need to make sure that the dashboard only contains the supported types does not get confused by the unsupported ones. """ irrelevant_types_for_dashboard = set(AssignmentType) - { AssignmentType.CASEWORK, AssignmentType.PREP_ASSIGNMENT, AssignmentType.EDONIQ_TEST, } for assignment_type in irrelevant_types_for_dashboard: self._test_assignment_type_not_in_dashboard(assignment_type=assignment_type) def _test_assignment_type_dashboard_details(self, assignment_type: AssignmentType): # GIVEN assignment, csa = mix_assignment_cocktail( assignment_type=assignment_type, deadline_at=datetime(2000, 4, 1), course_session=self.course_session, circle=self.circle, ) create_assignment_completion( user=self.m1, assignment=assignment, course_session=self.course_session, ) # WHEN response = self.query( self.GRAPHQL_QUERY, variables={"course_id": str(self.course.id)} ) # THEN self.assertResponseNoErrors(response) dashboard = response.json()["data"]["course_dashboard"] records = dashboard[0]["assignments"]["records"] self.assertEqual(len(records), 1) record = records[0] if isinstance(csa, CourseSessionAssignment): due_date = csa.submission_deadline else: due_date = csa.deadline self.assertEqual(record["course_session_id"], str(self.course_session.id)) self.assertEqual(record["course_session_assignment_id"], str(csa.id)) self.assertEqual(record["generation"], str(self.course_session.generation)) self.assertEqual(record["circle_id"], str(self.circle.id)) self.assertEqual(record["details_url"], due_date.url_expert) self.assertEqual(datetime.fromisoformat(record["deadline"]), due_date.start) self.assertEqual( record["assignment_title"], csa.learning_content.content_assignment.title, ) self.assertEqual( record["assignment_type_translation_key"], due_date.assignment_type_translation_key, ) def _test_assignment_type_not_in_dashboard(self, assignment_type: AssignmentType): _, csa = mix_assignment_cocktail( assignment_type=assignment_type, course_session=self.course_session, circle=self.circle, ) # WHEN response = self.query( self.GRAPHQL_QUERY, variables={"course_id": str(self.course.id)} ) # THEN self.assertResponseNoErrors(response) dashboard = response.json()["data"]["course_dashboard"] records = dashboard[0]["assignments"]["records"] self.assertEqual(len(records), 0) def test_metrics_summary(self): # GIVEN assignment_1, _ = mix_assignment_cocktail( deadline_at=datetime(1990, 4, 1), assignment_type=AssignmentType.CASEWORK, course_session=self.course_session, circle=self.circle, ) assignment_2, _ = mix_assignment_cocktail( deadline_at=datetime(2000, 4, 1), assignment_type=AssignmentType.EDONIQ_TEST, course_session=self.course_session, circle=self.circle, ) assignment_3, _ = mix_assignment_cocktail( deadline_at=datetime(2010, 4, 1), assignment_type=AssignmentType.PREP_ASSIGNMENT, course_session=self.course_session, circle=self.circle, ) # no completions for this assignment yet assignment_4, _ = mix_assignment_cocktail( deadline_at=datetime(2020, 4, 1), assignment_type=AssignmentType.EDONIQ_TEST, course_session=self.course_session, circle=self.circle, ) # assignment 1 assigment_1_results = [ (self.m1, True), # passed (self.m2, False), # failed (self.m3, None), # unranked ] for user, has_passed in assigment_1_results: if has_passed is None: continue create_assignment_completion( user=user, assignment=assignment_1, course_session=self.course_session, has_passed=has_passed, ) # assignment 2 assignment_2_results = [ (self.m1, True), # passed (self.m2, True), # passed (self.m3, False), # failed ] for user, has_passed in assignment_2_results: create_assignment_completion( user=user, assignment=assignment_2, course_session=self.course_session, has_passed=has_passed, ) # assignment 3 assignment_3_results = [ (self.m1, True), # passed (self.m2, True), # passed (self.m3, True), # passed ] for user, has_passed in assignment_3_results: create_assignment_completion( user=user, assignment=assignment_3, course_session=self.course_session, has_passed=has_passed, ) # WHEN response = self.query( self.GRAPHQL_QUERY, variables={"course_id": str(self.course.id)} ) # THEN self.assertResponseNoErrors(response) dashboard = response.json()["data"]["course_dashboard"] # 1 -> incomplete (not counted for average) # 2 -> complete 66% passed ... # 3 -> complete 100% passed --> 83.5% # 4 -> incomplete (not counted for average) summary = dashboard[0]["assignments"]["summary"] self.assertEqual(summary["completed_count"], 2) self.assertEqual(summary["average_passed"], 83.5) records = dashboard[0]["assignments"]["records"] self.assertEqual(len(records), 4) # 1 -> assigment_1_results (oldest) assignment_1_metrics = records[0]["metrics"] self.assertEqual(assignment_1_metrics["passed_count"], 1) self.assertEqual(assignment_1_metrics["failed_count"], 1) self.assertEqual(assignment_1_metrics["unranked_count"], 1) self.assertEqual(assignment_1_metrics["ranking_completed"], False) self.assertEqual(assignment_1_metrics["average_passed"], 34) # 2 -> assignment_2_results assignment_2_metrics = records[1]["metrics"] self.assertEqual(assignment_2_metrics["passed_count"], 2) self.assertEqual(assignment_2_metrics["failed_count"], 1) self.assertEqual(assignment_2_metrics["unranked_count"], 0) self.assertEqual(assignment_2_metrics["ranking_completed"], True) self.assertEqual(assignment_2_metrics["average_passed"], 67) # 3 -> assignment_3_results assignment_3_metrics = records[2]["metrics"] self.assertEqual(assignment_3_metrics["passed_count"], 3) self.assertEqual(assignment_3_metrics["failed_count"], 0) self.assertEqual(assignment_3_metrics["unranked_count"], 0) self.assertEqual(assignment_3_metrics["ranking_completed"], True) self.assertEqual(assignment_3_metrics["average_passed"], 100) # 4 -> no completions (newest) assignment_4_metrics = records[3]["metrics"] self.assertEqual(assignment_4_metrics["passed_count"], 0) self.assertEqual(assignment_4_metrics["failed_count"], 0) self.assertEqual(assignment_4_metrics["unranked_count"], 3) self.assertEqual(assignment_4_metrics["ranking_completed"], False) self.assertEqual(assignment_4_metrics["average_passed"], 0) def mix_assignment_cocktail( assignment_type: AssignmentType, course_session: CourseSession, circle: Circle, deadline_at: datetime | None = None, ) -> Tuple[Assignment, CourseSessionAssignment | CourseSessionEdoniqTest]: """ Little test helper to create a course session assignment or edoniq test based on the given assignment type. """ assignment = create_assignment( course=course_session.course, assignment_type=assignment_type ) if assignment_type == AssignmentType.EDONIQ_TEST: cset = create_course_session_edoniq_test( deadline_at=deadline_at, course_session=course_session, learning_content_edoniq_test=create_assignment_learning_content( circle=circle, assignment=assignment, ), ) return assignment, cset else: csa = create_course_session_assignment( deadline_at=deadline_at, course_session=course_session, learning_content_assignment=create_assignment_learning_content( circle=circle, assignment=assignment, ), ) return assignment, csa