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))
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