def test_student_activity__no_questions_done( client, teacher, group, students, student_group_assignments, student_assignments, ): assert login_teacher(client, teacher) group.teacher.add(teacher) teacher.current_groups.add(group) resp = client.post( reverse("teacher-dashboard--student-activity"), json.dumps({}), content_type="application/json", ) data = json.loads(resp.content)["groups"][0] assert data["title"] == group.title assert data["n_students"] == len(students) assert data["new"] is True assert len(data["assignments"]) == len(student_group_assignments) for assignment, assignment_ in zip(data["assignments"], student_group_assignments): assert assignment["title"] == assignment_.assignment.title assert assignment["n_completed"] == 0 assert assignment["mean_grade"] == 0 assert assignment["min_grade"] == 0 assert assignment["max_grade"] == 0 assert assignment["new"] is True assert assignment["expired"] is False assert assignment["link"].endswith(assignment_.hash + "/")
def test_evaluate_rationale__missing_params(client, teacher, answers): answer = answers[0] assert login_teacher(client, teacher) n = AnswerAnnotation.objects.count() resp = client.post( reverse("teacher-dashboard--evaluate-rationale"), {"id": answer.pk} ) assert resp.status_code == 400 assert AnswerAnnotation.objects.count() == n resp = client.post( reverse("teacher-dashboard--evaluate-rationale"), {"score": 0} ) assert resp.status_code == 400 assert AnswerAnnotation.objects.count() == n resp = client.post(reverse("teacher-dashboard--evaluate-rationale"), {}) assert resp.status_code == 400 assert AnswerAnnotation.objects.count() == n
def test_download_gradebook__assignment(client, teacher): assert login_teacher(client, teacher) RunningTask.objects.create(id=1, description="test", teacher=teacher) with mock.patch( "peerinst.views.teacher.convert_gradebook_to_csv" ) as convert_gradebook_to_csv, mock.patch( "peerinst.views.teacher.AsyncResult" ) as AsyncResult: convert_gradebook_to_csv.return_value = iter(["test\n"]) result = mock.Mock() result.ready.return_value = True result.result = {"group": "test", "assignment": "test"} AsyncResult.return_value = result resp = client.post( reverse("teacher-gradebook--download"), json.dumps({"task_id": 1}), content_type="application/json", ) assert resp.status_code == 200 assert isinstance(resp, StreamingHttpResponse) assert ( next(resp.streaming_content).strip().decode() == "myDALITE_gradebook_test_test.csv" ) assert next(resp.streaming_content).strip().decode() == "test" assert not RunningTask.objects.filter(id=1).exists()
def test_in_teacher_list_permission_in_view( client, student_group_assignment, teacher ): assert teacher not in student_group_assignment.group.teacher.all() assert login_teacher(client, teacher) url = reverse( "REST:student-group-assigment-answers", args=[ student_group_assignment.pk, student_group_assignment.assignment.questions.first().pk, ], ) response = client.get(url) # 404 based on limited queryset assert response.status_code == status.HTTP_404_NOT_FOUND student_group_assignment.group.teacher.add(teacher) assert teacher in student_group_assignment.group.teacher.all() response = client.get(url) assert response.status_code == status.HTTP_200_OK
def test_request_gradebook__assignment__no_celery(client, teacher, group, student_group_assignment): assert login_teacher(client, teacher) group.teacher.add(teacher) n = RunningTask.objects.count() with mock.patch( "peerinst.views.teacher.compute_gradebook_async" ) as compute_gradebook_async, mock.patch( "peerinst.views.teacher.download_gradebook") as download_gradebook: result = mock.Mock() compute_gradebook_async.return_value = result download_gradebook.return_value = HttpResponse() resp = client.post( reverse("teacher-gradebook--request"), json.dumps({ "group_id": group.pk, "assignment_id": student_group_assignment.pk, }), content_type="application/json", ) assert resp.status_code == 200 compute_gradebook_async.assert_called_with(group.pk, student_group_assignment.pk) download_gradebook.assert_called_with(mock.ANY, results=result) assert RunningTask.objects.count() == n
def test_rationales_to_score(client, teacher, answers, discipline): teacher.disciplines.add(discipline) for i, answer in enumerate(answers): answer.question.discipline = discipline answer.question.save() assert login_teacher(client, teacher) with mock.patch("peerinst.rationale_annotation.Quality") as Quality: qualities = [float(i) / len(answers) for i in range(len(answers))] Quality.objects.filter.return_value.exists.return_value = True Quality.objects.get.return_value.batch_evaluate.return_value = [ (q, None) for q in qualities ] resp = client.post( reverse("teacher-dashboard--rationales"), json.dumps({}), content_type="application/json", ) assert resp.status_code == 200 data = json.loads(resp.content.decode()) assert len(data["rationales"]) == 5 for a, a_ in zip(data["rationales"], answers[::-1]): assert a["id"] == a_.pk assert a["title"] == a_.question.title assert a["rationale"] == a_.rationale assert a["choice"] == a_.first_answer_choice assert (a["text"] == a_.question.answerchoice_set.all()[ a_.first_answer_choice - 1].text) assert a["correct"] == a_.question.is_correct( a_.first_answer_choice)
def test_collections__with_params(client, collections, teachers, discipline): teacher = teachers[0] teachers = teachers[1:] teacher.disciplines.add(discipline) assert login_teacher(client, teacher) for i, collection in enumerate(collections): collection.followers.remove(*teachers[:-i - 1]) resp = client.post( reverse("teacher-dashboard--collections"), json.dumps({"n": 1}), content_type="application/json", ) assert resp.status_code == 200 data = json.loads(resp.content) assert len(data["collections"]) == 1 for collection, collection_ in zip(data["collections"], reversed(collections)): assert collection["title"] == collection_.title assert collection["description"] == collection_.description assert collection["discipline"] == collection_.discipline.title assert collection["n_assignments"] == collection_.assignments.count() assert collection["n_followers"] == collection_.followers.count()
def test_student_activity__dashboard(client, teacher, group, questions, student, student_group_assignments): assert login_teacher(client, teacher) group.teacher.add(teacher) teacher.current_groups.add(group) student.groups.add(group) add_answers( student, questions, student_group_assignments[0].assignment, correct_first=True, answer_second=True, correct_second=True, ) resp = client.get(reverse("teacher-dashboard")) assert resp.status_code == 200 assert any(t.name == "peerinst/teacher/cards/student_activity_card.html" for t in resp.templates) assert any(t.name == "peerinst/teacher/dashboard.html" for t in resp.templates) assert str(student_group_assignments[0].assignment.title) in resp.content assert str(group.title) in resp.content assert (str(student_group_assignments[1].assignment.title) not in resp.content)
def test_request_gradebook__group(client, teacher, group): assert login_teacher(client, teacher) group.teacher.add(teacher) class AsyncResultMock(AsyncResult): def __new__(cls): return mock.mock(spec=cls) with mock.patch("peerinst.views.teacher.compute_gradebook_async" ) as compute_gradebook_async, mock.patch( "peerinst.views.teacher.AsyncResult", new=AsyncResultMock): result = mock.Mock(spec=AsyncResultMock) result.id = 1 compute_gradebook_async.return_value = result resp = client.post( reverse("teacher-gradebook--request"), json.dumps({"group_id": group.pk}), content_type="application/json", ) compute_gradebook_async.assert_called_with(group.pk, None) assert resp.status_code == 201 data = json.loads(resp.content) assert data["id"] == 1 assert data[ "description"] == "gradebook for group <strong>{}</strong>".format( group.name) assert not data["completed"] assert RunningTask.objects.filter(id=1).exists()
def test_messages(client, teacher, thread): assert login_teacher(client, teacher) notification_type = ContentType.objects.get( app_label="pinax_forums", model="ThreadSubscription" ) resp = client.get(reverse("teacher-dashboard--messages")) assert resp.status_code == 200 data = json.loads(resp.content.decode())["threads"] assert len(data) == 1 for thread in data: assert "id" in thread assert "last_reply" in thread assert "author" in thread["last_reply"] assert "content" in thread["last_reply"] assert ( thread["n_new"] == TeacherNotification.objects.filter( teacher=teacher, notification_type=notification_type, object_id__in=ForumThread.objects.get( id=thread["id"] ).subscriptions.values_list("id"), ).count() ) resp = client.get(thread["link"]) assert resp.status_code == 200 assert "pinax/forums/thread.html" in resp.template_name
def test_rationales__dashboard(client, teacher, discipline, answers): assert login_teacher(client, teacher) teacher.disciplines.add(discipline) for i, answer in enumerate(answers): answer.question.discipline = discipline answer.question.save() answer.second_answer_choice = 1 answer.save() n = AnswerAnnotation.objects.count() resp = client.get(reverse("teacher-dashboard--rationales")) assert resp.status_code == 200 assert any(t.name == "peerinst/teacher/cards/rationale_to_score_card.html" for t in resp.templates) resp = client.post( reverse("teacher-dashboard--evaluate-rationale"), { "id": answers[0].id, "score": 0 }, follow=True, ) assert resp.status_code == 200 assert any(t.name == "peerinst/teacher/cards/rationale_to_score_card.html" for t in resp.templates) assert AnswerAnnotation.objects.count() == n + 1
def test_discipline_list(client, disciplines, student, teacher): """ Requirements: 1. Must be authenticated 2. Must not be a student to GET 3. Must be admin for anything else """ # 1. Must be authenticated url = reverse("REST:discipline-list") response = client.get(url) assert response.status_code == status.HTTP_403_FORBIDDEN # 2. Must not be a student to GET assert login_student(client, student) response = client.get(url) assert response.status_code == status.HTTP_403_FORBIDDEN assert login_teacher(client, teacher) response = client.get(url) assert response.status_code == status.HTTP_200_OK retrieved_disciplines = json.loads(response.content) for d in disciplines: assert d.pk in [d["pk"] for d in retrieved_disciplines] # 3. Must be admin for anything else response = client.post(url, {"title": "New discipline"}) assert response.status_code == status.HTTP_403_FORBIDDEN
def test_unsubscribe_from_thread__wrong_thread(client, teacher): assert login_teacher(client, teacher) resp = client.post( reverse("teacher-dashboard--unsubscribe-thread"), json.dumps({"id": 1}), content_type="application/json", ) assert resp.status_code == 400
def test_teacher_favourites(client, questions, teachers): """ Requirements: 1. Must be authenticated 2. Only current teacher endpoint is accessible via GET 3. Can update favourites through PUT 4. No other http methods """ # Setup teachers[0].favourite_questions.add(questions[0], questions[1]) # 1. Must be authenticated url = reverse("REST:teacher", args=[teachers[0].pk]) response = client.get(url) assert response.status_code == status.HTTP_403_FORBIDDEN # 2. Only current teacher endpoint is accessible via GET assert login_teacher(client, teachers[0]) response = client.get(url) assert response.status_code == status.HTTP_200_OK favourites = json.loads(response.content)["favourite_questions"] for f in favourites: assert f in [fq.pk for fq in teachers[0].favourite_questions.all()] url = reverse("REST:teacher", args=[teachers[1].pk]) response = client.get(url) assert response.status_code == status.HTTP_404_NOT_FOUND # 3. Can update favourites through PUT url = reverse("REST:teacher", args=[teachers[0].pk]) new_favourites = [questions[0].pk, questions[2].pk] response = client.put( url, {"favourite_questions": new_favourites}, content_type="application/json", ) retrieved_favourites = json.loads(response.content)["favourite_questions"] for q in retrieved_favourites: assert q in new_favourites assert q in teachers[0].favourite_questions.values_list( "pk", flat=True ) assert questions[1].pk not in retrieved_favourites assert questions[1] not in teachers[0].favourite_questions.all() # 4. No other http methods disallowed = ["post", "patch", "delete", "head", "options", "trace"] for method in disallowed: response = getattr(client, method)(url) assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED
def test_remove_gradebook_task__doesn_t_exists(client, teacher): assert login_teacher(client, teacher) resp = client.post( reverse("teacher-gradebook--remove"), json.dumps({"task_id": 1}), content_type="application/json", ) assert resp.status_code == 200
def test_remove_gradebook_task__missing_params(client, teacher): assert login_teacher(client, teacher) resp = client.post( reverse("teacher-gradebook--remove"), json.dumps({}), content_type="application/json", ) assert resp.status_code == 400
def test_evaluate_rationale__wrong_answer_pk(client, teacher): assert login_teacher(client, teacher) n = AnswerAnnotation.objects.count() resp = client.post( reverse("teacher-dashboard--evaluate-rationale"), {"id": 0, "score": 0} ) assert resp.status_code == 400 assert AnswerAnnotation.objects.count() == n
def test_unsubscribe_from_thread(client, teacher, thread): assert login_teacher(client, teacher) assert ThreadSubscription.objects.filter(user=teacher.user, thread=thread).count() resp = client.post( reverse("teacher-dashboard--unsubscribe-thread"), json.dumps({"id": thread.pk}), content_type="application/json", ) assert resp.status_code == 200 assert not ThreadSubscription.objects.filter(user=teacher.user, thread=thread).count()
def test_new_questions__wrong_params(client, teacher): assert login_teacher(client, teacher) with mock.patch( "peerinst.views.teacher.get_json_params", return_value=HttpResponse("", status=400), ): resp = client.post( reverse("teacher-dashboard--new-questions"), json.dumps({}), content_type="application/json", ) assert resp.status_code == 400
def test_remove_gradebook_task__exists(client, teacher): assert login_teacher(client, teacher) RunningTask.objects.create(id=1, description="test", teacher=teacher) resp = client.post( reverse("teacher-gradebook--remove"), json.dumps({"task_id": 1}), content_type="application/json", ) assert resp.status_code == 200 assert not RunningTask.objects.filter(id=1).exists()
def test_request_gradebook__no_teacher_access(client, teacher, group): assert login_teacher(client, teacher) n = RunningTask.objects.count() resp = client.post( reverse("teacher-gradebook--request"), json.dumps({"group_id": group.pk}), content_type="application/json", ) assert resp.status_code == 403 assert RunningTask.objects.count() == n
def test_request_gradebook__missing_params(client, teacher, group): assert login_teacher(client, teacher) group.teacher.add(teacher) n = RunningTask.objects.count() resp = client.post( reverse("teacher-gradebook--request"), json.dumps({}), content_type="application/json", ) assert resp.status_code == 400 assert RunningTask.objects.count() == n
def test_gradebook_task_result__ready(client, teacher): assert login_teacher(client, teacher) with mock.patch("peerinst.views.teacher.AsyncResult") as AsyncResult: result = mock.Mock() result.ready.return_value = True AsyncResult.return_value = result resp = client.post( reverse("teacher-gradebook--result"), json.dumps({"task_id": 1}), content_type="application/json", ) assert resp.status_code == 200
def test_student_activity__some_questions_done_correct_first_wrong_second( client, teacher, group, students, student_group_assignments, student_assignments, ): assert login_teacher(client, teacher) group.teacher.add(teacher) teacher.current_groups.add(group) for assignment in student_group_assignments: for question in assignment.questions: question.grading_scheme = GradingScheme.ADVANCED question.save() for assignment_ in assignment.studentassignment_set.all()[::2]: add_answers( student=assignment_.student, questions=assignment.questions, assignment=assignment.assignment, correct_first=True, correct_second=False, ) resp = client.post( reverse("teacher-dashboard--student-activity"), json.dumps({}), content_type="application/json", ) data = json.loads(resp.content)["groups"][0] assert data["title"] == group.title assert data["n_students"] == len(students) assert data["new"] is True assert len(data["assignments"]) == len(student_group_assignments) for assignment, assignment_ in zip(data["assignments"], student_group_assignments): assert assignment["title"] == assignment_.assignment.title assert assignment["n_completed"] == float(len(students[::2])) assert ( assignment["mean_grade"] == float(len(assignment_.questions)) // 2) assert (assignment["min_grade"] == float(len(assignment_.questions)) // 2) assert (assignment["max_grade"] == float(len(assignment_.questions)) // 2) assert assignment["new"] is True assert assignment["expired"] is False assert assignment["link"].endswith(assignment_.hash + "/")
def test_evaluate_rationale(client, teacher, answers): answer = answers[0] assert login_teacher(client, teacher) n = AnswerAnnotation.objects.count() resp = client.post( reverse("teacher-dashboard--evaluate-rationale"), {"id": answer.pk, "score": 0}, follow=True, ) assert resp.status_code == 200 assert any( t.name == "peerinst/teacher/cards/rationale_to_score_card.html" for t in resp.templates ) assert AnswerAnnotation.objects.count() == n + 1
def test_new_questions__dashboard(client, teacher, questions, assignment, disciplines): assert login_teacher(client, teacher) for question in questions[:len(questions) // 2]: question.discipline = disciplines[0] question.save() for question in questions[len(questions) // 2:]: question.discipline = disciplines[1] question.save() teacher.disciplines.add(disciplines[0]) resp = client.get(reverse("teacher-dashboard--new-questions")) assert resp.status_code == 200 assert any(t.name == "peerinst/question/cards/question_card.html" for t in resp.templates) assert str(disciplines[0]) in resp.content assert str(disciplines[1]) not in resp.content
def test_download_gradebook__celery_error(client, teacher): assert login_teacher(client, teacher) RunningTask.objects.create(id=1, description="test", teacher=teacher) with mock.patch("peerinst.views.teacher.convert_gradebook_to_csv" ) as convert_gradebook_to_csv, mock.patch( "peerinst.views.teacher.AsyncResult") as AsyncResult: convert_gradebook_to_csv.return_value = iter(["test\n"]) result = mock.Mock() result.ready.side_effect = AttributeError() AsyncResult.return_value = result resp = client.post( reverse("teacher-gradebook--download"), json.dumps({"task_id": 1}), content_type="application/json", ) assert resp.status_code == 500 assert RunningTask.objects.filter(id=1).exists()
def test_get_tasks(client, teacher): assert login_teacher(client, teacher) for i in range(1, 11): RunningTask.objects.create( id=i, description="test{}".format(i), teacher=teacher ) with mock.patch("peerinst.views.teacher.AsyncResult") as AsyncResult: result = mock.Mock() completed = (i % 2 == 0 for i in reversed(list(range(1, 11)))) result.ready = lambda: next(completed) AsyncResult.return_value = result resp = client.get(reverse("teacher-tasks")) assert resp.status_code == 200 data = json.loads(resp.content.decode()) for task, i in zip(data["tasks"], reversed(list(range(1, 11)))): assert task["id"] == str(i) assert task["description"] == "test{}".format(i) assert task["completed"] == (i % 2 == 0)
def test_clone_assignment(client, assignment, teacher): assert login_teacher(client, teacher) resp = client.post( reverse("assignment-copy", args=[assignment.pk]), { "identifier": "unique", "title": "title", "description": "a bucket of my questions", }, follow=True, ) assert resp.status_code == 200 new_assignment = Assignment.objects.last() for q in new_assignment.questions.all(): assert q in assignment.questions.all() assert new_assignment.questions.count() == assignment.questions.count() assert new_assignment.parent == assignment
def test_download_gradebook__no_running_task(client, teacher): assert login_teacher(client, teacher) with mock.patch("peerinst.views.teacher.convert_gradebook_to_csv" ) as convert_gradebook_to_csv, mock.patch( "peerinst.views.teacher.AsyncResult") as AsyncResult: convert_gradebook_to_csv.return_value = iter(["test\n"]) result = mock.Mock() result.ready.return_value = True result.result = {"group": "test"} AsyncResult.return_value = result resp = client.post( reverse("teacher-gradebook--download"), json.dumps({"task_id": 1}), content_type="application/json", ) assert resp.status_code == 200 assert isinstance(resp, StreamingHttpResponse) assert (next( resp.streaming_content).strip() == "myDALITE_gradebook_test.csv") assert next(resp.streaming_content).strip() == "test"