Skip to content

Commit

Permalink
improve api v2 for runs (#632)
Browse files Browse the repository at this point in the history
[#186625877]
  • Loading branch information
uraniumanchor authored Dec 13, 2023
1 parent 3930a47 commit 58341ca
Show file tree
Hide file tree
Showing 14 changed files with 319 additions and 66 deletions.
38 changes: 33 additions & 5 deletions tests/apiv2/test_bids.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ def test_detail(self):
data = self.get_detail(self.chain_top)
self.assertEqual(data, serialized.data)

with self.subTest('nested'):
serialized = BidSerializer(
self.opened_parent_bid, event_pk=self.event.id, tree=True
)
data = self.get_detail(
self.opened_parent_bid, kwargs={'event_pk': self.event.id}
)
self.assertEqual(data, serialized.data)

def test_hidden_detail(self):
with self.subTest('user with permission'):
self.hidden_parent_bid.refresh_from_db()
Expand All @@ -46,7 +55,9 @@ def test_list(self):
with self.subTest('authenticated'):
with self.subTest('normal list'):
serialized = BidSerializer(
models.Bid.objects.filter(event=self.event).public(), many=True
models.Bid.objects.filter(event=self.event).public(),
event_pk=self.event.id,
many=True,
)
data = self.get_list(kwargs={'event_pk': self.event.pk})
self.assertEqual(data['results'], serialized.data)
Expand All @@ -58,6 +69,7 @@ def test_list(self):
getattr(
models.Bid.objects.filter(event=self.event), feed
)(),
event_pk=self.event.id,
many=True,
)
data = self.get_list(
Expand All @@ -67,9 +79,9 @@ def test_list(self):

# current is a bit more detailed
with self.subTest('current'):
opened_bid = BidSerializer(self.opened_bid)
opened_bid = BidSerializer(self.opened_bid, event_pk=self.event.id)
# challenge is pinned, always shows up regardless of parameters
challenge = BidSerializer(self.challenge)
challenge = BidSerializer(self.challenge, event_pk=self.event.id)
data = self.get_list(
kwargs={'event_pk': self.event.pk, 'feed': 'current'},
data={'now': self.run.starttime},
Expand Down Expand Up @@ -110,6 +122,7 @@ def test_list(self):
)(),
include_hidden=True,
with_permissions=('tracker.view_hidden_bid',),
event_pk=self.event.id,
many=True,
)
data = self.get_list(
Expand Down Expand Up @@ -143,6 +156,7 @@ def test_tree(self):
serialized = BidSerializer(
models.Bid.objects.filter(event=self.event, level=0).public(),
many=True,
event_pk=self.event.id,
tree=True,
)
data = self.get_noun('tree', kwargs={'event_pk': self.event.pk})
Expand All @@ -154,6 +168,7 @@ def test_tree(self):
include_hidden=True,
with_permissions=('tracker.view_hidden_bid',),
many=True,
event_pk=self.event.id,
tree=True,
)
data = self.get_noun(
Expand Down Expand Up @@ -364,7 +379,7 @@ def test_patch(self):


class TestBidSerializer(TestBidBase, APITestCase):
def _format_bid(self, bid, *, child=False, skip_children=False):
def _format_bid(self, bid, *, with_event=True, child=False, skip_children=False):
data = {
'type': 'bid',
'id': bid.id,
Expand All @@ -381,13 +396,14 @@ def _format_bid(self, bid, *, child=False, skip_children=False):
if not child:
data = {
**data,
'event': bid.event_id,
'speedrun': bid.speedrun_id,
'parent': bid.parent_id,
'goal': bid.goal,
'chain': bid.chain,
'pinned': bid.pinned,
}
if with_event:
data['event'] = bid.event_id
if not bid.chain:
data = {
**data,
Expand Down Expand Up @@ -417,6 +433,10 @@ def _format_bid(self, bid, *, child=False, skip_children=False):

def test_single(self):
with self.subTest('chained bid'):
# TODO
# serialized = BidSerializer(self.chain_top)
# self.assertV2ModelPresent(self._format_bid(self.chain_top, tree=False), serialized.data)

serialized = BidSerializer(self.chain_top, tree=True)
self.assertV2ModelPresent(self._format_bid(self.chain_top), serialized.data)
self.assertV2ModelPresent(
Expand Down Expand Up @@ -481,3 +501,11 @@ def test_single(self):
self.assertV2ModelPresent(
self._format_bid(self.opened_bid), serialized.data
)

with self.subTest('nested in event'):
serialized = BidSerializer(
self.opened_bid, event_pk=self.event.id, tree=True
)
self.assertV2ModelPresent(
self._format_bid(self.opened_bid, with_event=False), serialized.data
)
118 changes: 118 additions & 0 deletions tests/apiv2/test_runs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
from tracker import models
from tracker.api.serializers import (
EventSerializer,
HeadsetSerializer,
RunnerSerializer,
SpeedRunSerializer,
)

from ..test_speedrun import TestSpeedRunBase
from ..util import APITestCase


class TestRunViewSet(TestSpeedRunBase, APITestCase):
model_name = 'speedrun'
view_user_permissions = ['can_view_tech_notes']

def setUp(self):
super().setUp()
self.client.force_authenticate(user=self.locked_user)

def test_detail(self):
with self.subTest('normal detail'):
serialized = SpeedRunSerializer(self.run1)
data = self.get_detail(self.run1)
self.assertEqual(serialized.data, data)
serialized = SpeedRunSerializer(self.run1, event_pk=self.event.pk)
data = self.get_detail(self.run1, kwargs={'event_pk': self.event.pk})
self.assertEqual(data, serialized.data)

with self.subTest('wrong event (whether event exists or not)'):
self.get_detail(
self.run1, kwargs={'event_pk': self.event.pk + 1}, status_code=404
)

with self.subTest('permissions checks'):
self.get_detail(
self.run1, data={'tech_notes': ''}, user=None, status_code=403
)

def test_list(self):
with self.subTest('normal lists'):
serialized = SpeedRunSerializer(
models.SpeedRun.objects.filter(event=self.event), many=True
)
data = self.get_list()
self.assertEqual(data['results'], serialized.data)

serialized = SpeedRunSerializer(
models.SpeedRun.objects.filter(event=self.event),
event_pk=self.event.pk,
many=True,
)
data = self.get_list(kwargs={'event_pk': self.event.pk})
self.assertEqual(data['results'], serialized.data)

with self.subTest('requesting tech notes'):
serialized = SpeedRunSerializer(
models.SpeedRun.objects.filter(event=self.event),
with_permissions=('tracker.can_view_tech_notes',),
with_tech_notes=True,
many=True,
)
data = self.get_list(data={'tech_notes': ''})
self.assertEqual(data['results'], serialized.data)

with self.subTest('permissions checks'):
self.get_list(data={'tech_notes': ''}, user=None, status_code=403)

with self.subTest('not a real event'):
self.get_list(kwargs={'event_pk': self.event.pk + 100}, status_code=404)


class TestRunSerializer(TestSpeedRunBase, APITestCase):
def _format_run(self, run, *, with_event=True, with_tech_notes=False):
data = {
'type': 'speedrun',
'id': run.id,
'name': run.name,
'display_name': run.display_name,
'commentators': HeadsetSerializer(run.commentators, many=True).data,
'run_time': run.run_time,
'order': run.order,
'hosts': HeadsetSerializer(run.hosts, many=True).data,
'endtime': run.endtime,
'category': run.category,
'runners': RunnerSerializer(run.runners, many=True).data,
'description': run.description,
'console': run.console,
'starttime': run.starttime,
'anchor_time': run.anchor_time,
'setup_time': run.setup_time,
}
if with_event:
data['event'] = EventSerializer(run.event).data
if with_tech_notes:
data['tech_notes'] = run.tech_notes
return data

def test_single(self):
with self.subTest('public view'):
serialized = SpeedRunSerializer(self.run1)
self.assertV2ModelPresent(self._format_run(self.run1), serialized.data)

with self.subTest('tech notes'):
serialized = SpeedRunSerializer(
self.run1,
with_permissions=('tracker.can_view_tech_notes',),
with_tech_notes=True,
)
self.assertV2ModelPresent(
self._format_run(self.run1, with_tech_notes=True), serialized.data
)

with self.subTest('without event'):
serialized = SpeedRunSerializer(self.run1, event_pk=self.event.id)
self.assertV2ModelPresent(
self._format_run(self.run1, with_event=False), serialized.data
)
6 changes: 0 additions & 6 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,12 +216,6 @@ def setUp(self):
order=1,
event=self.event2,
)
# TODO: something about resetting the timestamps to the right format idk
self.run1.refresh_from_db()
self.run2.refresh_from_db()
self.run3.refresh_from_db()
self.run4.refresh_from_db()
self.run5.refresh_from_db()

@classmethod
def format_run(cls, run):
Expand Down
10 changes: 7 additions & 3 deletions tests/test_speedrun.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@
from backports import zoneinfo


class TestSpeedRun(TransactionTestCase):
class TestSpeedRunBase(TransactionTestCase):
def setUp(self):
super().setUp()
self.event1 = models.Event.objects.create(datetime=today_noon, targetamount=5)
self.run1 = models.SpeedRun.objects.create(
name='Test Run', run_time='45:00', setup_time='5:00', order=1
Expand All @@ -39,13 +40,16 @@ def setUp(self):
self.runner1 = models.Runner.objects.create(name='trihex')
self.runner2 = models.Runner.objects.create(name='neskamikaze')


class TestSpeedRun(TestSpeedRunBase):
# TODO: maybe disallow partial seconds? this cropped as a bug but we never actually use milliseconds

def test_run_time(self):
def test_timestamps(self):
self.run1.run_time = '45:00.1'
self.run1.setup_time = 300000
self.run1.save()
self.run1.refresh_from_db()
self.assertEqual(self.run1.run_time, '0:45:00.100')
self.assertEqual(self.run1.setup_time, '0:05:00')

def test_first_run_start_time(self):
self.assertEqual(self.run1.starttime, self.event1.datetime)
Expand Down
66 changes: 40 additions & 26 deletions tests/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from django.core.serializers.json import DjangoJSONEncoder
from django.db import connection
from django.db.migrations.executor import MigrationExecutor
from django.db.models import Q
from django.test import RequestFactory, TransactionTestCase, override_settings
from django.urls import reverse
from rest_framework.test import APIClient
Expand Down Expand Up @@ -366,6 +367,7 @@ def _compare_model(self, expected_model, found_model, partial, prefix=''):
for k in expected_model.keys()
if k in found_model and isinstance(found_model[k], dict)
]
nested_objects = [n for n in nested_objects if n[1]]
nested_list_keys = {
f'{prefix}.'
if prefix
Expand Down Expand Up @@ -529,39 +531,51 @@ def setUp(self):
Permission.objects.get(name='Can edit locked events')
)
if self.model_name:
self.view_user.user_permissions.add(
Permission.objects.get(name=f'Can view {self.model_name}'),
# TODO: unify codename use to get rid of the union
view_perm = Permission.objects.get(
Q(name=f'Can view {self.model_name}')
| Q(codename=f'view_{self.model_name}')
)

change_perm = Permission.objects.get(
Q(name=f'Can change {self.model_name}')
| Q(codename=f'change_{self.model_name}')
)
add_perm = Permission.objects.get(
Q(name=f'Can add {self.model_name}')
| Q(codename=f'add_{self.model_name}')
)
self.view_user.user_permissions.add(view_perm)
self.add_user.user_permissions.add(
Permission.objects.get(name=f'Can add {self.model_name}'),
Permission.objects.get(name=f'Can change {self.model_name}'),
Permission.objects.get(name=f'Can view {self.model_name}'),
view_perm,
change_perm,
add_perm,
)
self.locked_user.user_permissions.add(
Permission.objects.get(name=f'Can add {self.model_name}'),
Permission.objects.get(name=f'Can change {self.model_name}'),
Permission.objects.get(name=f'Can view {self.model_name}'),
view_perm,
change_perm,
add_perm,
)
self.view_user.user_permissions.add(
*(Permission.objects.filter(codename__in=self.view_user_permissions))
permissions = Permission.objects.filter(codename__in=self.view_user_permissions)
assert permissions.count() == len(
self.view_user_permissions
), 'permission code mismatch'
self.view_user.user_permissions.add(*permissions)
permissions |= Permission.objects.filter(codename__in=self.add_user_permissions)
assert permissions.count() == len(
set(self.view_user_permissions + self.add_user_permissions)
), 'permission code mismatch'
self.add_user.user_permissions.add(*permissions)
permissions |= Permission.objects.filter(
codename__in=self.locked_user_permissions
)
self.add_user.user_permissions.add(
*(
Permission.objects.filter(
codename__in=self.view_user_permissions + self.add_user_permissions
)
assert permissions.count() == len(
set(
self.view_user_permissions
+ self.add_user_permissions
+ self.locked_user_permissions
)
)
self.locked_user.user_permissions.add(
*(
Permission.objects.filter(
codename__in=self.view_user_permissions
+ self.add_user_permissions
+ self.locked_user_permissions
)
)
)
), 'permission code mismatch'
self.locked_user.user_permissions.add(*permissions)
self.super_user = User.objects.create(username='super', is_superuser=True)
self.maxDiff = None

Expand Down
2 changes: 1 addition & 1 deletion tracker/api/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def filter_queryset(self, request, queryset, view):
queryset = queryset.pending()
# no change for 'all'
elif feed is not None:
if feed.upper() in Bid.objects.ALL_FEEDS:
if feed.upper() in Bid.ALL_FEEDS:
logger.warning(f'unhandled valid bid feed `{feed}`')
raise NotFound(
detail=messages.INVALID_FEED % feed, code=messages.INVALID_FEED_CODE
Expand Down
2 changes: 2 additions & 0 deletions tracker/api/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
MALFORMED_SEARCH_PARAMETER_CODE = 'malformed_search_parameter'
UNAUTHORIZED_FILTER_PARAM = _('You are not allowed to perform that search.')
UNAUTHORIZED_FILTER_PARAM_CODE = 'unauthorized_search_parameter'
UNAUTHORIZED_FIELD = _('You are not allowed to query that field.')
UNAUTHORIZED_FIELD_CODE = 'unauthorized_field'
INVALID_FEED = _('`%s` is not a valid feed.')
INVALID_FEED_CODE = 'invalid_feed'
INVALID_SEARCH_PARAMETER_CODE = 'invalid_search_parameter'
Loading

0 comments on commit 58341ca

Please sign in to comment.