Fix friend_code max length issues in tests, and fix in_game_name length issues, also update tests to fit more scenarios
This commit is contained in:
parent
0d4655bf80
commit
b9c4d7a61d
10 changed files with 558 additions and 66 deletions
|
|
@ -1,6 +1,14 @@
|
||||||
from django.contrib.auth.models import AbstractUser
|
from django.contrib.auth.models import AbstractUser
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
import re
|
||||||
|
|
||||||
|
def validate_friend_code(value):
|
||||||
|
"""Validate that friend code follows the format XXXX-XXXX-XXXX-XXXX where X is a digit."""
|
||||||
|
if not re.match(r'^\d{4}-\d{4}-\d{4}-\d{4}$', value):
|
||||||
|
raise ValidationError(
|
||||||
|
'Friend code must be in format XXXX-XXXX-XXXX-XXXX where X is a digit.'
|
||||||
|
)
|
||||||
|
|
||||||
class CustomUser(AbstractUser):
|
class CustomUser(AbstractUser):
|
||||||
default_friend_code = models.ForeignKey("FriendCode", on_delete=models.SET_NULL, null=True, blank=True)
|
default_friend_code = models.ForeignKey("FriendCode", on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
|
|
@ -34,8 +42,8 @@ class CustomUser(AbstractUser):
|
||||||
self.save(update_fields=["default_friend_code"])
|
self.save(update_fields=["default_friend_code"])
|
||||||
|
|
||||||
class FriendCode(models.Model):
|
class FriendCode(models.Model):
|
||||||
friend_code = models.CharField(max_length=19)
|
friend_code = models.CharField(max_length=19, validators=[validate_friend_code])
|
||||||
in_game_name = models.CharField(max_length=16, null=False, blank=False)
|
in_game_name = models.CharField(max_length=14, null=False, blank=False)
|
||||||
user = models.ForeignKey(CustomUser, on_delete=models.PROTECT, related_name='friend_codes')
|
user = models.ForeignKey(CustomUser, on_delete=models.PROTECT, related_name='friend_codes')
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ from accounts.models import FriendCode
|
||||||
from accounts.forms import FriendCodeForm, CustomUserCreationForm, UserSettingsForm
|
from accounts.forms import FriendCodeForm, CustomUserCreationForm, UserSettingsForm
|
||||||
from accounts.templatetags import gravatar
|
from accounts.templatetags import gravatar
|
||||||
from trades.models import TradeOffer
|
from trades.models import TradeOffer
|
||||||
|
from tests.utils.rarity import RARITY_MAPPING
|
||||||
|
|
||||||
# Create your tests here.
|
# Create your tests here.
|
||||||
|
|
||||||
|
|
@ -415,11 +416,11 @@ class FriendCodeViewsTests(TestCase):
|
||||||
response = self.client.get(url)
|
response = self.client.get(url)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
# POST request.
|
# POST request.
|
||||||
new_data = {"in_game_name": "UpdatedViewGame"}
|
new_data = {"in_game_name": "UpdatedGame"}
|
||||||
response = self.client.post(url, new_data)
|
response = self.client.post(url, new_data)
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
self.friend_code2.refresh_from_db()
|
self.friend_code2.refresh_from_db()
|
||||||
self.assertEqual(self.friend_code2.in_game_name, "UpdatedViewGame")
|
self.assertEqual(self.friend_code2.in_game_name, "UpdatedGame")
|
||||||
|
|
||||||
def test_edit_friend_code_view_wrong_user(self):
|
def test_edit_friend_code_view_wrong_user(self):
|
||||||
"""A user should not be able to edit a friend code that does not belong to them."""
|
"""A user should not be able to edit a friend code that does not belong to them."""
|
||||||
|
|
@ -490,7 +491,7 @@ class FriendCodeViewsTests(TestCase):
|
||||||
self.trade_offer = TradeOffer.objects.create(
|
self.trade_offer = TradeOffer.objects.create(
|
||||||
initiated_by=self.friend_code2,
|
initiated_by=self.friend_code2,
|
||||||
is_closed=False,
|
is_closed=False,
|
||||||
rarity_icon="⭐️",
|
rarity_icon=RARITY_MAPPING[5],
|
||||||
rarity_level=5
|
rarity_level=5
|
||||||
)
|
)
|
||||||
url = reverse("delete_friend_code", kwargs={"pk": self.friend_code2.pk})
|
url = reverse("delete_friend_code", kwargs={"pk": self.friend_code2.pk})
|
||||||
|
|
|
||||||
|
|
@ -36,16 +36,18 @@ class Deck(models.Model):
|
||||||
|
|
||||||
class Card(models.Model):
|
class Card(models.Model):
|
||||||
id = models.AutoField(primary_key=True)
|
id = models.AutoField(primary_key=True)
|
||||||
name = models.CharField(max_length=128)
|
name = models.CharField(max_length=64)
|
||||||
decks = models.ManyToManyField("Deck")
|
decks = models.ManyToManyField("Deck")
|
||||||
cardset = models.CharField(max_length=8)
|
cardset = models.CharField(max_length=32)
|
||||||
cardnum = models.IntegerField()
|
cardnum = models.IntegerField()
|
||||||
style = models.CharField(max_length=255, null=False)
|
style = models.CharField(max_length=16)
|
||||||
rarity_icon = models.CharField(max_length=8)
|
rarity_icon = models.CharField(max_length=12)
|
||||||
rarity_level = models.IntegerField()
|
rarity_level = models.IntegerField()
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ('cardset', 'cardnum')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
# For display, we show the original rarity icons.
|
return f"{self.name} ({self.cardset} #{self.cardnum})"
|
||||||
return f"{self.name} {self.rarity_icon} {self.cardset}"
|
|
||||||
|
|
@ -9,6 +9,7 @@ from accounts.models import CustomUser, FriendCode
|
||||||
from cards.models import Card, Deck, DeckNameTranslation, CardNameTranslation
|
from cards.models import Card, Deck, DeckNameTranslation, CardNameTranslation
|
||||||
from trades.models import TradeOffer, TradeOfferHaveCard, TradeOfferWantCard
|
from trades.models import TradeOffer, TradeOfferHaveCard, TradeOfferWantCard
|
||||||
from cards.templatetags import card_badge, card_multiselect
|
from cards.templatetags import card_badge, card_multiselect
|
||||||
|
from tests.utils.rarity import RARITY_MAPPING
|
||||||
|
|
||||||
class CardsModelsTests(TestCase):
|
class CardsModelsTests(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
@ -19,15 +20,15 @@ class CardsModelsTests(TestCase):
|
||||||
name="Test Card",
|
name="Test Card",
|
||||||
cardset="A",
|
cardset="A",
|
||||||
cardnum=1,
|
cardnum=1,
|
||||||
style="color: blue;",
|
style="default",
|
||||||
rarity_icon="★",
|
rarity_icon=RARITY_MAPPING[1],
|
||||||
rarity_level=1
|
rarity_level=1
|
||||||
)
|
)
|
||||||
# Establish many-to-many relationship.
|
# Establish many-to-many relationship.
|
||||||
self.card.decks.add(self.deck)
|
self.card.decks.add(self.deck)
|
||||||
|
|
||||||
def test_card_str(self):
|
def test_card_str(self):
|
||||||
expected = f"{self.card.name} {self.card.rarity_icon} {self.card.cardset}"
|
expected = f"{self.card.name} ({self.card.cardset} #{self.card.cardnum})"
|
||||||
self.assertEqual(str(self.card), expected)
|
self.assertEqual(str(self.card), expected)
|
||||||
|
|
||||||
def test_deck_str(self):
|
def test_deck_str(self):
|
||||||
|
|
@ -158,8 +159,8 @@ class CardsViewsTests(TestCase):
|
||||||
name="Test Card",
|
name="Test Card",
|
||||||
cardset="A",
|
cardset="A",
|
||||||
cardnum=1,
|
cardnum=1,
|
||||||
style="background: red;",
|
style="default",
|
||||||
rarity_icon="★",
|
rarity_icon=RARITY_MAPPING[1],
|
||||||
rarity_level=1
|
rarity_level=1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ from collections import OrderedDict
|
||||||
from unittest.mock import patch, MagicMock
|
from unittest.mock import patch, MagicMock
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
import importlib
|
import importlib
|
||||||
|
from tests.utils.rarity import RARITY_MAPPING
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
@ -74,13 +75,13 @@ class HomePageViewTests(TestCase):
|
||||||
# Create trade offers with consistent rarities
|
# Create trade offers with consistent rarities
|
||||||
cls.common_trade = TradeOffer.objects.create(
|
cls.common_trade = TradeOffer.objects.create(
|
||||||
initiated_by=cls.friend_code,
|
initiated_by=cls.friend_code,
|
||||||
rarity_icon='★',
|
rarity_icon=RARITY_MAPPING[1],
|
||||||
rarity_level=1
|
rarity_level=1
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.rare_trade = TradeOffer.objects.create(
|
cls.rare_trade = TradeOffer.objects.create(
|
||||||
initiated_by=cls.friend_code,
|
initiated_by=cls.friend_code,
|
||||||
rarity_icon='★★★',
|
rarity_icon=RARITY_MAPPING[3],
|
||||||
rarity_level=3
|
rarity_level=3
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
10
tests/utils/rarity.py
Normal file
10
tests/utils/rarity.py
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
RARITY_MAPPING = {
|
||||||
|
1: "🔷",
|
||||||
|
2: "🔷🔷",
|
||||||
|
3: "🔷🔷🔷",
|
||||||
|
4: "🔷🔷🔷🔷",
|
||||||
|
5: "⭐️",
|
||||||
|
6: "⭐️⭐️",
|
||||||
|
7: "⭐️⭐️⭐️",
|
||||||
|
8: "👑"
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
from cards.models import Card
|
from cards.models import Card
|
||||||
|
from django.core.exceptions import PermissionDenied
|
||||||
|
|
||||||
class TradeOfferContextMixin:
|
class TradeOfferContextMixin:
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
|
|
@ -21,3 +22,16 @@ class TradeOfferContextMixin:
|
||||||
context["selected_friend_code"] = selected_friend_code
|
context["selected_friend_code"] = selected_friend_code
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
class FriendCodeRequiredMixin:
|
||||||
|
"""
|
||||||
|
Mixin to ensure the authenticated user has at least one friend code.
|
||||||
|
This mixin must be placed after LoginRequiredMixin in the view's inheritance order.
|
||||||
|
"""
|
||||||
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
# Since LoginRequiredMixin guarantees that request.user is authenticated,
|
||||||
|
# we assume request.user has the attribute `friend_codes`. If no friend code exists,
|
||||||
|
# raise a PermissionDenied error.
|
||||||
|
if not getattr(request.user, 'friend_codes', None) or not request.user.friend_codes.exists():
|
||||||
|
raise PermissionDenied("No friend codes available for your account.")
|
||||||
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
@ -254,12 +254,12 @@ class TradeAcceptance(models.Model):
|
||||||
def clean(self):
|
def clean(self):
|
||||||
# Validate that the requested and offered cards exist in the through tables.
|
# Validate that the requested and offered cards exist in the through tables.
|
||||||
try:
|
try:
|
||||||
have_through_obj = self.trade_offer.trade_offer_have_cards.get(card=self.requested_card)
|
have_through_obj = self.trade_offer.trade_offer_have_cards.get(card_id=self.requested_card_id)
|
||||||
except TradeOfferHaveCard.DoesNotExist:
|
except TradeOfferHaveCard.DoesNotExist:
|
||||||
raise ValidationError("The requested card must be one of the trade offer's available cards (have_cards).")
|
raise ValidationError("The requested card must be one of the trade offer's available cards (have_cards).")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
want_through_obj = self.trade_offer.trade_offer_want_cards.get(card=self.offered_card)
|
want_through_obj = self.trade_offer.trade_offer_want_cards.get(card_id=self.offered_card_id)
|
||||||
except TradeOfferWantCard.DoesNotExist:
|
except TradeOfferWantCard.DoesNotExist:
|
||||||
raise ValidationError("The offered card must be one of the trade offer's requested cards (want_cards).")
|
raise ValidationError("The offered card must be one of the trade offer's requested cards (want_cards).")
|
||||||
|
|
||||||
|
|
@ -279,11 +279,11 @@ class TradeAcceptance(models.Model):
|
||||||
if self.pk:
|
if self.pk:
|
||||||
active_acceptances = active_acceptances.exclude(pk=self.pk)
|
active_acceptances = active_acceptances.exclude(pk=self.pk)
|
||||||
|
|
||||||
requested_count = active_acceptances.filter(requested_card=self.requested_card).count()
|
requested_count = active_acceptances.filter(requested_card_id=self.requested_card_id).count()
|
||||||
if requested_count >= have_through_obj.quantity:
|
if requested_count >= have_through_obj.quantity:
|
||||||
raise ValidationError("This requested card has been fully accepted.")
|
raise ValidationError("This requested card has been fully accepted.")
|
||||||
|
|
||||||
offered_count = active_acceptances.filter(offered_card=self.offered_card).count()
|
offered_count = active_acceptances.filter(offered_card_id=self.offered_card_id).count()
|
||||||
if offered_count >= want_through_obj.quantity:
|
if offered_count >= want_through_obj.quantity:
|
||||||
raise ValidationError("This offered card has already been fully used.")
|
raise ValidationError("This offered card has already been fully used.")
|
||||||
|
|
||||||
|
|
|
||||||
499
trades/tests.py
499
trades/tests.py
|
|
@ -18,7 +18,7 @@ from trades.forms import (
|
||||||
TradeOfferAcceptForm,
|
TradeOfferAcceptForm,
|
||||||
TradeAcceptanceTransitionForm,
|
TradeAcceptanceTransitionForm,
|
||||||
)
|
)
|
||||||
|
from tests.utils.rarity import RARITY_MAPPING
|
||||||
|
|
||||||
# ------------------------------------------------------------------------
|
# ------------------------------------------------------------------------
|
||||||
# Model Tests
|
# Model Tests
|
||||||
|
|
@ -26,27 +26,29 @@ from trades.forms import (
|
||||||
class TradeOfferModelTest(TestCase):
|
class TradeOfferModelTest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
# Create a user and friend code for testing
|
|
||||||
self.user = User.objects.create_user(
|
self.user = User.objects.create_user(
|
||||||
username="testuser", email="test@example.com", password="password"
|
username="testuser", email="test@example.com", password="password"
|
||||||
)
|
)
|
||||||
self.friend_code = FriendCode.objects.create(
|
self.friend_code = FriendCode.objects.create(
|
||||||
friend_code="FC-1234", in_game_name="TestInGame", user=self.user
|
friend_code="1234-5678-9012-3456", in_game_name="TestInGame", user=self.user
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create cards with the same rarity (valid scenario)
|
# Create cards with the same rarity (valid scenario)
|
||||||
self.card1 = Card.objects.create(
|
self.card1 = Card.objects.create(
|
||||||
name="Card1", cardset="set1", cardnum=1, style="default", rarity_icon="R", rarity_level=1
|
name="Card1", cardset="set1", cardnum=1, style="default",
|
||||||
|
rarity_icon=RARITY_MAPPING[1], rarity_level=1
|
||||||
)
|
)
|
||||||
self.card2 = Card.objects.create(
|
self.card2 = Card.objects.create(
|
||||||
name="Card2", cardset="set1", cardnum=2, style="default", rarity_icon="R", rarity_level=1
|
name="Card2", cardset="set1", cardnum=2, style="default",
|
||||||
|
rarity_icon=RARITY_MAPPING[1], rarity_level=1
|
||||||
)
|
)
|
||||||
# Create a card with a different rarity (to test invalid trade offers)
|
# Create a card with a different rarity (to test invalid trade offers)
|
||||||
self.card3 = Card.objects.create(
|
self.card3 = Card.objects.create(
|
||||||
name="Card3", cardset="set1", cardnum=3, style="default", rarity_icon="SR", rarity_level=2
|
name="Card3", cardset="set1", cardnum=3, style="default",
|
||||||
|
rarity_icon=RARITY_MAPPING[8], rarity_level=8
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create a valid trade offer with consistent rarity details.
|
# Create a valid trade offer with consistent rarity details
|
||||||
self.trade_offer = TradeOffer.objects.create(initiated_by=self.friend_code)
|
self.trade_offer = TradeOffer.objects.create(initiated_by=self.friend_code)
|
||||||
TradeOfferHaveCard.objects.create(
|
TradeOfferHaveCard.objects.create(
|
||||||
trade_offer=self.trade_offer, card=self.card1, quantity=2
|
trade_offer=self.trade_offer, card=self.card1, quantity=2
|
||||||
|
|
@ -59,7 +61,7 @@ class TradeOfferModelTest(TestCase):
|
||||||
"""Test update_rarity_fields succeeds with cards sharing the same rarity."""
|
"""Test update_rarity_fields succeeds with cards sharing the same rarity."""
|
||||||
self.trade_offer.update_rarity_fields()
|
self.trade_offer.update_rarity_fields()
|
||||||
self.assertEqual(self.trade_offer.rarity_level, 1)
|
self.assertEqual(self.trade_offer.rarity_level, 1)
|
||||||
self.assertEqual(self.trade_offer.rarity_icon, "R")
|
self.assertEqual(self.trade_offer.rarity_icon, "🔷")
|
||||||
|
|
||||||
def test_update_rarity_fields_invalid(self):
|
def test_update_rarity_fields_invalid(self):
|
||||||
"""If a card with a different rarity is added, update_rarity_fields should raise an error."""
|
"""If a card with a different rarity is added, update_rarity_fields should raise an error."""
|
||||||
|
|
@ -84,21 +86,23 @@ class TradeAcceptanceModelTest(TestCase):
|
||||||
username="acceptuser", email="acc@example.com", password="password"
|
username="acceptuser", email="acc@example.com", password="password"
|
||||||
)
|
)
|
||||||
self.friend_code = FriendCode.objects.create(
|
self.friend_code = FriendCode.objects.create(
|
||||||
friend_code="FC-5678", in_game_name="AccInGame", user=self.user
|
friend_code="1111-2222-3333-4444", in_game_name="AccInGame", user=self.user
|
||||||
)
|
)
|
||||||
self.other_user = User.objects.create_user(
|
self.other_user = User.objects.create_user(
|
||||||
username="initiator", email="init@example.com", password="password"
|
username="initiator", email="init@example.com", password="password"
|
||||||
)
|
)
|
||||||
self.initiator_friend_code = FriendCode.objects.create(
|
self.initiator_friend_code = FriendCode.objects.create(
|
||||||
friend_code="FC-0000", in_game_name="InitInGame", user=self.other_user
|
friend_code="5555-6666-7777-8888", in_game_name="InitInGame", user=self.other_user
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create two cards (with the same rarity)
|
# Create two cards (with the same rarity)
|
||||||
self.card1 = Card.objects.create(
|
self.card1 = Card.objects.create(
|
||||||
name="CardA", cardset="setA", cardnum=1, style="default", rarity_icon="R", rarity_level=1
|
name="CardA", cardset="setA", cardnum=1, style="default",
|
||||||
|
rarity_icon=RARITY_MAPPING[2], rarity_level=2
|
||||||
)
|
)
|
||||||
self.card2 = Card.objects.create(
|
self.card2 = Card.objects.create(
|
||||||
name="CardB", cardset="setA", cardnum=2, style="default", rarity_icon="R", rarity_level=1
|
name="CardB", cardset="setA", cardnum=2, style="default",
|
||||||
|
rarity_icon=RARITY_MAPPING[2], rarity_level=2
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create a trade offer by the initiator.
|
# Create a trade offer by the initiator.
|
||||||
|
|
@ -198,16 +202,16 @@ class TradeOfferFormTest(TestCase):
|
||||||
username="formuser", email="form@example.com", password="password"
|
username="formuser", email="form@example.com", password="password"
|
||||||
)
|
)
|
||||||
self.friend_code = FriendCode.objects.create(
|
self.friend_code = FriendCode.objects.create(
|
||||||
friend_code="FC-FORM", in_game_name="FormUser", user=self.user
|
friend_code="9999-8888-7777-6666", in_game_name="FormUser", user=self.user
|
||||||
)
|
)
|
||||||
# Create two cards with the same rarity details.
|
# Create two cards with the same rarity details.
|
||||||
self.card1 = Card.objects.create(
|
self.card1 = Card.objects.create(
|
||||||
name="FormCard1", cardset="formset", cardnum=1, style="default",
|
name="FormCard1", cardset="formset", cardnum=1, style="default",
|
||||||
rarity_icon="R", rarity_level=1
|
rarity_icon=RARITY_MAPPING[3], rarity_level=3
|
||||||
)
|
)
|
||||||
self.card2 = Card.objects.create(
|
self.card2 = Card.objects.create(
|
||||||
name="FormCard2", cardset="formset", cardnum=2, style="default",
|
name="FormCard2", cardset="formset", cardnum=2, style="default",
|
||||||
rarity_icon="R", rarity_level=1
|
rarity_icon=RARITY_MAPPING[3], rarity_level=3
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_trade_offer_create_form_valid(self):
|
def test_trade_offer_create_form_valid(self):
|
||||||
|
|
@ -325,7 +329,7 @@ class TradeViewsTest(TestCase):
|
||||||
username="viewuser", email="view@example.com", password="password"
|
username="viewuser", email="view@example.com", password="password"
|
||||||
)
|
)
|
||||||
self.friend_code = FriendCode.objects.create(
|
self.friend_code = FriendCode.objects.create(
|
||||||
friend_code="FC-VIEW", in_game_name="ViewUser", user=self.user
|
friend_code="4444-3333-2222-1111", in_game_name="ViewUser", user=self.user
|
||||||
)
|
)
|
||||||
self.user.default_friend_code = self.friend_code
|
self.user.default_friend_code = self.friend_code
|
||||||
self.user.save(update_fields=["default_friend_code"])
|
self.user.save(update_fields=["default_friend_code"])
|
||||||
|
|
@ -334,11 +338,11 @@ class TradeViewsTest(TestCase):
|
||||||
# Create sample cards.
|
# Create sample cards.
|
||||||
self.card1 = Card.objects.create(
|
self.card1 = Card.objects.create(
|
||||||
name="ViewCard1", cardset="setV", cardnum=1, style="default",
|
name="ViewCard1", cardset="setV", cardnum=1, style="default",
|
||||||
rarity_icon="R", rarity_level=1
|
rarity_icon=RARITY_MAPPING[7], rarity_level=7
|
||||||
)
|
)
|
||||||
self.card2 = Card.objects.create(
|
self.card2 = Card.objects.create(
|
||||||
name="ViewCard2", cardset="setV", cardnum=2, style="default",
|
name="ViewCard2", cardset="setV", cardnum=2, style="default",
|
||||||
rarity_icon="R", rarity_level=1
|
rarity_icon=RARITY_MAPPING[7], rarity_level=7
|
||||||
)
|
)
|
||||||
# Create a trade offer initiated by the logged-in user's friend code.
|
# Create a trade offer initiated by the logged-in user's friend code.
|
||||||
self.trade_offer = TradeOffer.objects.create(initiated_by=self.friend_code)
|
self.trade_offer = TradeOffer.objects.create(initiated_by=self.friend_code)
|
||||||
|
|
@ -460,3 +464,462 @@ class TradeViewsTest(TestCase):
|
||||||
valid_state = allowed_states[0]
|
valid_state = allowed_states[0]
|
||||||
response = self.client.post(update_url, {"state": valid_state})
|
response = self.client.post(update_url, {"state": valid_state})
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
|
|
||||||
|
class TradeOfferSecurityTests(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
User = get_user_model()
|
||||||
|
# Create three users for testing various security scenarios
|
||||||
|
self.user1 = User.objects.create_user(
|
||||||
|
username="user1", email="user1@example.com", password="password1"
|
||||||
|
)
|
||||||
|
self.user2 = User.objects.create_user(
|
||||||
|
username="user2", email="user2@example.com", password="password2"
|
||||||
|
)
|
||||||
|
self.user3 = User.objects.create_user(
|
||||||
|
username="user3", email="user3@example.com", password="password3"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create friend codes for each user with correct format
|
||||||
|
self.fc1 = FriendCode.objects.create(
|
||||||
|
friend_code="1111-2222-3333-4444", in_game_name="User1Game", user=self.user1
|
||||||
|
)
|
||||||
|
self.fc2 = FriendCode.objects.create(
|
||||||
|
friend_code="5555-6666-7777-8888", in_game_name="User2Game", user=self.user2
|
||||||
|
)
|
||||||
|
self.fc3 = FriendCode.objects.create(
|
||||||
|
friend_code="9999-0000-1111-2222", in_game_name="User3Game", user=self.user3
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create test cards with proper rarity levels
|
||||||
|
self.card1 = Card.objects.create(
|
||||||
|
name="SecCard1", cardset="secset", cardnum=1, style="default",
|
||||||
|
rarity_icon=RARITY_MAPPING[3], rarity_level=3
|
||||||
|
)
|
||||||
|
self.card2 = Card.objects.create(
|
||||||
|
name="SecCard2", cardset="secset", cardnum=2, style="default",
|
||||||
|
rarity_icon=RARITY_MAPPING[3], rarity_level=3
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create a trade offer by user1
|
||||||
|
self.trade_offer = TradeOffer.objects.create(initiated_by=self.fc1)
|
||||||
|
TradeOfferHaveCard.objects.create(
|
||||||
|
trade_offer=self.trade_offer, card=self.card1, quantity=1
|
||||||
|
)
|
||||||
|
TradeOfferWantCard.objects.create(
|
||||||
|
trade_offer=self.trade_offer, card=self.card2, quantity=1
|
||||||
|
)
|
||||||
|
|
||||||
|
self.client = Client()
|
||||||
|
|
||||||
|
def test_unauthorized_trade_offer_deletion(self):
|
||||||
|
"""Test that users cannot delete trade offers they don't own."""
|
||||||
|
self.client.login(username="user2", password="password2")
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("trade_offer_delete", kwargs={"pk": self.trade_offer.pk})
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
self.assertTrue(TradeOffer.objects.filter(pk=self.trade_offer.pk).exists())
|
||||||
|
|
||||||
|
def test_unauthorized_trade_acceptance_update(self):
|
||||||
|
"""Test that uninvolved users cannot update trade acceptances."""
|
||||||
|
# Create an acceptance between user2 and user1's offer
|
||||||
|
acceptance = TradeAcceptance.objects.create(
|
||||||
|
trade_offer=self.trade_offer,
|
||||||
|
accepted_by=self.fc2,
|
||||||
|
requested_card=self.card1,
|
||||||
|
offered_card=self.card2,
|
||||||
|
state=TradeAcceptance.AcceptanceState.ACCEPTED,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Try to update the acceptance as user3 (uninvolved)
|
||||||
|
self.client.login(username="user3", password="password3")
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("trade_acceptance_update", kwargs={"pk": acceptance.pk}),
|
||||||
|
{"state": TradeAcceptance.AcceptanceState.SENT}
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
|
def test_cross_user_friend_code_manipulation(self):
|
||||||
|
"""Test that users cannot use other users' friend codes."""
|
||||||
|
self.client.login(username="user2", password="password2")
|
||||||
|
|
||||||
|
# Try to create a trade offer using user1's friend code
|
||||||
|
response = self.client.get(
|
||||||
|
reverse("trade_offer_create"),
|
||||||
|
{
|
||||||
|
"initiated_by": self.fc1.pk, # User1's friend code
|
||||||
|
"have_cards": [f"{self.card1.pk}:1"],
|
||||||
|
"want_cards": [f"{self.card2.pk}:1"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 200) # Form should fail validation
|
||||||
|
self.assertFalse(
|
||||||
|
TradeOffer.objects.filter(initiated_by=self.fc1).count() > 1
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_authenticated_only_views(self):
|
||||||
|
"""Test that authenticated-only views are properly protected."""
|
||||||
|
# Test without login
|
||||||
|
urls_to_test = [
|
||||||
|
reverse("trade_offer_create"),
|
||||||
|
reverse("trade_offer_my_list"),
|
||||||
|
reverse("trade_acceptance_create", kwargs={"offer_pk": self.trade_offer.pk}),
|
||||||
|
]
|
||||||
|
|
||||||
|
# First ensure we're logged out
|
||||||
|
self.client.logout()
|
||||||
|
|
||||||
|
for url in urls_to_test:
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertRedirects(
|
||||||
|
response,
|
||||||
|
f"/accounts/login/?next={url}",
|
||||||
|
msg_prefix=f"URL {url} should require authentication"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TradeOfferEdgeCasesTest(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
User = get_user_model()
|
||||||
|
self.user = User.objects.create_user(
|
||||||
|
username="edgeuser", email="edge@example.com", password="password"
|
||||||
|
)
|
||||||
|
self.friend_code = FriendCode.objects.create(
|
||||||
|
friend_code="3333-4444-5555-6666", in_game_name="EdgeUser", user=self.user
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create test cards with different rarities using proper levels and icons
|
||||||
|
self.common_card = Card.objects.create(
|
||||||
|
name="CommonCard", cardset="edgeset", cardnum=1, style="default",
|
||||||
|
rarity_icon=RARITY_MAPPING[1], rarity_level=1
|
||||||
|
)
|
||||||
|
self.rare_card = Card.objects.create(
|
||||||
|
name="RareCard", cardset="edgeset", cardnum=2, style="default",
|
||||||
|
rarity_icon=RARITY_MAPPING[5], rarity_level=5
|
||||||
|
)
|
||||||
|
self.crown_card = Card.objects.create(
|
||||||
|
name="CrownCard", cardset="edgeset", cardnum=3, style="default",
|
||||||
|
rarity_icon=RARITY_MAPPING[8], rarity_level=8
|
||||||
|
)
|
||||||
|
|
||||||
|
self.client = Client()
|
||||||
|
self.client.login(username="edgeuser", password="password")
|
||||||
|
|
||||||
|
def test_zero_quantity_trade_offer(self):
|
||||||
|
"""Test that trade offers with zero quantity are handled properly."""
|
||||||
|
response = self.client.get(
|
||||||
|
reverse("trade_offer_create"),
|
||||||
|
{
|
||||||
|
"initiated_by": self.friend_code.pk,
|
||||||
|
"have_cards": [f"{self.common_card.pk}:0"],
|
||||||
|
"want_cards": [f"{self.common_card.pk}:1"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertFalse(
|
||||||
|
TradeOffer.objects.filter(initiated_by=self.friend_code).exists()
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_negative_quantity_trade_offer(self):
|
||||||
|
"""Test that trade offers with negative quantity are handled properly."""
|
||||||
|
response = self.client.get(
|
||||||
|
reverse("trade_offer_create"),
|
||||||
|
{
|
||||||
|
"initiated_by": self.friend_code.pk,
|
||||||
|
"have_cards": [f"{self.common_card.pk}:-1"],
|
||||||
|
"want_cards": [f"{self.common_card.pk}:1"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertFalse(
|
||||||
|
TradeOffer.objects.filter(initiated_by=self.friend_code).exists()
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_mixed_rarity_trade_offer(self):
|
||||||
|
"""Test that trade offers with mixed rarity cards are rejected."""
|
||||||
|
response = self.client.get(
|
||||||
|
reverse("trade_offer_create"),
|
||||||
|
{
|
||||||
|
"initiated_by": self.friend_code.pk,
|
||||||
|
"have_cards": [f"{self.common_card.pk}:1"],
|
||||||
|
"want_cards": [f"{self.crown_card.pk}:1"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertFalse(
|
||||||
|
TradeOffer.objects.filter(initiated_by=self.friend_code).exists()
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_duplicate_card_entries(self):
|
||||||
|
"""Test handling of duplicate card entries in trade offers."""
|
||||||
|
response = self.client.get(
|
||||||
|
reverse("trade_offer_create"),
|
||||||
|
{
|
||||||
|
"initiated_by": self.friend_code.pk,
|
||||||
|
"have_cards": [
|
||||||
|
f"{self.common_card.pk}:1",
|
||||||
|
f"{self.common_card.pk}:1"
|
||||||
|
],
|
||||||
|
"want_cards": [f"{self.common_card.pk}:1"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertFalse(
|
||||||
|
TradeOffer.objects.filter(initiated_by=self.friend_code).exists()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TradeSearchTests(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
User = get_user_model()
|
||||||
|
self.user = User.objects.create_user(
|
||||||
|
username="searchuser", email="search@example.com", password="password"
|
||||||
|
)
|
||||||
|
self.friend_code = FriendCode.objects.create(
|
||||||
|
friend_code="7777-8888-9999-0000", in_game_name="SearchUser", user=self.user
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create test cards with proper rarity levels
|
||||||
|
self.card1 = Card.objects.create(
|
||||||
|
name="SearchCard1", cardset="sc1", cardnum=1, style="default",
|
||||||
|
rarity_icon=RARITY_MAPPING[4], rarity_level=4
|
||||||
|
)
|
||||||
|
self.card2 = Card.objects.create(
|
||||||
|
name="SearchCard2", cardset="sc1", cardnum=2, style="default",
|
||||||
|
rarity_icon=RARITY_MAPPING[4], rarity_level=4
|
||||||
|
)
|
||||||
|
self.card3 = Card.objects.create(
|
||||||
|
name="SearchCard3", cardset="sc1", cardnum=3, style="default",
|
||||||
|
rarity_icon=RARITY_MAPPING[4], rarity_level=4
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create some trade offers
|
||||||
|
self.trade_offer1 = TradeOffer.objects.create(initiated_by=self.friend_code)
|
||||||
|
TradeOfferHaveCard.objects.create(
|
||||||
|
trade_offer=self.trade_offer1, card=self.card1, quantity=2
|
||||||
|
)
|
||||||
|
TradeOfferWantCard.objects.create(
|
||||||
|
trade_offer=self.trade_offer1, card=self.card2, quantity=1
|
||||||
|
)
|
||||||
|
|
||||||
|
self.trade_offer2 = TradeOffer.objects.create(initiated_by=self.friend_code)
|
||||||
|
TradeOfferHaveCard.objects.create(
|
||||||
|
trade_offer=self.trade_offer2, card=self.card2, quantity=1
|
||||||
|
)
|
||||||
|
TradeOfferWantCard.objects.create(
|
||||||
|
trade_offer=self.trade_offer2, card=self.card3, quantity=1
|
||||||
|
)
|
||||||
|
|
||||||
|
self.client = Client()
|
||||||
|
|
||||||
|
def test_search_by_have_cards(self):
|
||||||
|
"""Test searching for trade offers by cards the user has doesn't show offers initiated by the user."""
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("trade_offer_search"),
|
||||||
|
{
|
||||||
|
"have_cards": [f"{self.card2.pk}:1"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertNotContains(response, self.trade_offer1.initiated_by.in_game_name)
|
||||||
|
self.assertNotContains(response, self.trade_offer2.initiated_by.in_game_name)
|
||||||
|
|
||||||
|
def test_search_by_want_cards(self):
|
||||||
|
"""Test searching for trade offers by cards the user wants doesn't show offers initiated by the user."""
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("trade_offer_search"),
|
||||||
|
{
|
||||||
|
"want_cards": [f"{self.card1.pk}:1"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertNotContains(response, self.trade_offer1.initiated_by.in_game_name)
|
||||||
|
self.assertNotContains(response, self.trade_offer2.initiated_by.in_game_name)
|
||||||
|
|
||||||
|
def test_search_with_invalid_card_id(self):
|
||||||
|
"""Test search behavior with invalid card IDs."""
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("trade_offer_search"),
|
||||||
|
{
|
||||||
|
"have_cards": ["999999:1"], # Non-existent card ID
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertNotContains(response, self.trade_offer1.initiated_by.in_game_name)
|
||||||
|
self.assertNotContains(response, self.trade_offer2.initiated_by.in_game_name)
|
||||||
|
|
||||||
|
def test_search_closed_trades(self):
|
||||||
|
"""Test that closed trades don't appear in search results."""
|
||||||
|
self.trade_offer1.is_closed = True
|
||||||
|
self.trade_offer1.save()
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("trade_offer_search"),
|
||||||
|
{
|
||||||
|
"have_cards": [f"{self.card2.pk}:1"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertNotContains(response, self.trade_offer1.initiated_by.in_game_name)
|
||||||
|
|
||||||
|
|
||||||
|
class TradeAcceptanceComplexTests(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
User = get_user_model()
|
||||||
|
self.initiator = User.objects.create_user(
|
||||||
|
username="initiator", email="init@example.com", password="password"
|
||||||
|
)
|
||||||
|
self.acceptor = User.objects.create_user(
|
||||||
|
username="acceptor", email="accept@example.com", password="password"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.initiator_fc = FriendCode.objects.create(
|
||||||
|
friend_code="1234-5678-9012-3456", in_game_name="InitUser", user=self.initiator
|
||||||
|
)
|
||||||
|
self.acceptor_fc = FriendCode.objects.create(
|
||||||
|
friend_code="6543-2109-8765-4321", in_game_name="AcceptUser", user=self.acceptor
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create test cards with proper rarity levels
|
||||||
|
self.card1 = Card.objects.create(
|
||||||
|
name="ComplexCard1", cardset="cx1", cardnum=1, style="default",
|
||||||
|
rarity_icon=RARITY_MAPPING[6], rarity_level=6
|
||||||
|
)
|
||||||
|
self.card2 = Card.objects.create(
|
||||||
|
name="ComplexCard2", cardset="cx1", cardnum=2, style="default",
|
||||||
|
rarity_icon=RARITY_MAPPING[6], rarity_level=6
|
||||||
|
)
|
||||||
|
self.card3 = Card.objects.create(
|
||||||
|
name="ComplexCard3", cardset="cx1", cardnum=3, style="default",
|
||||||
|
rarity_icon=RARITY_MAPPING[6], rarity_level=6
|
||||||
|
)
|
||||||
|
self.card4 = Card.objects.create(
|
||||||
|
name="ComplexCard4", cardset="cx1", cardnum=4, style="default",
|
||||||
|
rarity_icon=RARITY_MAPPING[6], rarity_level=6
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create a trade offer with multiple quantities
|
||||||
|
self.trade_offer = TradeOffer.objects.create(initiated_by=self.initiator_fc)
|
||||||
|
TradeOfferHaveCard.objects.create(
|
||||||
|
trade_offer=self.trade_offer, card=self.card1, quantity=3
|
||||||
|
)
|
||||||
|
TradeOfferHaveCard.objects.create(
|
||||||
|
trade_offer=self.trade_offer, card=self.card3, quantity=1
|
||||||
|
)
|
||||||
|
TradeOfferWantCard.objects.create(
|
||||||
|
trade_offer=self.trade_offer, card=self.card2, quantity=3
|
||||||
|
)
|
||||||
|
TradeOfferWantCard.objects.create(
|
||||||
|
trade_offer=self.trade_offer, card=self.card4, quantity=1
|
||||||
|
)
|
||||||
|
|
||||||
|
self.client = Client()
|
||||||
|
|
||||||
|
def test_multiple_acceptances_quantity_limit(self):
|
||||||
|
"""Test that multiple acceptances cannot exceed the offer's quantity limit."""
|
||||||
|
self.client.login(username="acceptor", password="password")
|
||||||
|
|
||||||
|
# Create first acceptance
|
||||||
|
response1 = self.client.post(
|
||||||
|
reverse("trade_acceptance_create", kwargs={"offer_pk": self.trade_offer.pk}),
|
||||||
|
{
|
||||||
|
"accepted_by": self.acceptor_fc.pk,
|
||||||
|
"requested_card": self.card1.pk,
|
||||||
|
"offered_card": self.card2.pk,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.assertEqual(response1.status_code, 302) # Successful creation
|
||||||
|
|
||||||
|
# Create second acceptance
|
||||||
|
response2 = self.client.post(
|
||||||
|
reverse("trade_acceptance_create", kwargs={"offer_pk": self.trade_offer.pk}),
|
||||||
|
{
|
||||||
|
"accepted_by": self.acceptor_fc.pk,
|
||||||
|
"requested_card": self.card1.pk,
|
||||||
|
"offered_card": self.card2.pk,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.assertEqual(response2.status_code, 302) # Successful creation
|
||||||
|
|
||||||
|
# Try to create a fourth acceptance (should fail as only 3 are allowed)
|
||||||
|
response3 = self.client.post(
|
||||||
|
reverse("trade_acceptance_create", kwargs={"offer_pk": self.trade_offer.pk}),
|
||||||
|
{
|
||||||
|
"accepted_by": self.acceptor_fc.pk,
|
||||||
|
"requested_card": self.card1.pk,
|
||||||
|
"offered_card": self.card2.pk,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.assertEqual(response3.status_code, 302) # Successful creation
|
||||||
|
|
||||||
|
response4 = self.client.post(
|
||||||
|
reverse("trade_acceptance_create", kwargs={"offer_pk": self.trade_offer.pk}),
|
||||||
|
{
|
||||||
|
"accepted_by": self.acceptor_fc.pk,
|
||||||
|
"requested_card": self.card1.pk,
|
||||||
|
"offered_card": self.card2.pk,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.assertEqual(response4.status_code, 200) # Should fail
|
||||||
|
self.assertEqual(
|
||||||
|
self.trade_offer.acceptances.count(), 3,
|
||||||
|
"Should not allow more acceptances than the quantity limit"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_complex_state_transitions(self):
|
||||||
|
"""Test complex state transition scenarios."""
|
||||||
|
self.client.login(username="acceptor", password="password")
|
||||||
|
|
||||||
|
# Create an acceptance
|
||||||
|
acceptance = TradeAcceptance.objects.create(
|
||||||
|
trade_offer=self.trade_offer,
|
||||||
|
accepted_by=self.acceptor_fc,
|
||||||
|
requested_card=self.card1,
|
||||||
|
offered_card=self.card2,
|
||||||
|
state=TradeAcceptance.AcceptanceState.ACCEPTED,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test invalid state transition sequence
|
||||||
|
invalid_transitions = [
|
||||||
|
TradeAcceptance.AcceptanceState.THANKED_BY_ACCEPTOR, # Can't thank before sending
|
||||||
|
TradeAcceptance.AcceptanceState.RECEIVED, # Can't receive before sending
|
||||||
|
TradeAcceptance.AcceptanceState.THANKED_BY_BOTH, # Can't thank by both directly
|
||||||
|
]
|
||||||
|
|
||||||
|
for invalid_state in invalid_transitions:
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("trade_acceptance_update", kwargs={"pk": acceptance.pk}),
|
||||||
|
{"state": invalid_state}
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 200) # Should stay on form
|
||||||
|
acceptance.refresh_from_db()
|
||||||
|
self.assertEqual(
|
||||||
|
acceptance.state,
|
||||||
|
TradeAcceptance.AcceptanceState.ACCEPTED,
|
||||||
|
f"Invalid transition to {invalid_state} should not be allowed"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test valid state transition sequence
|
||||||
|
valid_transitions = [
|
||||||
|
(self.initiator, TradeAcceptance.AcceptanceState.SENT),
|
||||||
|
(self.acceptor, TradeAcceptance.AcceptanceState.RECEIVED),
|
||||||
|
(self.initiator, TradeAcceptance.AcceptanceState.THANKED_BY_INITIATOR),
|
||||||
|
(self.acceptor, TradeAcceptance.AcceptanceState.THANKED_BY_BOTH),
|
||||||
|
]
|
||||||
|
|
||||||
|
for user, state in valid_transitions:
|
||||||
|
self.client.login(username=user.username, password="password")
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("trade_acceptance_update", kwargs={"pk": acceptance.pk}),
|
||||||
|
{"state": state}
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 302) # Should redirect on success
|
||||||
|
acceptance.refresh_from_db()
|
||||||
|
self.assertEqual(
|
||||||
|
acceptance.state,
|
||||||
|
state,
|
||||||
|
f"Valid transition to {state} should be allowed"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ from trades.templatetags.trade_offer_tags import render_trade_offer
|
||||||
from django.template import RequestContext
|
from django.template import RequestContext
|
||||||
from playwright.sync_api import sync_playwright
|
from playwright.sync_api import sync_playwright
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from .mixins import FriendCodeRequiredMixin
|
||||||
|
|
||||||
class TradeOfferCreateView(LoginRequiredMixin, CreateView):
|
class TradeOfferCreateView(LoginRequiredMixin, CreateView):
|
||||||
http_method_names = ['get'] # restricts this view to GET only
|
http_method_names = ['get'] # restricts this view to GET only
|
||||||
|
|
@ -108,15 +109,10 @@ class TradeOfferAllListView(ListView):
|
||||||
)
|
)
|
||||||
return super().render_to_response(context, **response_kwargs)
|
return super().render_to_response(context, **response_kwargs)
|
||||||
|
|
||||||
class TradeOfferMyListView(LoginRequiredMixin, ListView):
|
class TradeOfferMyListView(LoginRequiredMixin, FriendCodeRequiredMixin, ListView):
|
||||||
model = TradeOffer # Fallback model; our context data holds separate filtered querysets.
|
model = TradeOffer
|
||||||
template_name = "trades/trade_offer_my_list.html"
|
template_name = "trades/trade_offer_my_list.html"
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
|
||||||
if request.user.is_authenticated and not request.user.friend_codes.exists():
|
|
||||||
raise PermissionDenied("No friend codes available for your account.")
|
|
||||||
return super().dispatch(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def get_selected_friend_code(self):
|
def get_selected_friend_code(self):
|
||||||
friend_codes = self.request.user.friend_codes.all()
|
friend_codes = self.request.user.friend_codes.all()
|
||||||
friend_code_param = self.request.GET.get("friend_code")
|
friend_code_param = self.request.GET.get("friend_code")
|
||||||
|
|
@ -127,10 +123,8 @@ class TradeOfferMyListView(LoginRequiredMixin, ListView):
|
||||||
selected_friend_code = self.request.user.default_friend_code or friend_codes.first()
|
selected_friend_code = self.request.user.default_friend_code or friend_codes.first()
|
||||||
else:
|
else:
|
||||||
selected_friend_code = self.request.user.default_friend_code or friend_codes.first()
|
selected_friend_code = self.request.user.default_friend_code or friend_codes.first()
|
||||||
|
|
||||||
if not selected_friend_code:
|
if not selected_friend_code:
|
||||||
raise PermissionDenied("You do not have an active friend code associated with your account.")
|
raise PermissionDenied("You do not have an active friend code associated with your account.")
|
||||||
|
|
||||||
return selected_friend_code
|
return selected_friend_code
|
||||||
|
|
||||||
def get_show_closed(self):
|
def get_show_closed(self):
|
||||||
|
|
@ -224,15 +218,13 @@ class TradeOfferMyListView(LoginRequiredMixin, ListView):
|
||||||
return render(self.request, "trades/_trade_offer_list_paginated.html", {"offers": offers})
|
return render(self.request, "trades/_trade_offer_list_paginated.html", {"offers": offers})
|
||||||
return super().render_to_response(context, **response_kwargs)
|
return super().render_to_response(context, **response_kwargs)
|
||||||
|
|
||||||
class TradeOfferDeleteView(LoginRequiredMixin, DeleteView):
|
class TradeOfferDeleteView(LoginRequiredMixin, FriendCodeRequiredMixin, DeleteView):
|
||||||
model = TradeOffer
|
model = TradeOffer
|
||||||
success_url = reverse_lazy("trade_offer_list")
|
success_url = reverse_lazy("trade_offer_list")
|
||||||
template_name = "trades/trade_offer_delete.html"
|
template_name = "trades/trade_offer_delete.html"
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
# Retrieve the object normally
|
|
||||||
self.object = super().get_object()
|
self.object = super().get_object()
|
||||||
# Perform the permission check here
|
|
||||||
if self.object.initiated_by_id not in request.user.friend_codes.values_list("id", flat=True):
|
if self.object.initiated_by_id not in request.user.friend_codes.values_list("id", flat=True):
|
||||||
raise PermissionDenied("You are not authorized to delete or close this trade offer.")
|
raise PermissionDenied("You are not authorized to delete or close this trade offer.")
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
@ -339,7 +331,10 @@ class TradeOfferSearchView(ListView):
|
||||||
|
|
||||||
qs = TradeOffer.objects.filter(
|
qs = TradeOffer.objects.filter(
|
||||||
is_closed=False,
|
is_closed=False,
|
||||||
).exclude(initiated_by__in=self.request.user.friend_codes.all())
|
)
|
||||||
|
|
||||||
|
if self.request.user.is_authenticated:
|
||||||
|
qs = qs.exclude(initiated_by__in=self.request.user.friend_codes.all())
|
||||||
|
|
||||||
# Chain filters for offered selections (i.e. the user "has" cards).
|
# Chain filters for offered selections (i.e. the user "has" cards).
|
||||||
if have_selections:
|
if have_selections:
|
||||||
|
|
@ -498,7 +493,7 @@ class TradeOfferDetailView(DetailView):
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
class TradeAcceptanceCreateView(LoginRequiredMixin, CreateView):
|
class TradeAcceptanceCreateView(LoginRequiredMixin, FriendCodeRequiredMixin, CreateView):
|
||||||
"""
|
"""
|
||||||
View to create a new TradeAcceptance.
|
View to create a new TradeAcceptance.
|
||||||
The URL should provide 'offer_pk' so that the proper TradeOffer can be identified.
|
The URL should provide 'offer_pk' so that the proper TradeOffer can be identified.
|
||||||
|
|
@ -509,19 +504,17 @@ class TradeAcceptanceCreateView(LoginRequiredMixin, CreateView):
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
self.trade_offer = self.get_trade_offer()
|
self.trade_offer = self.get_trade_offer()
|
||||||
if self.trade_offer.initiated_by_id in request.user.friend_codes.values_list("id", flat=True) or self.trade_offer.is_closed:
|
|
||||||
raise PermissionDenied("You cannot accept this trade offer.")
|
|
||||||
if not request.user.friend_codes.exists():
|
|
||||||
raise PermissionDenied("No friend codes available for your account.")
|
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_trade_offer(self):
|
def get_trade_offer(self):
|
||||||
return (
|
return TradeOffer.objects.get(pk=self.kwargs['offer_pk'])
|
||||||
TradeOffer.objects.get(pk=self.kwargs['offer_pk'])
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_form_kwargs(self):
|
def get_form_kwargs(self):
|
||||||
kwargs = super().get_form_kwargs()
|
kwargs = super().get_form_kwargs()
|
||||||
|
if (self.trade_offer.initiated_by_id in
|
||||||
|
self.request.user.friend_codes.values_list("id", flat=True) or
|
||||||
|
self.trade_offer.is_closed):
|
||||||
|
raise PermissionDenied("You cannot accept this trade offer.")
|
||||||
kwargs['trade_offer'] = self.trade_offer
|
kwargs['trade_offer'] = self.trade_offer
|
||||||
kwargs['friend_codes'] = self.request.user.friend_codes.all()
|
kwargs['friend_codes'] = self.request.user.friend_codes.all()
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
@ -561,7 +554,7 @@ class TradeAcceptanceCreateView(LoginRequiredMixin, CreateView):
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return reverse_lazy("trade_offer_detail", kwargs={"pk": self.trade_offer.pk})
|
return reverse_lazy("trade_offer_detail", kwargs={"pk": self.trade_offer.pk})
|
||||||
|
|
||||||
class TradeAcceptanceUpdateView(LoginRequiredMixin, UpdateView):
|
class TradeAcceptanceUpdateView(LoginRequiredMixin, FriendCodeRequiredMixin, UpdateView):
|
||||||
"""
|
"""
|
||||||
View to update the state of an existing TradeAcceptance.
|
View to update the state of an existing TradeAcceptance.
|
||||||
The allowed state transitions are provided via the form.
|
The allowed state transitions are provided via the form.
|
||||||
|
|
@ -572,10 +565,9 @@ class TradeAcceptanceUpdateView(LoginRequiredMixin, UpdateView):
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
if not request.user.friend_codes.exists():
|
|
||||||
raise PermissionDenied("No friend codes available for your account.")
|
|
||||||
friend_codes = request.user.friend_codes.values_list("id", flat=True)
|
friend_codes = request.user.friend_codes.values_list("id", flat=True)
|
||||||
if self.object.accepted_by_id not in friend_codes and self.object.trade_offer.initiated_by_id not in friend_codes:
|
if (self.object.accepted_by_id not in friend_codes and
|
||||||
|
self.object.trade_offer.initiated_by_id not in friend_codes):
|
||||||
raise PermissionDenied("You are not authorized to update this acceptance.")
|
raise PermissionDenied("You are not authorized to update this acceptance.")
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue