154 lines
6.7 KiB
Python
154 lines
6.7 KiB
Python
import tempfile
|
|
import time
|
|
import unittest.mock
|
|
from pathlib import Path
|
|
|
|
from django.contrib.auth import get_user_model
|
|
from django.core.cache import cache
|
|
from django.test import TestCase
|
|
from django.urls import reverse
|
|
|
|
from library.models import Project
|
|
|
|
|
|
class HomeSectionsTests(TestCase):
|
|
def setUp(self):
|
|
self.user = get_user_model().objects.create_user(username='Owner', password='pw')
|
|
|
|
def _home(self):
|
|
session = self.client.session
|
|
session['authorized'] = True
|
|
session.save()
|
|
self.client.force_login(self.user)
|
|
return self.client.get(reverse('landing:home')).content.decode()
|
|
|
|
def test_empty_categories_skipped(self):
|
|
Project.objects.create(slug='only-mod', title='Only Mod', category='mod', owner=self.user)
|
|
html = self._home()
|
|
self.assertIn('>Mod</h2>', html)
|
|
self.assertNotIn('>Skin</h2>', html)
|
|
self.assertNotIn('>Guide</h2>', html)
|
|
|
|
def test_popular_ordered_by_rating(self):
|
|
Project.objects.create(slug='m0', title='Mod 0', category='mod', owner=self.user, rating_score=0)
|
|
Project.objects.create(slug='m2', title='Mod 2', category='mod', owner=self.user, rating_score=2)
|
|
Project.objects.create(slug='m1', title='Mod 1', category='mod', owner=self.user, rating_score=1)
|
|
html = self._home()
|
|
sections = html.split('class="home-cat-block"')[1:]
|
|
mod = next(s for s in sections if '>Mod</h2>' in s)
|
|
popular = mod.split('home-cat-divider')[0]
|
|
self.assertLess(popular.index('Mod 2'), popular.index('Mod 1'))
|
|
self.assertLess(popular.index('Mod 1'), popular.index('Mod 0'))
|
|
|
|
def test_columns_capped_at_six(self):
|
|
for i in range(8):
|
|
Project.objects.create(slug=f'm{i}', title=f'Mod {i}', category='mod', owner=self.user, rating_score=i)
|
|
html = self._home()
|
|
sections = html.split('class="home-cat-block"')[1:]
|
|
mod = next(s for s in sections if '>Mod</h2>' in s)
|
|
popular = mod.split('home-cat-divider')[0]
|
|
recent = mod.split('home-cat-divider')[1]
|
|
self.assertEqual(popular.count('class="pack-card-row"'), 6)
|
|
self.assertEqual(recent.count('class="pack-card-row"'), 6)
|
|
|
|
def test_recent_column_orders_by_creation(self):
|
|
Project.objects.create(slug='s0', title='High', category='skin', owner=self.user, rating_score=5)
|
|
Project.objects.create(slug='s1', title='Mid', category='skin', owner=self.user, rating_score=3)
|
|
Project.objects.create(slug='s2', title='Low', category='skin', owner=self.user, rating_score=0)
|
|
html = self._home()
|
|
sections = html.split('class="home-cat-block"')[1:]
|
|
skin = next(s for s in sections if '>Skin</h2>' in s)
|
|
popular = skin.split('home-cat-divider')[0]
|
|
recent = skin.split('home-cat-divider')[1]
|
|
# Popular leads with the top-rated pack.
|
|
self.assertLess(popular.index('High'), popular.index('Low'))
|
|
# Recent leads with the newest (created last -> 'Low').
|
|
self.assertLess(recent.index('Low'), recent.index('High'))
|
|
|
|
|
|
class ServerStatsTests(TestCase):
|
|
def setUp(self):
|
|
User = get_user_model()
|
|
self.staff = User.objects.create_user(
|
|
username='Boss', password='pw', is_staff=True,
|
|
)
|
|
self.normal = User.objects.create_user(username='NormalUser', password='pw')
|
|
cache.clear()
|
|
|
|
def _authorized(self, user=None):
|
|
session = self.client.session
|
|
session['authorized'] = True
|
|
session.save()
|
|
if user is not None:
|
|
self.client.force_login(user)
|
|
return session
|
|
|
|
def test_staff_api_returns_snapshot(self):
|
|
self._authorized(self.staff)
|
|
resp = self.client.get(reverse('landing:server_stats_api'))
|
|
self.assertEqual(resp.status_code, 200)
|
|
data = resp.json()
|
|
for key in ('app', 'system', 'content', 'recent', 'traffic', 'generated_at'):
|
|
self.assertIn(key, data)
|
|
self.assertIn('users', data['content'])
|
|
self.assertIn('versions', data['recent'])
|
|
|
|
def test_api_payload_cached(self):
|
|
self._authorized(self.staff)
|
|
first = self.client.get(reverse('landing:server_stats_api')).json()
|
|
second = self.client.get(reverse('landing:server_stats_api')).json()
|
|
self.assertEqual(first['generated_at'], second['generated_at'])
|
|
|
|
def test_non_staff_api_forbidden(self):
|
|
self._authorized(self.normal)
|
|
resp = self.client.get(reverse('landing:server_stats_api'))
|
|
self.assertEqual(resp.status_code, 403)
|
|
self.assertEqual(resp.json()['detail'], 'Only staff can view server stats.')
|
|
|
|
def test_page_is_staff_shell(self):
|
|
self._authorized(self.staff)
|
|
resp = self.client.get(reverse('landing:server_stats'))
|
|
self.assertEqual(resp.status_code, 200)
|
|
self.assertContains(resp, 'stats-updated')
|
|
self.assertContains(resp, '/api/stats/')
|
|
|
|
def test_page_forbidden_for_non_staff(self):
|
|
self._authorized(self.normal)
|
|
resp = self.client.get(reverse('landing:server_stats'))
|
|
self.assertEqual(resp.status_code, 403)
|
|
|
|
def test_anonymous_redirects_to_login(self):
|
|
self._authorized()
|
|
resp = self.client.get(reverse('landing:server_stats'))
|
|
self.assertEqual(resp.status_code, 302)
|
|
|
|
|
|
class TrafficStatsTests(TestCase):
|
|
def test_parses_access_log(self):
|
|
from landing import views
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
log = Path(tmp) / 'gunicorn-access.log'
|
|
now = time.strftime('%d/%b/%Y:%H:%M:%S %z', time.localtime())
|
|
log.write_text(''.join([
|
|
f'127.0.0.1 [{now}] "GET /home/ HTTP/1.1" 200 1200 0.004\n',
|
|
f'127.0.0.1 [{now}] "POST /api/uploads/ HTTP/1.1" 200 80 0.020\n',
|
|
f'127.0.0.1 [{now}] "GET /packs/test-pack/ HTTP/1.1" 404 300 0.001\n',
|
|
f'10.0.0.5 [{now}] "GET /api/stats/ HTTP/1.1" 500 50 0.150\n',
|
|
]))
|
|
with unittest.mock.patch.object(views, 'GUNICORN_ACCESS_LOG', log):
|
|
stats = views._traffic_stats()
|
|
self.assertIsNotNone(stats)
|
|
self.assertEqual(stats['requests_total'], 4)
|
|
self.assertEqual(stats['requests_min'], 4)
|
|
self.assertEqual(stats['status'], {'2xx': 2, '3xx': 0, '4xx': 1, '5xx': 1})
|
|
self.assertEqual(stats['max_ms'], 150.0)
|
|
self.assertEqual(len(stats['recent']), 4)
|
|
self.assertEqual(stats['recent'][0]['status'], 500)
|
|
|
|
def test_missing_log_returns_none(self):
|
|
from landing import views
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
log = Path(tmp) / 'nope.log'
|
|
with unittest.mock.patch.object(views, 'GUNICORN_ACCESS_LOG', log):
|
|
self.assertIsNone(views._traffic_stats())
|