Repository: e-valuation/EvaP

Issue:
Remove test_coverage and test_usecases
test_coverage contains some "real" tests which should be moved to appropriate places, and some tests that don't really test anything which should be removed.

test_usecases has tests that basically start on the main page, navigate to some view and then test that view. we should probably remove the navigating and make ordinary view tests out of them.

successor to #707 



Discussion:

Resolving patch:
diff --git a/evap/evaluation/models.py b/evap/evaluation/models.py
--- a/evap/evaluation/models.py
+++ b/evap/evaluation/models.py
@@ -263,10 +263,6 @@ def save(self, *args, **kw):
     def is_fully_reviewed(self):
         return not self.open_textanswer_set.exists()
 
-    @property
-    def is_not_fully_reviewed(self):
-        return self.open_textanswer_set.exists()
-
     @property
     def vote_end_datetime(self):
         # The evaluation ends at EVALUATION_END_OFFSET_HOURS:00 of the day AFTER self.vote_end_date.
@@ -329,10 +325,6 @@ def can_staff_edit(self):
     def can_staff_delete(self):
         return self.can_staff_edit and (not self.num_voters > 0 or self.is_single_result)
 
-    @property
-    def can_staff_approve(self):
-        return self.state in ['new', 'prepared', 'editor_approved']
-
     @property
     def can_publish_grades(self):
         from evap.results.tools import get_sum_of_answer_counters
@@ -377,7 +369,7 @@ def review_finished(self):
     def single_result_created(self):
         pass
 
-    @transition(field=state, source='reviewed', target='evaluated', conditions=[lambda self: self.is_not_fully_reviewed])
+    @transition(field=state, source='reviewed', target='evaluated', conditions=[lambda self: not self.is_fully_reviewed])
     def reopen_review(self):
         pass
 
@@ -431,38 +423,19 @@ def days_until_evaluation(self):
     def is_user_editor_or_delegate(self, user):
         if self.contributions.filter(can_edit=True, contributor=user).exists():
             return True
-        else:
-            represented_users = user.represented_users.all()
-            if self.contributions.filter(can_edit=True, contributor__in=represented_users).exists():
-                return True
-
-        return False
-
-    def is_user_responsible_or_delegate(self, user):
-        if self.contributions.filter(responsible=True, contributor=user).exists():
+        represented_users = user.represented_users.all()
+        if self.contributions.filter(can_edit=True, contributor__in=represented_users).exists():
             return True
-        else:
-            represented_users = user.represented_users.all()
-            if self.contributions.filter(responsible=True, contributor__in=represented_users).exists():
-                return True
-
         return False
 
-    def is_user_contributor(self, user):
-        return self.contributions.filter(contributor=user).exists()
-
     def is_user_contributor_or_delegate(self, user):
-        if self.is_user_contributor(user):
+        if self.contributions.filter(contributor=user).exists():
+            return True
+        represented_users = user.represented_users.all()
+        if self.contributions.filter(contributor__in=represented_users).exists():
             return True
-        else:
-            represented_users = user.represented_users.all()
-            if self.contributions.filter(contributor__in=represented_users).exists():
-                return True
         return False
 
-    def is_user_editor(self, user):
-        return self.contributions.filter(contributor=user, can_edit=True).exists()
-
     def warnings(self):
         result = []
         if self.state in ['new', 'prepared', 'editor_approved'] and not self.all_contributions_have_questionnaires:
@@ -734,10 +707,6 @@ class Meta:
         verbose_name = _("text answer")
         verbose_name_plural = _("text answers")
 
-    @property
-    def is_reviewed(self):
-        return self.state != self.NOT_REVIEWED
-
     @property
     def is_hidden(self):
         return self.state == self.HIDDEN


Test patch:
diff --git a/evap/contributor/tests/test_views.py b/evap/contributor/tests/test_views.py
--- a/evap/contributor/tests/test_views.py
+++ b/evap/contributor/tests/test_views.py
@@ -1,4 +1,6 @@
-from evap.evaluation.models import Course
+from model_mommy import mommy
+
+from evap.evaluation.models import Course, UserProfile
 from evap.evaluation.tests.tools import ViewTest, course_with_responsible_and_editor
 
 TESTING_COURSE_ID = 2
@@ -21,32 +23,57 @@ class TestContributorSettingsView(ViewTest):
     def setUpTestData(cls):
         course_with_responsible_and_editor()
 
+    def test_save_settings(self):
+        user = mommy.make(UserProfile)
+        page = self.get_assert_200(self.url, "responsible")
+        form = page.forms["settings-form"]
+        form["delegates"] = [user.pk]
+        form.submit()
+
+        self.assertEquals(list(UserProfile.objects.get(username='responsible').delegates.all()), [user])
+
 
 class TestContributorCourseView(ViewTest):
-    test_users = ['editor', 'responsible']
     url = '/contributor/course/%s' % TESTING_COURSE_ID
+    test_users = ['editor', 'responsible']
 
     @classmethod
     def setUpTestData(cls):
-        course_with_responsible_and_editor(course_id=TESTING_COURSE_ID)
+        cls.course = course_with_responsible_and_editor(course_id=TESTING_COURSE_ID)
+
+    def test_wrong_state(self):
+        self.course.revert_to_new()
+        self.course.save()
+        self.get_assert_403(self.url, 'responsible')
 
 
 class TestContributorCoursePreviewView(ViewTest):
-    test_users = ['editor', 'responsible']
     url = '/contributor/course/%s/preview' % TESTING_COURSE_ID
+    test_users = ['editor', 'responsible']
 
     @classmethod
     def setUpTestData(cls):
-        course_with_responsible_and_editor(course_id=TESTING_COURSE_ID)
+        cls.course = course_with_responsible_and_editor(course_id=TESTING_COURSE_ID)
+
+    def setUp(self):
+        self.course = Course.objects.get(pk=TESTING_COURSE_ID)
+
+    def test_wrong_state(self):
+        self.course.revert_to_new()
+        self.course.save()
+        self.get_assert_403(self.url, 'responsible')
 
 
 class TestContributorCourseEditView(ViewTest):
-    test_users = ['editor', 'responsible']
     url = '/contributor/course/%s/edit' % TESTING_COURSE_ID
+    test_users = ['editor', 'responsible']
 
     @classmethod
     def setUpTestData(cls):
-        course_with_responsible_and_editor(course_id=TESTING_COURSE_ID)
+        cls.course = course_with_responsible_and_editor(course_id=TESTING_COURSE_ID)
+
+    def setUp(self):
+        self.course = Course.objects.get(pk=TESTING_COURSE_ID)
 
     def test_not_authenticated(self):
         """
@@ -68,10 +95,8 @@ def test_wrong_state(self):
             Asserts that a contributor attempting to edit a course
             that is in a state where editing is not allowed gets a 403.
         """
-        course = Course.objects.get(pk=TESTING_COURSE_ID)
-
-        course.editor_approve()
-        course.save()
+        self.course.editor_approve()
+        self.course.save()
 
         self.get_assert_403(self.url, 'responsible')
 
@@ -80,20 +105,18 @@ def test_contributor_course_edit(self):
             Tests whether the "save" button in the contributor's course edit view does not
             change the course's state, and that the "approve" button does that.
         """
-        course = Course.objects.get(pk=TESTING_COURSE_ID)
-
         page = self.get_assert_200(self.url, user="responsible")
         form = page.forms["course-form"]
         form["vote_start_datetime"] = "2098-01-01 11:43:12"
         form["vote_end_date"] = "2099-01-01"
 
         form.submit(name="operation", value="save")
-        course = Course.objects.get(pk=course.pk)
-        self.assertEqual(course.state, "prepared")
+        self.course = Course.objects.get(pk=self.course.pk)
+        self.assertEqual(self.course.state, "prepared")
 
         form.submit(name="operation", value="approve")
-        course = Course.objects.get(pk=course.pk)
-        self.assertEqual(course.state, "editor_approved")
+        self.course = Course.objects.get(pk=self.course.pk)
+        self.assertEqual(self.course.state, "editor_approved")
 
         # test what happens if the operation is not specified correctly
         response = form.submit(expect_errors=True)
diff --git a/evap/evaluation/tests/test_coverage.py b/evap/evaluation/tests/test_coverage.py
deleted file mode 100644
--- a/evap/evaluation/tests/test_coverage.py
+++ /dev/null
@@ -1,121 +0,0 @@
-from django.test.utils import override_settings
-
-from evap.evaluation.models import Course
-from evap.evaluation.tests.tools import WebTest
-
-
-"""
-These tests were created to get a higher test coverage. Some actually contain functional
-tests and should be moved to their appropriate place, others don't really test anything
-and should be replaced by better tests. Eventually, this file is to be removed.
-"""
-
-
-@override_settings(INSTITUTION_EMAIL_DOMAINS=["example.com"])
-class URLTests(WebTest):
-    fixtures = ['minimal_test_data']
-    csrf_checks = False
-
-    def test_all_urls(self):
-        """
-            This tests visits all URLs of evap and verifies they return a 200 for the specified user.
-        """
-        tests = [
-            # staff semester single_result
-            ("test_staff_semester_x_single_result_y_edit", "/staff/semester/1/course/11/edit", "evap"),
-            # staff questionnaires
-            ("test_staff_questionnaire", "/staff/questionnaire/", "evap"),
-            ("test_staff_questionnaire_create", "/staff/questionnaire/create", "evap"),
-            ("test_staff_questionnaire_x_edit", "/staff/questionnaire/3/edit", "evap"),
-            ("test_staff_questionnaire_x", "/staff/questionnaire/2", "evap"),
-            ("test_staff_questionnaire_x_copy", "/staff/questionnaire/2/copy", "evap"),
-            # staff user
-            ("test_staff_user_import", "/staff/user/import", "evap"),
-            ("test_staff_sample_xls", "/static/sample_user.xls", "evap"),
-            ("test_staff_user_x_edit", "/staff/user/4/edit", "evap"),
-            ("test_staff_user_merge", "/staff/user/merge", "evap"),
-            ("test_staff_user_x_merge_x", "/staff/user/4/merge/5", "evap"),
-            # rewards
-            ("test_staff_reward_points_redemption_events", "/rewards/reward_point_redemption_events/", "evap"),
-            ("test_staff_reward_points_redemption_event_export", "/rewards/reward_point_redemption_event/1/export", "evap"),
-            # course types
-            ("test_staff_course_type_merge", "/staff/course_types/merge", "evap"),
-        ]
-        for _, url, user in tests:
-            self.get_assert_200(url, user)
-
-    def test_permission_denied(self):
-        """
-            Tests whether all the 403s Evap can throw are correctly thrown.
-        """
-        self.get_assert_403("/contributor/course/7", "editor_of_course_1")
-        self.get_assert_403("/contributor/course/7/preview", "editor_of_course_1")
-        self.get_assert_403("/contributor/course/2/edit", "editor_of_course_1")
-        self.get_assert_403("/student/vote/5", "student")
-        self.get_assert_403("/results/semester/1/course/8", "student")
-        self.get_assert_403("/results/semester/1/course/7", "student")
-
-    def test_failing_forms(self):
-        """
-            Tests whether forms that fail because of missing required fields
-            when submitting them without entering any data actually do that.
-        """
-        forms = [
-            ("/staff/semester/create", "evap"),
-            ("/staff/semester/1/course/create", "evap"),
-            ("/staff/questionnaire/create", "evap"),
-            ("/staff/user/create", "evap"),
-            ("/staff/user/merge", "evap"),
-            ("/staff/course_types/merge", "evap"),
-        ]
-        for form in forms:
-            response = self.get_submit_assert_200(form[0], form[1])
-            self.assertIn("is required", response)
-
-        forms = [
-            ("/student/vote/5", "lazy.student"),
-            ("/staff/semester/1/course/1/email", "evap"),
-        ]
-        for form in forms:
-            response = self.get_submit_assert_200(form[0], form[1])
-            self.assertIn("alert-danger", response)
-
-    def test_failing_questionnaire_copy(self):
-        """
-            Tests whether copying and submitting a questionnaire form wihtout entering a new name fails.
-        """
-        response = self.get_submit_assert_200("/staff/questionnaire/2/copy", "evap")
-        self.assertIn("already exists", response)
-
-    """
-        The following tests test whether forms that succeed when
-        submitting them without entering any data actually do that.
-        They are in individual methods because most of them change the database.
-    """
-
-    def test_staff_semester_x_edit__nodata_success(self):
-        self.get_submit_assert_302("/staff/semester/1/edit", "evap")
-
-    def test_staff_semester_x_assign__nodata_success(self):
-        self.get_submit_assert_302("/staff/semester/1/assign", "evap")
-
-    def test_staff_semester_x_lottery__nodata_success(self):
-        self.get_submit_assert_200("/staff/semester/1/lottery", "evap")
-
-    def test_staff_semester_x_course_y_edit__nodata_success(self):
-        self.get_submit_assert_302("/staff/semester/1/course/1/edit", "evap", name="operation", value="save")
-
-    def test_staff_questionnaire_x_edit__nodata_success(self):
-        self.get_submit_assert_302("/staff/questionnaire/3/edit", "evap")
-
-    def test_staff_user_x_edit__nodata_success(self):
-        self.get_submit_assert_302("/staff/user/4/edit", "evap")
-
-    def test_staff_faq__nodata_success(self):
-        self.get_submit_assert_302("/staff/faq/", "evap")
-
-    def test_staff_faq_x__nodata_success(self):
-        self.get_submit_assert_302("/staff/faq/1", "evap")
-
-    def test_contributor_settings(self):
-        self.get_submit_assert_302("/contributor/settings", "responsible")
diff --git a/evap/results/tests/test_views.py b/evap/results/tests/test_views.py
--- a/evap/results/tests/test_views.py
+++ b/evap/results/tests/test_views.py
@@ -55,8 +55,12 @@ def setUpTestData(cls):
     def test_single_result_course(self):
         url = '/results/semester/%s/course/%s' % (self.semester.id, self.single_result_course.id)
         user = 'evap'
-        response = self.app.get(url, user=user)
-        self.assertEqual(response.status_code, 200, 'url "{}" failed with user "{}"'.format(self.url, user))
+        self.get_assert_200(url, user)
+
+    def test_wrong_state(self):
+        course = mommy.make(Course, state='reviewed', semester=self.semester)
+        url = '/results/semester/%s/course/%s' % (self.semester.id, course.id)
+        self.get_assert_403(url, 'student')
 
     def test_private_course(self):
         student = UserProfile.objects.get(username="student")
diff --git a/evap/rewards/tests/test_views.py b/evap/rewards/tests/test_views.py
--- a/evap/rewards/tests/test_views.py
+++ b/evap/rewards/tests/test_views.py
@@ -78,6 +78,17 @@ def test_redeem_points_for_expired_event(self):
         self.assertEqual(5, reward_points_of_user(self.student))
 
 
+class TestEventsView(ViewTest):
+    url = reverse('rewards:reward_point_redemption_events')
+    test_users = ['staff']
+
+    @classmethod
+    def setUpTestData(cls):
+        mommy.make(UserProfile, username='staff', groups=[Group.objects.get(name='Staff')])
+        mommy.make(RewardPointRedemptionEvent, pk=1, redeem_end_date=date.today() + timedelta(days=1))
+        mommy.make(RewardPointRedemptionEvent, pk=2, redeem_end_date=date.today() + timedelta(days=1))
+
+
 class TestEventCreateView(ViewTest):
     url = reverse('rewards:reward_point_redemption_event_create')
     test_users = ['staff']
@@ -124,6 +135,17 @@ def test_edit_redemption_event(self):
         self.assertEqual(RewardPointRedemptionEvent.objects.get(pk=1).name, 'new name')
 
 
+class TestExportView(ViewTest):
+    url = '/rewards/reward_point_redemption_event/1/export'
+    test_users = ['staff']
+
+    @classmethod
+    def setUpTestData(cls):
+        mommy.make(UserProfile, username='staff', groups=[Group.objects.get(name='Staff')])
+        event = mommy.make(RewardPointRedemptionEvent, pk=1, redeem_end_date=date.today() + timedelta(days=1))
+        mommy.make(RewardPointRedemption, value=1, event=event)
+
+
 class TestSemesterActivationView(ViewTest):
     url = '/rewards/reward_semester_activation/1/'
     csrf_checks = False
diff --git a/evap/staff/tests/test_usecases.py b/evap/staff/tests/test_usecases.py
deleted file mode 100644
--- a/evap/staff/tests/test_usecases.py
+++ /dev/null
@@ -1,173 +0,0 @@
-import os.path
-
-from django.conf import settings
-from django.contrib.auth.models import Group
-from django.urls import reverse
-
-from model_mommy import mommy
-
-from evap.evaluation.models import Semester, Questionnaire, Question, UserProfile, Course, \
-                                   CourseType, Contribution
-from evap.evaluation.tests.tools import WebTest
-
-
-class UsecaseTests(WebTest):
-
-    @classmethod
-    def setUpTestData(cls):
-        mommy.make(UserProfile, username="staff.user", groups=[Group.objects.get(name="Staff")])
-        mommy.make(CourseType, name_de="Vorlesung", name_en="Vorlesung")
-        mommy.make(CourseType, name_de="Seminar", name_en="Seminar")
-
-    def test_import(self):
-        page = self.app.get(reverse("staff:index"), user='staff.user')
-
-        # create a new semester
-        page = page.click("[Cc]reate [Nn]ew [Ss]emester")
-        semester_form = page.forms["semester-form"]
-        semester_form['name_de'] = "Testsemester"
-        semester_form['name_en'] = "test semester"
-        page = semester_form.submit().follow()
-
-        # retrieve new semester
-        semester = Semester.objects.get(name_de="Testsemester",
-                                        name_en="test semester")
-
-        self.assertEqual(semester.course_set.count(), 0, "New semester is not empty.")
-
-        # save original user count
-        original_user_count = UserProfile.objects.count()
-
-        # import excel file
-        page = page.click("[Ii]mport")
-        upload_form = page.forms["semester-import-form"]
-        upload_form['excel_file'] = (os.path.join(settings.BASE_DIR, "staff/fixtures/test_enrollment_data.xls"),)
-        page = upload_form.submit(name="operation", value="test")
-
-        upload_form = page.forms["semester-import-form"]
-        upload_form['vote_start_datetime'] = "2000-02-29"
-        upload_form['vote_end_date'] = "2012-02-29"
-        upload_form.submit(name="operation", value="import").follow()
-
-        self.assertEqual(UserProfile.objects.count(), original_user_count + 23)
-
-        courses = Course.objects.filter(semester=semester).all()
-        self.assertEqual(len(courses), 23)
-
-        for course in courses:
-            responsibles_count = Contribution.objects.filter(course=course, responsible=True).count()
-            self.assertEqual(responsibles_count, 1)
-
-        check_student = UserProfile.objects.get(username="diam.synephebos")
-        self.assertEqual(check_student.first_name, "Diam")
-        self.assertEqual(check_student.email, "diam.synephebos@institution.example.com")
-
-        check_contributor = UserProfile.objects.get(username="sanctus.aliquyam.ext")
-        self.assertEqual(check_contributor.first_name, "Sanctus")
-        self.assertEqual(check_contributor.last_name, "Aliquyam")
-        self.assertEqual(check_contributor.email, "567@external.example.com")
-
-    def test_login_key(self):
-        self.assertRedirects(self.app.get(reverse("results:index")), "/?next=/results/")
-
-        user = mommy.make(UserProfile)
-        user.generate_login_key()
-
-        url_with_key = reverse("results:index") + "?loginkey=%s" % user.login_key
-        self.app.get(url_with_key)
-
-    def test_create_questionnaire(self):
-        page = self.app.get(reverse("staff:index"), user="staff.user")
-
-        # create a new questionnaire
-        page = page.click("[Cc]reate [Nn]ew [Qq]uestionnaire")
-        questionnaire_form = page.forms["questionnaire-form"]
-        questionnaire_form['name_de'] = "Test Fragebogen"
-        questionnaire_form['name_en'] = "test questionnaire"
-        questionnaire_form['public_name_de'] = "Oeffentlicher Test Fragebogen"
-        questionnaire_form['public_name_en'] = "Public Test Questionnaire"
-        questionnaire_form['question_set-0-text_de'] = "Frage 1"
-        questionnaire_form['question_set-0-text_en'] = "Question 1"
-        questionnaire_form['question_set-0-type'] = "T"
-        questionnaire_form['index'] = 0
-        questionnaire_form.submit().follow()
-
-        # retrieve new questionnaire
-        questionnaire = Questionnaire.objects.get(name_de="Test Fragebogen", name_en="test questionnaire")
-        self.assertEqual(questionnaire.question_set.count(), 1, "New questionnaire is empty.")
-
-    def test_create_empty_questionnaire(self):
-        page = self.app.get(reverse("staff:index"), user="staff.user")
-
-        # create a new questionnaire
-        page = page.click("[Cc]reate [Nn]ew [Qq]uestionnaire")
-        questionnaire_form = page.forms["questionnaire-form"]
-        questionnaire_form['name_de'] = "Test Fragebogen"
-        questionnaire_form['name_en'] = "test questionnaire"
-        questionnaire_form['public_name_de'] = "Oeffentlicher Test Fragebogen"
-        questionnaire_form['public_name_en'] = "Public Test Questionnaire"
-        questionnaire_form['index'] = 0
-        page = questionnaire_form.submit()
-
-        self.assertIn("You must have at least one of these", page)
-
-        # retrieve new questionnaire
-        with self.assertRaises(Questionnaire.DoesNotExist):
-            Questionnaire.objects.get(name_de="Test Fragebogen", name_en="test questionnaire")
-
-    def test_copy_questionnaire(self):
-        questionnaire = mommy.make(Questionnaire, name_en="Seminar")
-        mommy.make(Question, questionnaire=questionnaire)
-        page = self.app.get(reverse("staff:index"), user="staff.user")
-
-        # create a new questionnaire
-        page = page.click("All questionnaires")
-        page = page.click("Copy", index=1)
-        questionnaire_form = page.forms["questionnaire-form"]
-        questionnaire_form['name_de'] = "Test Fragebogen (kopiert)"
-        questionnaire_form['name_en'] = "test questionnaire (copied)"
-        questionnaire_form['public_name_de'] = "Oeffentlicher Test Fragebogen (kopiert)"
-        questionnaire_form['public_name_en'] = "Public Test Questionnaire (copied)"
-        page = questionnaire_form.submit().follow()
-
-        # retrieve new questionnaire
-        questionnaire = Questionnaire.objects.get(name_de="Test Fragebogen (kopiert)", name_en="test questionnaire (copied)")
-        self.assertEqual(questionnaire.question_set.count(), 1, "New questionnaire is empty.")
-
-    def test_assign_questionnaires(self):
-        semester = mommy.make(Semester, name_en="Semester 1")
-        mommy.make(Course, semester=semester, type=CourseType.objects.get(name_de="Seminar"), contributions=[
-            mommy.make(Contribution, contributor=mommy.make(UserProfile),
-                       responsible=True, can_edit=True, comment_visibility=Contribution.ALL_COMMENTS)])
-        mommy.make(Course, semester=semester, type=CourseType.objects.get(name_de="Vorlesung"), contributions=[
-            mommy.make(Contribution, contributor=mommy.make(UserProfile),
-                       responsible=True, can_edit=True, comment_visibility=Contribution.ALL_COMMENTS)])
-        questionnaire = mommy.make(Questionnaire)
-        page = self.app.get(reverse("staff:index"), user="staff.user")
-
-        # assign questionnaire to courses
-        page = page.click("Semester 1", index=0)
-        page = page.click("Assign Questionnaires")
-        assign_form = page.forms["questionnaire-assign-form"]
-        assign_form['Seminar'] = [questionnaire.pk]
-        assign_form['Vorlesung'] = [questionnaire.pk]
-        page = assign_form.submit().follow()
-
-        for course in semester.course_set.all():
-            self.assertEqual(course.general_contribution.questionnaires.count(), 1)
-            self.assertEqual(course.general_contribution.questionnaires.get(), questionnaire)
-
-    def test_remove_responsibility(self):
-        user = mommy.make(UserProfile)
-        contribution = mommy.make(Contribution, contributor=user, responsible=True, can_edit=True, comment_visibility=Contribution.ALL_COMMENTS)
-
-        page = self.app.get(reverse("staff:index"), user="staff.user")
-        page = page.click(contribution.course.semester.name_en, index=0)
-        page = page.click(contribution.course.name_en)
-
-        # remove responsibility
-        form = page.forms["course-form"]
-        form['contributions-0-responsibility'] = "CONTRIBUTOR"
-        page = form.submit()
-
-        self.assertIn("No responsible contributors found", page)
diff --git a/evap/staff/tests/test_views.py b/evap/staff/tests/test_views.py
--- a/evap/staff/tests/test_views.py
+++ b/evap/staff/tests/test_views.py
@@ -10,7 +10,8 @@
 import xlrd
 
 from evap.evaluation.models import Semester, UserProfile, Course, CourseType, TextAnswer, Contribution, \
-                                   Questionnaire, Question, EmailTemplate, Degree, FaqSection, FaqQuestion
+                                   Questionnaire, Question, EmailTemplate, Degree, FaqSection, FaqQuestion, \
+                                   RatingAnswerCounter
 from evap.evaluation.tests.tools import FuzzyInt, WebTest, ViewTest
 from evap.staff.tools import generate_import_filename
 
@@ -121,6 +122,44 @@ def test_user_is_created(self):
         self.assertEqual(UserProfile.objects.order_by("pk").last().username, "mflkd862xmnbo5")
 
 
+class TestUserEditView(ViewTest):
+    url = "/staff/user/3/edit"
+    test_users = ['staff']
+
+    @classmethod
+    def setUpTestData(cls):
+        mommy.make(UserProfile, username='staff', groups=[Group.objects.get(name='Staff')])
+        mommy.make(UserProfile, pk=3)
+
+    def test_questionnaire_edit(self):
+        page = self.get_assert_200(self.url, "staff")
+        form = page.forms["user-form"]
+        form["username"] = "lfo9e7bmxp1xi"
+        form.submit()
+        self.assertTrue(UserProfile.objects.filter(username='lfo9e7bmxp1xi').exists())
+
+
+class TestUserMergeSelectionView(ViewTest):
+    url = "/staff/user/merge"
+    test_users = ['staff']
+
+    @classmethod
+    def setUpTestData(cls):
+        mommy.make(UserProfile, username='staff', groups=[Group.objects.get(name='Staff')])
+        mommy.make(UserProfile)
+
+
+class TestUserMergeView(ViewTest):
+    url = "/staff/user/3/merge/4"
+    test_users = ['staff']
+
+    @classmethod
+    def setUpTestData(cls):
+        mommy.make(UserProfile, username='staff', groups=[Group.objects.get(name='Staff')])
+        mommy.make(UserProfile, pk=3)
+        mommy.make(UserProfile, pk=4)
+
+
 class TestUserBulkDeleteView(ViewTest):
     url = '/staff/user/bulk_delete'
     test_users = ['staff']
@@ -383,7 +422,27 @@ class TestSemesterAssignView(ViewTest):
     @classmethod
     def setUpTestData(cls):
         mommy.make(UserProfile, username='staff', groups=[Group.objects.get(name='Staff')])
-        mommy.make(Semester, pk=1)
+        cls.semester = mommy.make(Semester, pk=1)
+        lecture_type = mommy.make(CourseType, name_de="Vorlesung", name_en="Lecture")
+        seminar_type = mommy.make(CourseType, name_de="Seminar", name_en="Seminar")
+        cls.questionnaire = mommy.make(Questionnaire)
+        course1 = mommy.make(Course, semester=cls.semester, type=seminar_type)
+        mommy.make(Contribution, contributor=mommy.make(UserProfile), course=course1,
+                   responsible=True, can_edit=True, comment_visibility=Contribution.ALL_COMMENTS)
+        course2 = mommy.make(Course, semester=cls.semester, type=lecture_type)
+        mommy.make(Contribution, contributor=mommy.make(UserProfile), course=course2,
+                   responsible=True, can_edit=True, comment_visibility=Contribution.ALL_COMMENTS)
+
+    def test_assign_questionnaires(self):
+        page = self.app.get(self.url, user="staff")
+        assign_form = page.forms["questionnaire-assign-form"]
+        assign_form['Seminar'] = [self.questionnaire.pk]
+        assign_form['Lecture'] = [self.questionnaire.pk]
+        page = assign_form.submit().follow()
+
+        for course in self.semester.course_set.all():
+            self.assertEqual(course.general_contribution.questionnaires.count(), 1)
+            self.assertEqual(course.general_contribution.questionnaires.get(), self.questionnaire)
 
 
 class TestSemesterTodoView(ViewTest):
@@ -438,6 +497,22 @@ def test_import_valid_file(self):
 
         self.assertEqual(UserProfile.objects.count(), original_user_count + 23)
 
+        courses = Course.objects.all()
+        self.assertEqual(len(courses), 23)
+
+        for course in courses:
+            responsibles_count = Contribution.objects.filter(course=course, responsible=True).count()
+            self.assertEqual(responsibles_count, 1)
+
+        check_student = UserProfile.objects.get(username="diam.synephebos")
+        self.assertEqual(check_student.first_name, "Diam")
+        self.assertEqual(check_student.email, "diam.synephebos@institution.example.com")
+
+        check_contributor = UserProfile.objects.get(username="sanctus.aliquyam.ext")
+        self.assertEqual(check_contributor.first_name, "Sanctus")
+        self.assertEqual(check_contributor.last_name, "Aliquyam")
+        self.assertEqual(check_contributor.email, "567@external.example.com")
+
     def test_error_handling(self):
         """
         Tests whether errors given from the importer are displayed
@@ -625,61 +700,70 @@ def test_view_downloads_csv_file(self):
 
 
 class TestCourseOperationView(ViewTest):
-    url = '/staff/semester/111/courseoperation'
-    fixtures = ['minimal_test_data']
+    url = '/staff/semester/1/courseoperation'
 
     @classmethod
     def setUpTestData(cls):
         mommy.make(UserProfile, username='staff', groups=[Group.objects.get(name='Staff')])
-        cls.semester = mommy.make(Semester, pk=111)
+        cls.semester = mommy.make(Semester, pk=1)
 
-    def helper_semester_state_views(self, course_ids, old_state, new_state, operation):
-        page = self.app.get("/staff/semester/1", user="evap")
+    def helper_semester_state_views(self, course, old_state, new_state, operation):
+        page = self.app.get("/staff/semester/1", user="staff")
         form = page.forms["form_" + old_state]
-        for course_id in course_ids:
-            self.assertIn(Course.objects.get(pk=course_id).state, old_state)
-        form['course'] = course_ids
+        self.assertIn(course.state, old_state)
+        form['course'] = course.pk
         response = form.submit('operation', value=operation)
 
         form = response.forms["course-operation-form"]
         response = form.submit()
         self.assertIn("Successfully", str(response))
-        for course_id in course_ids:
-            self.assertEqual(Course.objects.get(pk=course_id).state, new_state)
+        self.assertEqual(Course.objects.get(pk=course.pk).state, new_state)
 
     """
         The following tests make sure the course state transitions are triggerable via the UI.
     """
     def test_semester_publish(self):
-        self.helper_semester_state_views([7], "reviewed", "published", "publish")
+        course = mommy.make(Course, semester=self.semester, state='reviewed')
+        self.helper_semester_state_views(course, "reviewed", "published", "publish")
 
     def test_semester_reset_1(self):
-        self.helper_semester_state_views([2], "prepared", "new", "revertToNew")
+        course = mommy.make(Course, semester=self.semester, state='prepared')
+        self.helper_semester_state_views(course, "prepared", "new", "revertToNew")
 
     def test_semester_reset_2(self):
-        self.helper_semester_state_views([4], "approved", "new", "revertToNew")
+        course = mommy.make(Course, semester=self.semester, state='approved')
+        self.helper_semester_state_views(course, "approved", "new", "revertToNew")
 
     def test_semester_approve_1(self):
-        self.helper_semester_state_views([1], "new", "approved", "approve")
+        course = course = mommy.make(Course, semester=self.semester, state='new')
+        course.general_contribution.questionnaires = [mommy.make(Questionnaire)]
+        self.helper_semester_state_views(course, "new", "approved", "approve")
 
     def test_semester_approve_2(self):
-        self.helper_semester_state_views([2], "prepared", "approved", "approve")
+        course = mommy.make(Course, semester=self.semester, state='prepared')
+        course.general_contribution.questionnaires = [mommy.make(Questionnaire)]
+        self.helper_semester_state_views(course, "prepared", "approved", "approve")
 
     def test_semester_approve_3(self):
-        self.helper_semester_state_views([3], "editor_approved", "approved", "approve")
+        course = mommy.make(Course, semester=self.semester, state='editor_approved')
+        course.general_contribution.questionnaires = [mommy.make(Questionnaire)]
+        self.helper_semester_state_views(course, "editor_approved", "approved", "approve")
 
     def test_semester_contributor_ready_1(self):
-        self.helper_semester_state_views([1, 10], "new", "prepared", "prepare")
+        course = mommy.make(Course, semester=self.semester, state='new')
+        self.helper_semester_state_views(course, "new", "prepared", "prepare")
 
     def test_semester_contributor_ready_2(self):
-        self.helper_semester_state_views([3], "editor_approved", "prepared", "reenableEditorReview")
+        course = mommy.make(Course, semester=self.semester, state='editor_approved')
+        self.helper_semester_state_views(course, "editor_approved", "prepared", "reenableEditorReview")
 
     def test_semester_unpublish(self):
-        self.helper_semester_state_views([8], "published", "reviewed", "unpublish")
+        course = mommy.make(Course, semester=self.semester, state='published')
+        self.helper_semester_state_views(course, "published", "reviewed", "unpublish")
 
     def test_operation_start_evaluation(self):
         urloptions = '?course=1&operation=startEvaluation'
-        mommy.make(Course, pk=1, state='approved', semester=self.semester)
+        course = mommy.make(Course, state='approved', semester=self.semester)
 
         response = self.app.get(self.url + urloptions, user='staff')
         self.assertEqual(response.status_code, 200, 'url "{}" failed with user "staff"'.format(self.url))
@@ -687,12 +771,12 @@ def test_operation_start_evaluation(self):
         form = response.forms['course-operation-form']
         form.submit()
 
-        course = Course.objects.get(pk=1)
+        course = Course.objects.get(pk=course.pk)
         self.assertEqual(course.state, 'in_evaluation')
 
     def test_operation_prepare(self):
         urloptions = '?course=1&operation=prepare'
-        mommy.make(Course, pk=1, state='new', semester=self.semester)
+        course = mommy.make(Course, state='new', semester=self.semester)
 
         response = self.app.get(self.url + urloptions, user='staff')
         self.assertEqual(response.status_code, 200, 'url "{}" failed with user "staff"'.format(self.url))
@@ -700,7 +784,7 @@ def test_operation_prepare(self):
         form = response.forms['course-operation-form']
         form.submit()
 
-        course = Course.objects.get(pk=1)
+        course = Course.objects.get(pk=course.pk)
         self.assertEqual(course.state, 'prepared')
 
 
@@ -793,14 +877,57 @@ class TestCourseEditView(ViewTest):
     def setUpTestData(cls):
         mommy.make(UserProfile, username='staff', groups=[Group.objects.get(name='Staff')])
         semester = mommy.make(Semester, pk=1)
-        course = mommy.make(Course, semester=semester, pk=1)
+        degree = mommy.make(Degree)
+        cls.course = mommy.make(Course, semester=semester, pk=1, degrees=[degree])
+        mommy.make(Questionnaire, question_set=[mommy.make(Question)])
+        cls.course.general_contribution.questionnaires = [mommy.make(Questionnaire)]
 
         # This is necessary so that the call to is_single_result does not fail.
+        responsible = mommy.make(UserProfile)
+        cls.contribution = mommy.make(Contribution, course=cls.course, contributor=responsible, responsible=True, can_edit=True, comment_visibility=Contribution.ALL_COMMENTS)
+
+    def test_edit_course(self):
         user = mommy.make(UserProfile)
-        mommy.make(Contribution, course=course, contributor=user, responsible=True, can_edit=True, comment_visibility=Contribution.ALL_COMMENTS)
+        page = self.app.get(self.url, user="staff")
+
+        # remove responsibility
+        form = page.forms["course-form"]
+        form['contributions-0-contributor'] = user.pk
+        form['contributions-0-responsibility'] = "RESPONSIBLE"
+        page = form.submit("operation", value="save")
+        self.assertEqual(list(self.course.responsible_contributors), [user])
 
-    def test_single_result(self):
-        pass  # TODO: Should be done.
+    def test_remove_responsibility(self):
+        page = self.app.get(self.url, user="staff")
+
+        # remove responsibility
+        form = page.forms["course-form"]
+        form['contributions-0-responsibility'] = "CONTRIBUTOR"
+        page = form.submit("operation", value="save")
+
+        self.assertIn("No responsible contributors found", page)
+
+
+class TestSingleResultEditView(ViewTest):
+    url = '/staff/semester/1/course/1/edit'
+    test_users = ['staff']
+
+    @classmethod
+    def setUpTestData(cls):
+        mommy.make(UserProfile, username='staff', groups=[Group.objects.get(name='Staff')])
+        semester = mommy.make(Semester, pk=1)
+
+        course = mommy.make(Course, semester=semester, pk=1)
+        responsible = mommy.make(UserProfile)
+        contribution = mommy.make(Contribution, course=course, contributor=responsible, responsible=True, can_edit=True,
+                                  comment_visibility=Contribution.ALL_COMMENTS, questionnaires=[Questionnaire.single_result_questionnaire()])
+
+        question = Questionnaire.single_result_questionnaire().question_set.get()
+        mommy.make(RatingAnswerCounter, question=question, contribution=contribution, answer=1, count=5)
+        mommy.make(RatingAnswerCounter, question=question, contribution=contribution, answer=2, count=15)
+        mommy.make(RatingAnswerCounter, question=question, contribution=contribution, answer=3, count=40)
+        mommy.make(RatingAnswerCounter, question=question, contribution=contribution, answer=4, count=60)
+        mommy.make(RatingAnswerCounter, question=question, contribution=contribution, answer=5, count=30)
 
 
 class TestCoursePreviewView(ViewTest):
@@ -1102,7 +1229,6 @@ def test_changes_old_title(self):
         self.assertTrue(Questionnaire.objects.filter(name_de=new_name_de, name_en=new_name_en).exists())
 
     def test_no_second_update(self):
-
         # First save.
         page = self.app.get(url=self.url, user='staff')
         form = page.forms['questionnaire-form']
@@ -1117,6 +1243,111 @@ def test_no_second_update(self):
         self.assertEqual(page.location, '/staff/questionnaire/')
 
 
+class TestQuestionnaireCreateView(ViewTest):
+    url = "/staff/questionnaire/create"
+    test_users = ['staff']
+
+    @classmethod
+    def setUpTestData(cls):
+        mommy.make(UserProfile, username='staff', groups=[Group.objects.get(name='Staff')])
+
+    def test_create_questionnaire(self):
+        page = self.app.get(self.url, user="staff")
+
+        questionnaire_form = page.forms["questionnaire-form"]
+        questionnaire_form['name_de'] = "Test Fragebogen"
+        questionnaire_form['name_en'] = "test questionnaire"
+        questionnaire_form['public_name_de'] = "Oeffentlicher Test Fragebogen"
+        questionnaire_form['public_name_en'] = "Public Test Questionnaire"
+        questionnaire_form['question_set-0-text_de'] = "Frage 1"
+        questionnaire_form['question_set-0-text_en'] = "Question 1"
+        questionnaire_form['question_set-0-type'] = "T"
+        questionnaire_form['index'] = 0
+        questionnaire_form.submit().follow()
+
+        # retrieve new questionnaire
+        questionnaire = Questionnaire.objects.get(name_de="Test Fragebogen", name_en="test questionnaire")
+        self.assertEqual(questionnaire.question_set.count(), 1)
+
+    def test_create_empty_questionnaire(self):
+        page = self.app.get(self.url, user="staff")
+
+        questionnaire_form = page.forms["questionnaire-form"]
+        questionnaire_form['name_de'] = "Test Fragebogen"
+        questionnaire_form['name_en'] = "test questionnaire"
+        questionnaire_form['public_name_de'] = "Oeffentlicher Test Fragebogen"
+        questionnaire_form['public_name_en'] = "Public Test Questionnaire"
+        questionnaire_form['index'] = 0
+        page = questionnaire_form.submit()
+
+        self.assertIn("You must have at least one of these", page)
+
+        self.assertFalse(Questionnaire.objects.filter(name_de="Test Fragebogen", name_en="test questionnaire").exists())
+
+
+class TestQuestionnaireIndexView(ViewTest):
+    url = "/staff/questionnaire/"
+    test_users = ['staff']
+
+    @classmethod
+    def setUpTestData(cls):
+        mommy.make(UserProfile, username='staff', groups=[Group.objects.get(name='Staff')])
+        mommy.make(Questionnaire, is_for_contributors=True)
+        mommy.make(Questionnaire, is_for_contributors=False)
+
+
+class TestQuestionnaireEditView(ViewTest):
+    url = '/staff/questionnaire/2/edit'
+    test_users = ['staff']
+
+    @classmethod
+    def setUpTestData(cls):
+        questionnaire = mommy.make(Questionnaire, id=2)
+        mommy.make(Question, questionnaire=questionnaire)
+        mommy.make(UserProfile, username="staff", groups=[Group.objects.get(name="Staff")])
+
+
+class TestQuestionnaireViewView(ViewTest):
+    url = '/staff/questionnaire/2'
+    test_users = ['staff']
+
+    @classmethod
+    def setUpTestData(cls):
+        questionnaire = mommy.make(Questionnaire, id=2)
+        mommy.make(Question, questionnaire=questionnaire, type='T')
+        mommy.make(Question, questionnaire=questionnaire, type='G')
+        mommy.make(Question, questionnaire=questionnaire, type='L')
+        mommy.make(UserProfile, username="staff", groups=[Group.objects.get(name="Staff")])
+
+
+class TestQuestionnaireCopyView(ViewTest):
+    url = '/staff/questionnaire/2/copy'
+    test_users = ['staff']
+
+    @classmethod
+    def setUpTestData(cls):
+        questionnaire = mommy.make(Questionnaire, id=2)
+        mommy.make(Question, questionnaire=questionnaire)
+        mommy.make(UserProfile, username="staff", groups=[Group.objects.get(name="Staff")])
+
+    def test_not_changing_name_fails(self):
+        response = self.get_submit_assert_200(self.url, "staff")
+        self.assertIn("already exists", response)
+
+    def test_copy_questionnaire(self):
+        page = self.app.get(self.url, user="staff")
+
+        questionnaire_form = page.forms["questionnaire-form"]
+        questionnaire_form['name_de'] = "Test Fragebogen (kopiert)"
+        questionnaire_form['name_en'] = "test questionnaire (copied)"
+        questionnaire_form['public_name_de'] = "Oeffentlicher Test Fragebogen (kopiert)"
+        questionnaire_form['public_name_en'] = "Public Test Questionnaire (copied)"
+        page = questionnaire_form.submit().follow()
+
+        questionnaire = Questionnaire.objects.get(name_de="Test Fragebogen (kopiert)", name_en="test questionnaire (copied)")
+        self.assertEqual(questionnaire.question_set.count(), 1)
+
+
 class TestQuestionnaireDeletionView(WebTest):
     url = "/staff/questionnaire/delete"
     csrf_checks = False
@@ -1173,6 +1404,25 @@ def test_course_type_form(self):
         self.assertTrue(CourseType.objects.filter(name_de="Test", name_en="Test").exists())
 
 
+class TestCourseTypeMergeSelectionView(ViewTest):
+    url = "/staff/course_types/merge"
+    test_users = ['staff']
+
+    @classmethod
+    def setUpTestData(cls):
+        mommy.make(UserProfile, username='staff', groups=[Group.objects.get(name='Staff')])
+        cls.main_type = mommy.make(CourseType, pk=1, name_en="A course type")
+        cls.other_type = mommy.make(CourseType, pk=2, name_en="Obsolete course type")
+
+    def test_same_course_fails(self):
+        page = self.get_assert_200(self.url, user="staff")
+        form = page.forms["course-type-merge-selection-form"]
+        form["main_type"] = 1
+        form["other_type"] = 1
+        response = form.submit()
+        self.assertIn("You must select two different course types", str(response))
+
+
 class TestCourseTypeMergeView(ViewTest):
     url = "/staff/course_types/1/merge/2"
     test_users = ['staff']
@@ -1223,6 +1473,10 @@ def test_review_actions(self):
 
 class ArchivingTests(WebTest):
 
+    @classmethod
+    def setUpTestData(cls):
+        mommy.make(UserProfile, username="staff", groups=[Group.objects.get(name="Staff")])
+
     def test_raise_403(self):
         """
             Tests whether inaccessible views on archived semesters/courses correctly raise a 403.
@@ -1231,10 +1485,10 @@ def test_raise_403(self):
 
         semester_url = "/staff/semester/{}/".format(semester.pk)
 
-        self.get_assert_403(semester_url + "import", "evap")
-        self.get_assert_403(semester_url + "assign", "evap")
-        self.get_assert_403(semester_url + "course/create", "evap")
-        self.get_assert_403(semester_url + "courseoperation", "evap")
+        self.get_assert_403(semester_url + "import", "staff")
+        self.get_assert_403(semester_url + "assign", "staff")
+        self.get_assert_403(semester_url + "course/create", "staff")
+        self.get_assert_403(semester_url + "courseoperation", "staff")
 
 
 class TestTemplateEditView(ViewTest):