class CompetitionPhaseMigrationValidation(TestCase):
    def setUp(self):
        self.user = UserFactory(username='******')
        self.competition = CompetitionFactory(created_by=self.user, id=1)
        self.phase = PhaseFactory(competition=self.competition)
        self.leaderboard = LeaderboardFactory(competition=self.competition)

    def serialize_and_validate_data(self):
        serializer = CompetitionSerializer(self.competition)
        data = serializer.data

        with pytest.raises(ValidationError) as exception:
            serializer = CompetitionSerializer(data=data)
            serializer.is_valid(raise_exception=True)

        return exception

    def test_phase_is_valid(self):
        self.phase.auto_migrate_to_this_phase = False
        self.phase.save()
        exception = self.serialize_and_validate_data()

        assert ("'phase:'" not in str(exception.value))

    def test_phase_serializer_auto_migrate_on_first_phase(self):
        self.phase.auto_migrate_to_this_phase = True
        self.phase.save()
        exception = self.serialize_and_validate_data()

        errors = [
            "You cannot auto migrate in a competition with one phase",
            "You cannot auto migrate to the first phase of a competition"
        ]

        assert any(error in str(exception.value) for error in errors)

    def test_phase_serializer_no_phases(self):
        self.phase.competition = None
        self.phase.save()
        exception = self.serialize_and_validate_data()

        assert ("Competitions must have at least one phase"
                in str(exception.value))
Example #2
0
class SubmissionAPITests(APITestCase):
    def setUp(self):
        self.superuser = UserFactory(is_superuser=True, is_staff=True)

        # Competition and creator
        self.creator = UserFactory(username='******', password='******')
        self.collaborator = UserFactory(username='******', password='******')
        self.comp = CompetitionFactory(created_by=self.creator,
                                       collaborators=[self.collaborator])
        self.phase = PhaseFactory(competition=self.comp)

        # Extra dummy user to test permissions, they shouldn't have access to many things
        self.other_user = UserFactory(username='******', password='******')

        # Make a participant and submission into competition
        self.participant = UserFactory(username='******',
                                       password='******')
        CompetitionParticipantFactory(user=self.participant,
                                      competition=self.comp)
        self.existing_submission = SubmissionFactory(
            phase=self.phase,
            owner=self.participant,
            status=Submission.SUBMITTED,
            secret='7df3600c-1234-5678-bbc8-bbe91f42d875')

    def test_can_make_submission_checks_if_you_are_participant(self):
        # You should get a message back if you aren't registered in this competition
        self.client.login(username="******", password="******")

        resp = self.client.get(
            reverse("can_make_submission", args=(self.phase.pk, )))
        assert resp.status_code == 200
        assert not resp.data["can"]
        assert resp.data[
            "reason"] == "User not approved to participate in this competition"
        self.client.logout()

        # If you are in the competition (the creator), should be good-to-go
        self.client.login(username="******", password="******")
        resp = self.client.get(
            reverse("can_make_submission", args=(self.phase.pk, )))
        assert resp.status_code == 200
        assert resp.data["can"]

    def test_making_a_submission_checks_if_you_are_a_participant(self):
        # You should get a message back if you aren't registered in this competition
        self.client.login(username="******", password="******")

        resp = self.client.post(reverse("submission-list"),
                                {"phase": self.phase.pk})
        assert resp.status_code == 403
        assert "You do not have access to this competition to make a submission" in resp.data[
            "detail"]

    def test_trying_to_change_submission_without_secret_raises_permission_denied(
            self):
        url = reverse('submission-detail',
                      args=(self.existing_submission.pk, ))
        # As anonymous user
        resp = self.client.patch(url, {"status": Submission.FINISHED})
        assert resp.status_code == 400
        assert "Secret: (None) not a valid UUID" in str(resp.content)
        assert Submission.objects.filter(pk=self.existing_submission.pk,
                                         status=Submission.SUBMITTED)

        # As superuser (bad secret)
        self.client.force_login(self.creator)
        resp = self.client.patch(
            url, {
                "status": Submission.FINISHED,
                "secret": '7df3600c-aa6d-41c5-bbc8-bbe91f42d875'
            })
        assert resp.status_code == 403
        assert resp.data["detail"] == "Submission secrets do not match"
        assert Submission.objects.filter(pk=self.existing_submission.pk,
                                         status=Submission.SUBMITTED)

        # As anonymous user with secret
        self.client.logout()
        resp = self.client.patch(
            url, {
                "status": Submission.FINISHED,
                "secret": self.existing_submission.secret
            })
        assert resp.status_code == 200
        assert Submission.objects.filter(pk=self.existing_submission.pk,
                                         status=Submission.FINISHED)

    def test_cannot_delete_submission_you_didnt_create(self):
        url = reverse('submission-detail',
                      args=(self.existing_submission.pk, ))

        # As anonymous user
        resp = self.client.delete(url)
        assert resp.status_code == 403
        assert resp.data[
            "detail"] == "Cannot interact with submission you did not make"

        # As regular user
        self.client.force_login(self.other_user)
        resp = self.client.delete(url)
        assert resp.status_code == 403
        assert resp.data[
            "detail"] == "Cannot interact with submission you did not make"

        # As user who made submission
        self.client.force_login(self.participant)
        resp = self.client.delete(url)
        assert resp.status_code == 204
        assert not Submission.objects.filter(
            pk=self.existing_submission.pk).exists()

        # As superuser (re-making submission since it has been destroyed)
        self.existing_submission = SubmissionFactory(
            phase=self.phase,
            owner=self.participant,
            status=Submission.SUBMITTED,
            secret='7df3600c-1234-5678-90c8-bbe91f42d875')
        url = reverse('submission-detail',
                      args=(self.existing_submission.pk, ))

        self.client.force_login(self.superuser)
        resp = self.client.delete(url)
        assert resp.status_code == 204
        assert not Submission.objects.filter(
            pk=self.existing_submission.pk).exists()

    def test_cannot_get_details_of_submission_unless_creator_collab_or_superuser(
            self):
        url = reverse('submission-get-details',
                      args=(self.existing_submission.pk, ))

        # Non logged in user can't even see this
        resp = self.client.get(url)
        assert resp.status_code == 404

        # Regular user can't see this
        self.client.force_login(self.other_user)
        resp = self.client.get(url)
        assert resp.status_code == 404

        # Actual user can see download details
        self.client.force_login(self.participant)
        resp = self.client.get(url)
        assert resp.status_code == 200

        # Competition creator can see download details
        self.client.force_login(self.creator)
        resp = self.client.get(url)
        assert resp.status_code == 200

        # Collaborator can see download details
        self.client.force_login(self.collaborator)
        resp = self.client.get(url)
        assert resp.status_code == 200

        # Superuser can see download details
        self.client.force_login(self.superuser)
        resp = self.client.get(url)
        assert resp.status_code == 200

    def test_hidden_details_actually_stops_submission_creator_from_seeing_output(
            self):
        self.phase.hide_output = True
        self.phase.save()
        url = reverse('submission-get-details',
                      args=(self.existing_submission.pk, ))

        # Non logged in user can't even see this
        resp = self.client.get(url)
        assert resp.status_code == 404

        # Regular user can't see this
        self.client.force_login(self.other_user)
        resp = self.client.get(url)
        assert resp.status_code == 404

        # Actual user cannot see their submission details
        self.client.force_login(self.participant)
        resp = self.client.get(url)
        assert resp.status_code == 403
        assert resp.data[
            "detail"] == "Cannot access submission details while phase marked to hide output."

        # Competition creator can see download details
        self.client.force_login(self.creator)
        resp = self.client.get(url)
        assert resp.status_code == 200

        # Collaborator can see download details
        self.client.force_login(self.collaborator)
        resp = self.client.get(url)
        assert resp.status_code == 200

        # Superuser can see download details
        self.client.force_login(self.superuser)
        resp = self.client.get(url)
        assert resp.status_code == 200
class PhaseToPhaseMigrationTests(TestCase):
    def setUp(self):
        self.owner = UserFactory(username='******', super_user=True)
        self.normal_user = UserFactory(username='******')
        self.competition = CompetitionFactory(created_by=self.owner,
                                              title="Competition One")
        self.competition_participant = CompetitionParticipantFactory(
            user=self.normal_user, competition=self.competition)
        self.phase1 = PhaseFactory(competition=self.competition,
                                   auto_migrate_to_this_phase=False,
                                   start=twenty_five_minutes_ago,
                                   end=twenty_minutes_ago,
                                   index=0,
                                   name='Phase1',
                                   status=Phase.CURRENT)

        self.phase2 = PhaseFactory(competition=self.competition,
                                   auto_migrate_to_this_phase=True,
                                   start=five_minutes_ago,
                                   end=twenty_minutes_from_now,
                                   index=1,
                                   name='Phase2',
                                   status=Phase.NEXT)

        self.phase3 = PhaseFactory(competition=self.competition,
                                   auto_migrate_to_this_phase=False,
                                   start=twenty_minutes_from_now,
                                   index=2,
                                   name='Phase3',
                                   status=Phase.FINAL)

        for _ in range(4):
            self.make_submission()

    def make_submission(self, **kwargs):
        kwargs.setdefault('owner', self.owner)
        kwargs.setdefault('participant', self.competition_participant)
        kwargs.setdefault('phase', self.phase1)
        kwargs.setdefault('status', Submission.FINISHED)
        sub = SubmissionFactory(**kwargs)
        return sub

    def mock_migration(self):
        with mock.patch(
                'competitions.models.Submission.start') as submission_start:
            do_phase_migrations()
            return submission_start

    def test_migrate_submissions(self):
        assert not self.phase2.submissions.exists()
        mock_start = self.mock_migration()
        assert mock_start.call_count == self.phase1.submissions.count()
        assert self.phase1.submissions.count(
        ) == self.phase2.submissions.count()

    def test_currently_migrating_competitions_dont_migrate(self):
        self.mock_migration()
        assert Competition.objects.get(id=self.competition.id).is_migrating
        mock_start = self.mock_migration()
        assert not mock_start.called

    def test_competitions_with_scoring_submissions_dont_migrate(self):
        self.make_submission(status=Submission.SCORING,
                             participant=self.competition_participant)
        self.mock_migration()
        assert self.phase1.submissions.count(
        ) != self.phase2.submissions.count()

    def test_submission_ran_after_migration_complete(self):
        self.mock_migration()
        assert not self.phase2.submissions.filter(status='None').exists()

    def test_has_been_migrated_competitions_arent_migrated(self):
        self.phase2.has_been_migrated = True
        self.phase2.save()
        assert not self.phase2.submissions.exists()
        self.mock_migration()
        assert self.phase1.submissions.count(
        ) != self.phase2.submissions.count()

    def test_prevent_migration_to_auto_migrate_to_the_phase_is_false(self):
        assert not self.phase2.submissions.exists()
        assert not self.phase3.submissions.exists()
        self.mock_migration()
        assert self.phase1.submissions.count(
        ) == self.phase2.submissions.count()
        assert self.phase1.submissions.count(
        ) != self.phase3.submissions.count()
        assert self.phase2.submissions.count(
        ) != self.phase3.submissions.count()

    def test_all_submissions_migrated_before_changing_phase_status(self):
        self.mock_migration()
        assert Phase.objects.get(id=self.phase1.id).status == Phase.PREVIOUS
        assert Competition.objects.get(id=self.competition.id).is_migrating
        phase2 = Phase.objects.get(id=self.phase2.id)
        assert phase2.has_been_migrated
        assert phase2.status == Phase.CURRENT

        self.mock_migration()
        assert Competition.objects.get(id=self.competition.id).is_migrating

        self.phase2.submissions.update(status='Finished')
        self.mock_migration()
        assert not Competition.objects.get(
            id=self.phase1.competition.id).is_migrating

        mock_start = self.mock_migration()
        assert mock_start.call_count == 0

    def test_only_parent_submissions_migrated(self):
        self.parent_submission = self.make_submission()
        self.phase1.submissions.exclude(id=self.parent_submission.id).update(
            parent=self.parent_submission)
        assert Submission.objects.get(
            id=self.parent_submission.id).children.exists()
        assert Submission.objects.get(
            id=self.parent_submission.id).children.count() > 0
        mock_sub_start = self.mock_migration()
        assert mock_sub_start.call_count == 1