diff --git a/cards/tests.py b/cards/tests.py index 7ce503c..19518bf 100644 --- a/cards/tests.py +++ b/cards/tests.py @@ -1,3 +1,287 @@ -from django.test import TestCase +from django.test import TestCase, Client +from django.template import Template, Context +from datetime import timedelta -# Create your tests here. +from django.urls import reverse +from django.utils import timezone + +from accounts.models import CustomUser, FriendCode +from cards.models import Card, Deck, DeckNameTranslation, CardNameTranslation +from trades.models import TradeOffer, TradeOfferHaveCard, TradeOfferWantCard +from cards.templatetags import card_badge, card_multiselect + +class CardsModelsTestCase(TestCase): + def setUp(self): + self.deck = Deck.objects.create( + name="Test Deck", hex_color="#FFFFFF", cardset="A" + ) + self.card = Card.objects.create( + name="Test Card", + cardset="A", + cardnum=1, + style="color: blue;", + rarity_icon="★", + rarity_level=1 + ) + # Establish many-to-many relationship. + self.card.decks.add(self.deck) + + def test_card_str(self): + expected = f"{self.card.name} {self.card.rarity_icon} {self.card.cardset}" + self.assertEqual(str(self.card), expected) + + def test_deck_str(self): + self.assertEqual(str(self.deck), self.deck.name) + + def test_deck_name_translation_str(self): + deck_translation = DeckNameTranslation.objects.create( + name="Deck Translated", deck=self.deck, language="en" + ) + self.assertEqual(str(deck_translation), "Deck Translated") + + def test_card_name_translation_str(self): + card_translation = CardNameTranslation.objects.create( + name="Card Translated", card=self.card, language="en" + ) + self.assertEqual(str(card_translation), "Card Translated") + +class CardTemplatetagsTestCase(TestCase): + def setUp(self): + # Create a dummy card to use in template tag tests. + self.card = Card.objects.create( + name="Template Test Card", + cardset="B", + cardnum=2, + style="background: green;", + rarity_icon="☆", + rarity_level=2 + ) + + def test_card_badge_inclusion_tag(self): + """Test the card_badge inclusion tag renders correctly.""" + template_str = '{% load card_badge %}{% card_badge card quantity=3 %}' + t = Template(template_str) + c = Context({"card": self.card}) + rendered = t.render(c) + # Check that the rendered HTML contains the card name, quantity, and rarity. + self.assertIn(self.card.name, rendered) + self.assertIn("3", rendered) + self.assertIn(self.card.rarity_icon, rendered) + + def test_card_badge_inline_filter(self): + """Test the card_badge_inline filter returns safe HTML with correct data.""" + template_str = '{% load card_badge %}{{ card|card_badge_inline:5 }}' + t = Template(template_str) + c = Context({"card": self.card}) + rendered = t.render(c) + self.assertIn(self.card.name, rendered) + self.assertIn("5", rendered) + self.assertIn(self.card.rarity_icon, rendered) + + def test_card_multiselect_tag_no_selected_values(self): + """Test card_multiselect tag with no selected values.""" + context = card_multiselect.card_multiselect( + field_name="cards", + label="Select Cards", + placeholder="Choose a card", + cards=[self.card], + selected_values=None, + ) + self.assertEqual(context["field_name"], "cards") + self.assertEqual(context["label"], "Select Cards") + self.assertEqual(context["placeholder"], "Choose a card") + # When no cards are preselected, each card should have default attributes. + for card in context["cards"]: + self.assertFalse(getattr(card, "selected", False)) + self.assertEqual(getattr(card, "selected_quantity", 1), 1) + self.assertEqual(context["selected_values"], []) + + def test_card_multiselect_tag_with_selected_values(self): + """Test card_multiselect tag with preselected values (testing both with and without explicit quantity).""" + # Create a second card. + card2 = Card.objects.create( + name="Another Card", + cardset="B", + cardnum=3, + style="background: blue;", + rarity_icon="★", + rarity_level=2, + ) + selected_values = [f"{self.card.pk}:4", f"{card2.pk}"] + context = card_multiselect.card_multiselect( + field_name="cards", + label="Select Cards", + placeholder="Choose a card", + cards=[self.card, card2], + selected_values=selected_values, + ) + # Verify that self.card is marked as selected with quantity "4" and card2 with default quantity 1. + for card in context["cards"]: + if card.pk == self.card.pk: + self.assertTrue(getattr(card, "selected", False)) + self.assertEqual(getattr(card, "selected_quantity", 1), "4") + elif card.pk == card2.pk: + self.assertTrue(getattr(card, "selected", False)) + self.assertEqual(getattr(card, "selected_quantity", 1), 1) + else: + self.fail("Unexpected card in the multiselect context.") + self.assertCountEqual( + context["selected_values"], [str(self.card.pk), str(card2.pk)] + ) + + def test_card_multiselect_default_cards_when_none_provided(self): + """Test that card_multiselect defaults to Card.objects.all() when no cards are provided.""" + # Capture all cards from the database. + default_cards = list(Card.objects.all()) + context = card_multiselect.card_multiselect( + field_name="cards", + label="Select Cards", + placeholder="Choose a card", + cards=None, + selected_values=[], + ) + # Verify that the context's cards match those in the database. + self.assertEqual(list(context["cards"]), default_cards) + +class CardsViewsTestCase(TestCase): + def setUp(self): + self.client = Client() + # Create a test user and friend code for trade offers. + self.user = CustomUser.objects.create_user( + username="testuser", password="secret", email="test@example.com" + ) + self.friendcode = FriendCode.objects.create( + user=self.user, friend_code="1234-5678-9012", in_game_name="TestPlayer" + ) + # Create a test card. + self.card = Card.objects.create( + name="Test Card", + cardset="A", + cardnum=1, + style="background: red;", + rarity_icon="★", + rarity_level=1 + ) + + def test_card_detail_view_context(self): + """Test that the card detail view includes correct trade offer counts in context.""" + # Create a trade offer where the card appears as a "have" card. + trade_offer_have = TradeOffer.objects.create(initiated_by=self.friendcode) + TradeOfferHaveCard.objects.create( + trade_offer=trade_offer_have, card=self.card, quantity=2 + ) + + # Create a trade offer where the card appears as a "want" card. + trade_offer_want = TradeOffer.objects.create(initiated_by=self.friendcode) + TradeOfferWantCard.objects.create( + trade_offer=trade_offer_want, card=self.card, quantity=3 + ) + + url = reverse("cards:card_detail", kwargs={"pk": self.card.pk}) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + # Verify that the card instance is in context. + self.assertEqual(response.context["card"], self.card) + # Verify that the counts are correctly computed. + self.assertEqual(response.context.get("trade_offer_have_count"), 1) + self.assertEqual(response.context.get("trade_offer_want_count"), 1) + + def test_card_detail_view_404(self): + """Test that the card detail view returns a 404 for a non-existent card.""" + url = reverse("cards:card_detail", kwargs={"pk": 99999}) + response = self.client.get(url) + self.assertEqual(response.status_code, 404) + + def create_trade_offer_for_have(self, updated_delta_minutes=0): + """ + Helper method to create a trade offer for the 'have' side with a custom updated_at. + """ + offer = TradeOffer.objects.create(initiated_by=self.friendcode) + TradeOfferHaveCard.objects.create( + trade_offer=offer, card=self.card, quantity=1 + ) + # Adjust updated_at so that ordering can be tested. + new_time = timezone.now() + timedelta(minutes=updated_delta_minutes) + TradeOffer.objects.filter(pk=offer.pk).update(updated_at=new_time) + offer.refresh_from_db() + return offer + + def create_trade_offer_for_want(self, updated_delta_minutes=0): + """ + Helper method to create a trade offer for the 'want' side with a custom updated_at. + """ + offer = TradeOffer.objects.create(initiated_by=self.friendcode) + TradeOfferWantCard.objects.create( + trade_offer=offer, card=self.card, quantity=1 + ) + new_time = timezone.now() + timedelta(minutes=updated_delta_minutes) + TradeOffer.objects.filter(pk=offer.pk).update(updated_at=new_time) + offer.refresh_from_db() + return offer + + def test_trade_offer_have_list_view_pagination_and_ordering(self): + """Test the have list view for correct pagination and ordering.""" + # Create three trade offers with distinct updated_at times. + offer1 = self.create_trade_offer_for_have(updated_delta_minutes=1) + offer2 = self.create_trade_offer_for_have(updated_delta_minutes=2) + offer3 = self.create_trade_offer_for_have(updated_delta_minutes=3) + + url = reverse("cards:card_trade_offer_have_list", kwargs={"pk": self.card.pk}) + + # Test default ordering ("newest" which orders descending by updated_at). + response = self.client.get(url, {"order": "newest"}) + self.assertEqual(response.status_code, 200) + trade_offers = response.context.get("trade_offers") + self.assertEqual(response.context.get("side"), "have") + # With paginate_by=2, the first page should have 2 offers. + self.assertEqual(len(trade_offers), 2) + # The first offer should be the newest (offer3). + self.assertEqual(trade_offers[0].pk, offer3.pk) + self.assertEqual(trade_offers[1].pk, offer2.pk) + + # Test pagination: second page should contain the remaining offer. + response_page2 = self.client.get(url, {"order": "newest", "page": 2}) + self.assertEqual(response_page2.status_code, 200) + trade_offers_page2 = response_page2.context.get("trade_offers") + self.assertEqual(len(trade_offers_page2), 1) + self.assertEqual(trade_offers_page2[0].pk, offer1.pk) + + # Test "oldest" ordering (ascending by updated_at). + response_oldest = self.client.get(url, {"order": "oldest"}) + self.assertEqual(response_oldest.status_code, 200) + trade_offers_oldest = response_oldest.context.get("trade_offers") + self.assertEqual(len(trade_offers_oldest), 2) + self.assertEqual(trade_offers_oldest[0].pk, offer1.pk) + self.assertEqual(trade_offers_oldest[1].pk, offer2.pk) + + def test_trade_offer_want_list_view_pagination_and_ordering(self): + """Test the want list view for correct pagination and ordering.""" + offer1 = self.create_trade_offer_for_want(updated_delta_minutes=1) + offer2 = self.create_trade_offer_for_want(updated_delta_minutes=2) + offer3 = self.create_trade_offer_for_want(updated_delta_minutes=3) + + url = reverse("cards:card_trade_offer_want_list", kwargs={"pk": self.card.pk}) + + # Test order with "newest" first. + response = self.client.get(url, {"order": "newest"}) + self.assertEqual(response.status_code, 200) + trade_offers = response.context.get("trade_offers") + self.assertEqual(response.context.get("side"), "want") + self.assertEqual(len(trade_offers), 2) + self.assertEqual(trade_offers[0].pk, offer3.pk) + self.assertEqual(trade_offers[1].pk, offer2.pk) + + # Test pagination boundary on page 2. + response_page2 = self.client.get(url, {"order": "newest", "page": 2}) + self.assertEqual(response_page2.status_code, 200) + trade_offers_page2 = response_page2.context.get("trade_offers") + self.assertEqual(len(trade_offers_page2), 1) + self.assertEqual(trade_offers_page2[0].pk, offer1.pk) + + # Test ordering parameter for "oldest" ordering. + response_oldest = self.client.get(url, {"order": "oldest"}) + self.assertEqual(response_oldest.status_code, 200) + trade_offers_oldest = response_oldest.context.get("trade_offers") + self.assertEqual(len(trade_offers_oldest), 2) + self.assertEqual(trade_offers_oldest[0].pk, offer1.pk) + self.assertEqual(trade_offers_oldest[1].pk, offer2.pk) \ No newline at end of file