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())