This commit is contained in:
@@ -3,6 +3,15 @@ when:
|
|||||||
event: [push, manual]
|
event: [push, manual]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
test:
|
||||||
|
image: python:3.12-slim
|
||||||
|
commands:
|
||||||
|
- pip install --quiet -r requirements.txt
|
||||||
|
- python manage.py test backlogger --verbosity=2
|
||||||
|
environment:
|
||||||
|
DJANGO_SECRET_KEY: ci-test-key
|
||||||
|
DATABASE_URL: sqlite:///tmp/test.db
|
||||||
|
|
||||||
build-and-deploy:
|
build-and-deploy:
|
||||||
image: docker:cli
|
image: docker:cli
|
||||||
volumes:
|
volumes:
|
||||||
@@ -13,3 +22,4 @@ steps:
|
|||||||
- cp -r . /opt/services/app/
|
- cp -r . /opt/services/app/
|
||||||
- docker build -t k-boris-website /opt/services/app/
|
- docker build -t k-boris-website /opt/services/app/
|
||||||
- docker compose -f /opt/services/docker-compose.yml up -d --no-deps django
|
- docker compose -f /opt/services/docker-compose.yml up -d --no-deps django
|
||||||
|
depends_on: [test]
|
||||||
|
|||||||
283
backlogger/tests.py
Normal file
283
backlogger/tests.py
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from .forms import ItemForm, SignupForm
|
||||||
|
from .models import Item
|
||||||
|
|
||||||
|
|
||||||
|
def make_user(username='alice', active=True):
|
||||||
|
user = User.objects.create_user(username=username, password='testpass123', email=f'{username}@example.com')
|
||||||
|
user.is_active = active
|
||||||
|
user.save()
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
def make_item(user, name='Test Game', category=Item.GAMES, **kwargs):
|
||||||
|
return Item.objects.create(user=user, name=name, category=category, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Auth
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class AuthRequiredTests(TestCase):
|
||||||
|
def test_list_redirects_anonymous(self):
|
||||||
|
response = self.client.get(reverse('backlogger:list'))
|
||||||
|
self.assertRedirects(response, '/accounts/login/?next=/backlogger/')
|
||||||
|
|
||||||
|
def test_add_redirects_anonymous(self):
|
||||||
|
response = self.client.get(reverse('backlogger:add'))
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
|
def test_inactive_user_cannot_login(self):
|
||||||
|
make_user('bob', active=False)
|
||||||
|
response = self.client.post(reverse('login'), {'username': 'bob', 'password': 'testpass123'})
|
||||||
|
# Should stay on login page, not redirect to backlogger
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertFalse(response.wsgi_request.user.is_authenticated)
|
||||||
|
|
||||||
|
def test_active_user_can_login(self):
|
||||||
|
make_user('alice')
|
||||||
|
response = self.client.post(reverse('login'), {'username': 'alice', 'password': 'testpass123'}, follow=True)
|
||||||
|
self.assertTrue(response.wsgi_request.user.is_authenticated)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Signup
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class SignupTests(TestCase):
|
||||||
|
def test_get_signup_page(self):
|
||||||
|
response = self.client.get(reverse('signup'))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, 'Request account')
|
||||||
|
|
||||||
|
def test_signup_creates_inactive_user(self):
|
||||||
|
response = self.client.post(reverse('signup'), {
|
||||||
|
'username': 'newuser',
|
||||||
|
'email': 'new@example.com',
|
||||||
|
'password1': 'StrongPass99!',
|
||||||
|
'password2': 'StrongPass99!',
|
||||||
|
})
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, 'pending')
|
||||||
|
user = User.objects.get(username='newuser')
|
||||||
|
self.assertFalse(user.is_active)
|
||||||
|
|
||||||
|
def test_signup_invalid_mismatched_passwords(self):
|
||||||
|
self.client.post(reverse('signup'), {
|
||||||
|
'username': 'newuser',
|
||||||
|
'email': 'new@example.com',
|
||||||
|
'password1': 'StrongPass99!',
|
||||||
|
'password2': 'WrongPass99!',
|
||||||
|
})
|
||||||
|
self.assertFalse(User.objects.filter(username='newuser').exists())
|
||||||
|
|
||||||
|
def test_signup_duplicate_username(self):
|
||||||
|
make_user('taken')
|
||||||
|
response = self.client.post(reverse('signup'), {
|
||||||
|
'username': 'taken',
|
||||||
|
'email': 'other@example.com',
|
||||||
|
'password1': 'StrongPass99!',
|
||||||
|
'password2': 'StrongPass99!',
|
||||||
|
})
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(User.objects.filter(username='taken').count(), 1)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Item list
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class ItemListTests(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.alice = make_user('alice')
|
||||||
|
self.bob = make_user('bob')
|
||||||
|
self.client.force_login(self.alice)
|
||||||
|
|
||||||
|
def test_list_shows_own_items(self):
|
||||||
|
make_item(self.alice, name='Alice Game')
|
||||||
|
make_item(self.bob, name='Bob Game')
|
||||||
|
response = self.client.get(reverse('backlogger:list'))
|
||||||
|
self.assertContains(response, 'Alice Game')
|
||||||
|
self.assertNotContains(response, 'Bob Game')
|
||||||
|
|
||||||
|
def test_list_category_filter(self):
|
||||||
|
make_item(self.alice, name='My Game', category=Item.GAMES)
|
||||||
|
make_item(self.alice, name='My Book', category=Item.BOOKS)
|
||||||
|
response = self.client.get(reverse('backlogger:list') + '?category=games')
|
||||||
|
self.assertContains(response, 'My Game')
|
||||||
|
self.assertNotContains(response, 'My Book')
|
||||||
|
|
||||||
|
def test_list_empty_state(self):
|
||||||
|
response = self.client.get(reverse('backlogger:list'))
|
||||||
|
self.assertContains(response, 'Add your first one')
|
||||||
|
|
||||||
|
def test_list_sort_az(self):
|
||||||
|
make_item(self.alice, name='Zelda')
|
||||||
|
make_item(self.alice, name='Ape')
|
||||||
|
response = self.client.get(reverse('backlogger:list') + '?sort=az')
|
||||||
|
names = [item.name for item in response.context['items']]
|
||||||
|
self.assertEqual(names, sorted(names))
|
||||||
|
|
||||||
|
def test_list_sort_favorites_first(self):
|
||||||
|
make_item(self.alice, name='Normal')
|
||||||
|
make_item(self.alice, name='Fav', favorite=True)
|
||||||
|
response = self.client.get(reverse('backlogger:list') + '?sort=fav')
|
||||||
|
names = [item.name for item in response.context['items']]
|
||||||
|
self.assertEqual(names[0], 'Fav')
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Item add
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class ItemAddTests(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.user = make_user('alice')
|
||||||
|
self.client.force_login(self.user)
|
||||||
|
|
||||||
|
def test_add_game(self):
|
||||||
|
self.client.post(reverse('backlogger:add'), {
|
||||||
|
'category': 'games',
|
||||||
|
'name': 'Elden Ring',
|
||||||
|
'progress_percent': 50,
|
||||||
|
'favorite': False,
|
||||||
|
'hours_played': 20,
|
||||||
|
'total_hours': 80,
|
||||||
|
})
|
||||||
|
item = Item.objects.get(name='Elden Ring')
|
||||||
|
self.assertEqual(item.user, self.user)
|
||||||
|
self.assertEqual(item.category, Item.GAMES)
|
||||||
|
self.assertEqual(item.hours_played, 20)
|
||||||
|
|
||||||
|
def test_add_assigns_current_user(self):
|
||||||
|
self.client.post(reverse('backlogger:add'), {
|
||||||
|
'category': 'books',
|
||||||
|
'name': 'Dune',
|
||||||
|
'progress_percent': 0,
|
||||||
|
'favorite': False,
|
||||||
|
})
|
||||||
|
item = Item.objects.get(name='Dune')
|
||||||
|
self.assertEqual(item.user, self.user)
|
||||||
|
|
||||||
|
def test_add_invalid_shows_form(self):
|
||||||
|
response = self.client.post(reverse('backlogger:add'), {'name': ''})
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertFalse(Item.objects.exists())
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Item edit
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class ItemEditTests(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.alice = make_user('alice')
|
||||||
|
self.bob = make_user('bob')
|
||||||
|
self.client.force_login(self.alice)
|
||||||
|
|
||||||
|
def test_edit_own_item(self):
|
||||||
|
item = make_item(self.alice, name='Original')
|
||||||
|
self.client.post(reverse('backlogger:edit', args=[item.pk]), {
|
||||||
|
'category': 'games',
|
||||||
|
'name': 'Updated',
|
||||||
|
'progress_percent': 75,
|
||||||
|
'favorite': False,
|
||||||
|
})
|
||||||
|
item.refresh_from_db()
|
||||||
|
self.assertEqual(item.name, 'Updated')
|
||||||
|
|
||||||
|
def test_edit_other_user_item_returns_404(self):
|
||||||
|
item = make_item(self.bob, name='Bobs Item')
|
||||||
|
response = self.client.get(reverse('backlogger:edit', args=[item.pk]))
|
||||||
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Item delete
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class ItemDeleteTests(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.alice = make_user('alice')
|
||||||
|
self.bob = make_user('bob')
|
||||||
|
self.client.force_login(self.alice)
|
||||||
|
|
||||||
|
def test_delete_own_item(self):
|
||||||
|
item = make_item(self.alice, name='To Delete')
|
||||||
|
self.client.post(reverse('backlogger:delete', args=[item.pk]))
|
||||||
|
self.assertFalse(Item.objects.filter(pk=item.pk).exists())
|
||||||
|
|
||||||
|
def test_delete_other_user_item_returns_404(self):
|
||||||
|
item = make_item(self.bob, name='Bobs Item')
|
||||||
|
response = self.client.post(reverse('backlogger:delete', args=[item.pk]))
|
||||||
|
self.assertEqual(response.status_code, 404)
|
||||||
|
self.assertTrue(Item.objects.filter(pk=item.pk).exists())
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Forms
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class ItemFormTests(TestCase):
|
||||||
|
def _post(self, **kwargs):
|
||||||
|
data = {'category': 'games', 'name': 'Test', 'progress_percent': 0, 'favorite': False}
|
||||||
|
data.update(kwargs)
|
||||||
|
form = ItemForm(data)
|
||||||
|
form.is_valid()
|
||||||
|
return form
|
||||||
|
|
||||||
|
def test_game_fields_kept_for_games(self):
|
||||||
|
form = self._post(category='games', hours_played=10, total_hours=40)
|
||||||
|
self.assertEqual(form.cleaned_data['hours_played'], 10)
|
||||||
|
|
||||||
|
def test_game_fields_nulled_for_books(self):
|
||||||
|
form = self._post(category='books', hours_played=10)
|
||||||
|
self.assertIsNone(form.cleaned_data['hours_played'])
|
||||||
|
|
||||||
|
def test_book_fields_kept_for_books(self):
|
||||||
|
form = self._post(category='books', pages_read=100, total_pages=300)
|
||||||
|
self.assertEqual(form.cleaned_data['pages_read'], 100)
|
||||||
|
|
||||||
|
def test_book_fields_nulled_for_games(self):
|
||||||
|
form = self._post(category='games', pages_read=100)
|
||||||
|
self.assertIsNone(form.cleaned_data['pages_read'])
|
||||||
|
|
||||||
|
def test_film_fields_nulled_for_games(self):
|
||||||
|
form = self._post(category='games', duration_minutes=120)
|
||||||
|
self.assertIsNone(form.cleaned_data['duration_minutes'])
|
||||||
|
|
||||||
|
def test_progress_over_100_invalid(self):
|
||||||
|
form = self._post(progress_percent=150)
|
||||||
|
self.assertIn('progress_percent', form.errors)
|
||||||
|
|
||||||
|
def test_progress_negative_invalid(self):
|
||||||
|
form = self._post(progress_percent=-1)
|
||||||
|
self.assertIn('progress_percent', form.errors)
|
||||||
|
|
||||||
|
|
||||||
|
class SignupFormTests(TestCase):
|
||||||
|
def _data(self, **overrides):
|
||||||
|
data = {
|
||||||
|
'username': 'newuser',
|
||||||
|
'email': 'new@example.com',
|
||||||
|
'password1': 'StrongPass99!',
|
||||||
|
'password2': 'StrongPass99!',
|
||||||
|
}
|
||||||
|
data.update(overrides)
|
||||||
|
return data
|
||||||
|
|
||||||
|
def test_valid_form(self):
|
||||||
|
form = SignupForm(self._data())
|
||||||
|
self.assertTrue(form.is_valid())
|
||||||
|
|
||||||
|
def test_email_required(self):
|
||||||
|
form = SignupForm(self._data(email=''))
|
||||||
|
self.assertFalse(form.is_valid())
|
||||||
|
self.assertIn('email', form.errors)
|
||||||
|
|
||||||
|
def test_password_mismatch(self):
|
||||||
|
form = SignupForm(self._data(password2='different'))
|
||||||
|
self.assertFalse(form.is_valid())
|
||||||
Reference in New Issue
Block a user