Repository: Cloud-CV/EvalAI

Issue:
Teams: A user can participate in a Challenge through multiple teams
### Observed behaviour

#### Steps to reproduce
1. User X participated in Challenge C with team T1
2. User Y created a team T2
3. User Y invited User X to team T2
4. User Y can participate in challenge C with team T2

### Expected behavior
Step 4 should not be allowed since user X has already participated in the challenge with Team T1. And hence user Y should be asked to choose a new team to participate in the challenge. 
Teams: A user can participate in a Challenge through multiple teams
### Observed behaviour

#### Steps to reproduce
1. User X participated in Challenge C with team T1
2. User Y created a team T2
3. User Y invited User X to team T2
4. User Y can participate in challenge C with team T2

### Expected behavior
Step 4 should not be allowed since user X has already participated in the challenge with Team T1. And hence user Y should be asked to choose a new team to participate in the challenge. 


Discussion:

Resolving patch:
diff --git a/apps/challenges/views.py b/apps/challenges/views.py
--- a/apps/challenges/views.py
+++ b/apps/challenges/views.py
@@ -14,7 +14,7 @@
 from hosts.models import ChallengeHost, ChallengeHostTeam
 from hosts.utils import get_challenge_host_teams_for_user
 from participants.models import Participant, ParticipantTeam
-from participants.utils import get_participant_teams_for_user
+from participants.utils import get_participant_teams_for_user, has_user_participated_in_challenge
 
 
 from .models import Challenge, ChallengePhase, ChallengePhaseSplit
@@ -130,12 +130,19 @@ def add_participant_team_to_challenge(request, challenge_pk, participant_team_pk
         team__id=participant_team_pk).values_list('user', flat=True))
 
     if challenge_host_team_user_ids & participant_team_user_ids:
-        response_data = {'message': 'Sorry, You cannot participate in your own challenge!',
+        response_data = {'error': 'Sorry, You cannot participate in your own challenge!',
                          'challenge_id': int(challenge_pk), 'participant_team_id': int(participant_team_pk)}
         return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
 
+    for user in participant_team_user_ids:
+        if has_user_participated_in_challenge(user, challenge_pk):
+            response_data = {'error': 'Sorry, other team member(s) have already participated in the Challenge.'
+                             ' Please participate with a different team!',
+                             'challenge_id': int(challenge_pk), 'participant_team_id': int(participant_team_pk)}
+            return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
+
     if participant_team.challenge_set.filter(id=challenge_pk).exists():
-        response_data = {'message': 'Team already exists', 'challenge_id': int(challenge_pk),
+        response_data = {'error': 'Team already exists', 'challenge_id': int(challenge_pk),
                          'participant_team_id': int(participant_team_pk)}
         return Response(response_data, status=status.HTTP_200_OK)
     else:


Test patch:
diff --git a/tests/unit/challenges/test_views.py b/tests/unit/challenges/test_views.py
--- a/tests/unit/challenges/test_views.py
+++ b/tests/unit/challenges/test_views.py
@@ -317,6 +317,11 @@ def setUp(self):
             username='someuser3',
             password='some_secret_password')
 
+        # user invited to the participant team
+        self.user4 = User.objects.create(
+            username='someuser4',
+            password='some_secret_password')
+
         self.challenge_host_team2 = ChallengeHostTeam.objects.create(
             team_name='Some Test Challenge Host Team',
             created_by=self.user2)
@@ -351,17 +356,31 @@ def setUp(self):
             team_name='Some Participant Team',
             created_by=self.user3)
 
+        self.participant_team3 = ParticipantTeam.objects.create(
+            team_name='Some Participant Team by User 4',
+            created_by=self.user4)
+
         self.participant2 = Participant.objects.create(
             user=self.user3,
             status=Participant.SELF,
             team=self.participant_team2)
 
+        self.participant3 = Participant.objects.create(
+            user=self.user4,
+            status=Participant.ACCEPTED,
+            team=self.participant_team2)
+
+        self.participant4 = Participant.objects.create(
+            user=self.user4,
+            status=Participant.SELF,
+            team=self.participant_team3)
+
     def test_map_challenge_and_participant_team_together(self):
         response = self.client.post(self.url, {})
         self.assertEqual(response.status_code, status.HTTP_201_CREATED)
         # to check when the api is hit again
         expected = {
-            'message': 'Team already exists',
+            'error': 'Team already exists',
             'challenge_id': self.challenge.pk,
             'participant_team_id': self.participant_team.pk
         }
@@ -374,7 +393,7 @@ def test_when_challenge_host_maps_a_participant_team_with_his_challenge(self):
                                 kwargs={'challenge_pk': self.challenge2.pk,
                                         'participant_team_pk': self.participant_team2.pk})
         expected = {
-            'message': 'Sorry, You cannot participate in your own challenge!',
+            'error': 'Sorry, You cannot participate in your own challenge!',
             'challenge_id': self.challenge2.pk,
             'participant_team_id': self.participant_team2.pk
         }
@@ -396,7 +415,7 @@ def test_particular_challenge_for_mapping_with_participant_team_does_not_exist(s
     def test_particular_participant_team_for_mapping_with_challenge_does_not_exist(self):
         self.url = reverse_lazy('challenges:add_participant_team_to_challenge',
                                 kwargs={'challenge_pk': self.challenge.pk,
-                                        'participant_team_pk': self.participant_team.pk + 2})
+                                        'participant_team_pk': self.participant_team.pk + 3})
         expected = {
             'error': 'ParticipantTeam does not exist'
         }
@@ -404,6 +423,29 @@ def test_particular_participant_team_for_mapping_with_challenge_does_not_exist(s
         self.assertEqual(response.data, expected)
         self.assertEqual(response.status_code, status.HTTP_406_NOT_ACCEPTABLE)
 
+    def test_add_participant_team_to_challenge_when_some_members_have_already_participated(self):
+        self.url = reverse_lazy('challenges:add_participant_team_to_challenge',
+                                kwargs={'challenge_pk': self.challenge.pk,
+                                        'participant_team_pk': self.participant_team2.pk})
+
+        self.client.post(self.url, {})
+
+        expected = {
+            'error': 'Sorry, other team member(s) have already participated in the Challenge.'
+            ' Please participate with a different team!',
+            'challenge_id': self.challenge.pk,
+            'participant_team_id': self.participant_team3.pk,
+        }
+
+        # submitting the request again as a new team
+        self.url = reverse_lazy('challenges:add_participant_team_to_challenge',
+                                kwargs={'challenge_pk': self.challenge.pk,
+                                        'participant_team_pk': self.participant_team3.pk})
+
+        response = self.client.post(self.url, {})
+        self.assertEqual(response.data, expected)
+        self.assertEqual(response.status_code, status.HTTP_406_NOT_ACCEPTABLE)
+
 
 class DisableChallengeTest(BaseAPITestClass):