Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

91 Add endpoints for starting match, ending match and update scores #116

Merged
merged 13 commits into from
Jan 30, 2024
Merged
86 changes: 68 additions & 18 deletions tournament/doc/tournament-api-documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -312,10 +312,41 @@ None

</details>

Generate matches for a tournament

<details>
<summary><code>POST</code> <code><b>/tournament/{id}/matches/generate</b></code></summary>

### Parameters

#### Body

- Randomly generate matches (optional, default = false)

> ```javascript
> {
> "random": true
> }

### Responses

> | http code | content-type | response |
> |-----------|--------------------|----------------------------------------|
> | `200` | `application/json` | `{"nb-matches": 14, "matches": [...]}` |
> | `404` | `application/json` | `{"errors": ["AAA", ...]}` |

</details>

--------------------------------------------------------------------------------

## `/tournament/{id}/match`

### Manage match of a tournament

Retrieve details of a match

<details>
<summary><code>GET</code> <code><b>/tournament/{id}/matches/{match-id}</b></code></summary>
<summary><code>GET</code> <code><b>/tournament/{id}/match/{match-id}</b></code></summary>

### Parameters

Expand Down Expand Up @@ -343,10 +374,10 @@ None

</details>

Update match status
Start a match

<details>
<summary><code>PATCH</code> <code><b>/tournament/{id}/matches/{match-id}</b></code></summary>
<summary><code>POST</code> <code><b>/tournament/{id}/match/{match-id}/start</b></code></summary>

### Parameters

Expand All @@ -357,18 +388,36 @@ All fields are optional
- The status of the match
- The first player
- The second player
- The score of the first player
- The score of the second player
- The winner of the match

> ```javascript
> {
> "status": 1,
> "player1": "Player1",
> "player2": "Player2",
> "score1": 2,
> "score2": 1,
> "winner": "Player1"
> "player1": 1,
> "player2": 2
> }

### Responses

> | http code | content-type | response |
> |-----------|--------------------|-------------------------------------------|
> | `200` | `application/json` | `{"id": 1, "status": "In-progress", ...}` |
> | `400` | `application/json` | `{"errors": ["AAA", "BBB", "..."]}` |

</details>

Add one point to a player

<details>
<summary><code>POST</code> <code><b>/tournament/{id}/match/{match-id}/add-point</b></code></summary>

### Parameters

#### Body

- The player to add a point

> ```javascript
> {
> "player": 1
> }
> ```

Expand All @@ -381,27 +430,28 @@ All fields are optional

</details>

Generate matches for a tournament
End a match

<details>
<summary><code>POST</code> <code><b>/tournament/{id}/matches/generate</b></code></summary>
<summary><code>POST</code> <code><b>/tournament/{id}/match/{match-id}/end</b></code></summary>

### Parameters

#### Body

- Randomly generate matches (optional, default = false)
- Winner of the match

> ```javascript
> {
> "random": true
> "winner": 2
> }
> ```

### Responses

> | http code | content-type | response |
> |-----------|--------------------|----------------------------------------|
> | `200` | `application/json` | `{"nb-matches": 14, "matches": [...]}` |
> | `404` | `application/json` | `{"errors": ["AAA", ...]}` |
> | `200` | `application/json` | `{"id": 1, "status": "Finished", ...}` |
> | `400` | `application/json` | `{"errors": ["AAA", "BBB", "..."]}` |

</details>
6 changes: 4 additions & 2 deletions tournament/src/api/error_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,14 @@
ALREADY_STARTED = 'Tournament has already started'
NOT_ENOUGH_PLAYERS = 'Not enough players to start tournament'

TOURNAMENT_NOT_STARTED = 'Tournament has not started'

MATCH_FINISHED = 'Match has already finished'
MATCH_NOT_FOUND = 'Match not found'
MATCH_STATUS_INVALID = 'Invalid match status'
MATCH_STATUS_NOT_INT = 'Match status must be an integer'
MATCH_PLAYER_NOT_INT = 'Player must be an integer'
MATCH_PLAYER_NOT_EXIST = 'Player does not exist'
MATCH_PLAYER_SCORE_NOT_INT = 'Match player score must be an integer'
MATCH_PLAYER_SCORE_INVALID = f'Player score must be between 0 and {settings.MATCH_POINT_TO_WIN}'
MATCH_WINNER_NOT_INT = 'Winner must be an integer'
MATCH_WINNER_NOT_EXIST = 'Winner does not exist'
MATCHES_NOT_GENERATED = 'Matches have not been generated yet'
18 changes: 18 additions & 0 deletions tournament/src/api/migrations/0013_player_rank.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.7 on 2024-01-27 14:22

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('api', '0012_match_player_1_score_match_player_2_score_and_more'),
]

operations = [
migrations.AddField(
model_name='player',
name='rank',
field=models.IntegerField(null=True),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Generated by Django 4.2.7 on 2024-01-28 18:17

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('api', '0013_player_rank'),
]

operations = [
migrations.AlterField(
model_name='match',
name='player_1',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='player_1', to='api.player'),
),
migrations.AlterField(
model_name='match',
name='player_2',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='player_2', to='api.player'),
),
migrations.AlterField(
model_name='match',
name='winner',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='winner', to='api.player'),
),
]
7 changes: 4 additions & 3 deletions tournament/src/api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class Player(models.Model):
nickname = models.CharField(max_length=settings.MAX_NICKNAME_LENGTH)
user_id = models.IntegerField()
elo = models.IntegerField(null=True)
rank = models.IntegerField(null=True)
tournament = models.ForeignKey(Tournament, on_delete=models.CASCADE, related_name='players')


Expand All @@ -29,13 +30,13 @@ class Match(models.Model):
IN_PROGRESS = 1
FINISHED = 2

player_1 = models.ForeignKey(Player, on_delete=models.SET_NULL, null=True, related_name='player_1')
player_1 = models.ForeignKey(Player, on_delete=models.CASCADE, null=True, related_name='player_1')
player_1_score = models.IntegerField(null=True)

player_2 = models.ForeignKey(Player, on_delete=models.SET_NULL, null=True, related_name='player_2')
player_2 = models.ForeignKey(Player, on_delete=models.CASCADE, null=True, related_name='player_2')
player_2_score = models.IntegerField(null=True)

winner = models.ForeignKey(Player, on_delete=models.SET_NULL, null=True, related_name='winner')
winner = models.ForeignKey(Player, on_delete=models.CASCADE, null=True, related_name='winner')

tournament = models.ForeignKey(Tournament, on_delete=models.CASCADE, related_name='matches')

Expand Down
2 changes: 2 additions & 0 deletions tournament/src/api/tests/test_manage_tournament.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ def test_get_tournament(self, mock_authenticate_request):
self.assertEqual(body['status'], 'Created')
self.assertEqual(body['players'], [{
'nickname': f'Player{i}',
'rank': None,
'user-id': i
} for i in range(1, 14)])

Expand All @@ -62,6 +63,7 @@ def test_get_tournament_with_deadline(self, mock_authenticate_request):
self.assertEqual(body['players'], [
{
'nickname': f'Player{i}',
'rank': None,
'user-id': i
} for i in range(1, 3)
])
Expand Down
55 changes: 55 additions & 0 deletions tournament/src/api/tests/test_tournament.py
Original file line number Diff line number Diff line change
Expand Up @@ -547,3 +547,58 @@ def test_delete_no_tournament(self, mock_get):

self.assertEqual(response.status_code, 200)
self.assertEqual(body['message'], 'No tournament created by this user')


class StartTournamentTest(TestCase):
@patch('api.views.generate_matches_views.authenticate_request')
def setUp(self, mock_get):
user = {'id': 1}
mock_get.return_value = (user, None)
tournament = Tournament.objects.create(id=1, name='tournament 1', admin_id=1, max_players=8)

for i in range(0, 8):
Player.objects.create(
id=i,
nickname=f'player {i}',
user_id=i,
tournament=tournament,
)

url = reverse('generate-matches', args=(1,))

self.client.post(url, {'random': True}, content_type='application/json')

def start_tournament(self, tournament_id):
url = reverse('start-tournament', args=(tournament_id,))

response = self.client.patch(url)

body = json.loads(response.content.decode('utf8'))

return response, body

@patch('api.views.manage_tournament_views.authenticate_request')
def test_start_tournament(self, mock_get):
user = {'id': 1}
mock_get.return_value = (user, None)

tournament = Tournament.objects.get(id=1)

response, body = self.start_tournament(1)

self.assertEqual(response.status_code, 200)
self.assertEqual(body['message'], f'Tournament `{tournament.name}` successfully started')

tournament = Tournament.objects.get(id=1)

self.assertEqual(tournament.status, Tournament.IN_PROGRESS)

@patch('api.views.manage_tournament_views.authenticate_request')
def test_start_tournament_not_found(self, mock_get):
user = {'id': 1}
mock_get.return_value = (user, None)

response, body = self.start_tournament(2)

self.assertEqual(response.status_code, 404)
self.assertEqual(body['errors'], ['tournament with id `2` does not exist'])
Loading