479 lines
16 KiB
Python
479 lines
16 KiB
Python
from datetime import datetime
|
|
from typing import Tuple
|
|
|
|
from graphene_django.utils import GraphQLTestCase
|
|
|
|
from vbv_lernwelt.assignment.models import Assignment, AssignmentType
|
|
from vbv_lernwelt.course.creators.test_utils import (
|
|
add_course_session_group_supervisor,
|
|
add_course_session_user,
|
|
create_assignment,
|
|
create_assignment_completion,
|
|
create_assignment_learning_content,
|
|
create_circle,
|
|
create_competence_certificate,
|
|
create_course,
|
|
create_course_session,
|
|
create_course_session_assignment,
|
|
create_course_session_edoniq_test,
|
|
create_course_session_group,
|
|
create_user,
|
|
)
|
|
from vbv_lernwelt.course.models import CourseSession, CourseSessionUser
|
|
from vbv_lernwelt.course_session.models import (
|
|
CourseSessionAssignment,
|
|
CourseSessionEdoniqTest,
|
|
)
|
|
from vbv_lernwelt.learnpath.models import Circle
|
|
|
|
|
|
class AssignmentTestCase(GraphQLTestCase):
|
|
GRAPHQL_URL = "/server/graphql/"
|
|
GRAPHQL_QUERY = """query($course_id: ID!) {
|
|
course_statistics(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")
|
|
|
|
group = create_course_session_group(course_session=self.course_session)
|
|
add_course_session_group_supervisor(group=group, user=self.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_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.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,
|
|
add_competence_certificate=True,
|
|
)
|
|
|
|
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_statistics"]
|
|
|
|
records = dashboard["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_statistics"]
|
|
|
|
records = dashboard["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,
|
|
add_competence_certificate=True,
|
|
)
|
|
|
|
assignment_2, _ = mix_assignment_cocktail(
|
|
deadline_at=datetime(2000, 4, 1),
|
|
assignment_type=AssignmentType.EDONIQ_TEST,
|
|
course_session=self.course_session,
|
|
circle=self.circle,
|
|
add_competence_certificate=True,
|
|
)
|
|
|
|
assignment_3, _ = mix_assignment_cocktail(
|
|
deadline_at=datetime(2010, 4, 1),
|
|
assignment_type=AssignmentType.CASEWORK,
|
|
course_session=self.course_session,
|
|
circle=self.circle,
|
|
add_competence_certificate=True,
|
|
)
|
|
|
|
# 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,
|
|
add_competence_certificate=False,
|
|
)
|
|
|
|
# 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_statistics"]
|
|
|
|
# 1 -> incomplete (not counted for average)
|
|
# 2 -> complete 66% passed ...
|
|
# 3 -> complete 100% passed --> 67%
|
|
# 4 -> not included in certificate
|
|
summary = dashboard["assignments"]["summary"]
|
|
self.assertEqual(summary["completed_count"], 3)
|
|
self.assertEqual(summary["average_passed"], 67.0)
|
|
|
|
records = dashboard["assignments"]["records"]
|
|
self.assertEqual(len(records), 3)
|
|
|
|
# 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"], True)
|
|
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)
|
|
|
|
|
|
class ExpertAssignmentTestCase(AssignmentTestCase):
|
|
def setUp(self):
|
|
self.course, self.course_page = create_course("Test Course")
|
|
self.course_session = create_course_session(course=self.course, title=":)")
|
|
course_session = create_course_session(course=self.course, title=":(")
|
|
self.circle, _ = create_circle(title="Circle", course_page=self.course_page)
|
|
circle, _ = create_circle(title="Other Circle", course_page=self.course_page)
|
|
|
|
create_course_session_group(course_session=self.course_session)
|
|
|
|
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")
|
|
csu = add_course_session_user(
|
|
course_session=self.course_session,
|
|
user=self.e1,
|
|
role=CourseSessionUser.Role.EXPERT,
|
|
)
|
|
csu.expert.add(self.circle)
|
|
|
|
m4 = create_user("member_4")
|
|
add_course_session_user(
|
|
course_session=course_session,
|
|
user=m4,
|
|
role=CourseSessionUser.Role.MEMBER,
|
|
)
|
|
|
|
m5 = create_user("member_5")
|
|
add_course_session_user(
|
|
course_session=course_session,
|
|
user=m5,
|
|
role=CourseSessionUser.Role.MEMBER,
|
|
)
|
|
|
|
# assignments in the other course session should not be included
|
|
assignment_1, _ = mix_assignment_cocktail(
|
|
deadline_at=datetime(1990, 4, 1),
|
|
assignment_type=AssignmentType.CASEWORK,
|
|
course_session=course_session,
|
|
circle=self.circle,
|
|
add_competence_certificate=True,
|
|
)
|
|
|
|
assignment_2, _ = mix_assignment_cocktail(
|
|
deadline_at=datetime(2000, 4, 1),
|
|
assignment_type=AssignmentType.EDONIQ_TEST,
|
|
course_session=course_session,
|
|
circle=self.circle,
|
|
add_competence_certificate=True,
|
|
)
|
|
|
|
create_assignment_completion(
|
|
user=m4,
|
|
assignment=assignment_1,
|
|
course_session=course_session,
|
|
has_passed=True,
|
|
)
|
|
|
|
create_assignment_completion(
|
|
user=m5,
|
|
assignment=assignment_2,
|
|
course_session=course_session,
|
|
)
|
|
|
|
# assignments in the same course session, but in a different circle. Should not be included
|
|
assignment_3, _ = mix_assignment_cocktail(
|
|
deadline_at=datetime(1990, 4, 1),
|
|
assignment_type=AssignmentType.CASEWORK,
|
|
course_session=self.course_session,
|
|
circle=circle,
|
|
add_competence_certificate=True,
|
|
)
|
|
|
|
assignment_4, _ = mix_assignment_cocktail(
|
|
deadline_at=datetime(2000, 4, 1),
|
|
assignment_type=AssignmentType.EDONIQ_TEST,
|
|
course_session=self.course_session,
|
|
circle=circle,
|
|
add_competence_certificate=True,
|
|
)
|
|
|
|
create_assignment_completion(
|
|
user=self.m1,
|
|
assignment=assignment_1,
|
|
course_session=self.course_session,
|
|
has_passed=True,
|
|
)
|
|
|
|
create_assignment_completion(
|
|
user=self.m1,
|
|
assignment=assignment_2,
|
|
course_session=self.course_session,
|
|
)
|
|
|
|
self.client.force_login(self.e1)
|
|
|
|
|
|
def mix_assignment_cocktail(
|
|
assignment_type: AssignmentType,
|
|
course_session: CourseSession,
|
|
circle: Circle,
|
|
add_competence_certificate: bool = False,
|
|
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 add_competence_certificate:
|
|
certificate = create_competence_certificate(course=course_session.course)
|
|
assignment.competence_certificate = certificate
|
|
assignment.save()
|
|
|
|
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
|