from django.test import TestCase, Client from django.urls import reverse from django.contrib.auth import get_user_model from django.core.files.uploadedfile import SimpleUploadedFile from django.core.files.uploadedfile import SimpleUploadedFile from .models import Seller, Game, Set, Card, CardListing, Order from decimal import Decimal User = get_user_model() class CardListingTests(TestCase): def setUp(self): # Create User and Seller self.user = User.objects.create_user(username='seller', password='password') self.seller = Seller.objects.create( user=self.user, store_name='Test Store', slug='test-store' ) # Create Game, Set, Card self.game = Game.objects.create(name='Test Game', slug='test-game') self.set = Set.objects.create(game=self.game, name='Test Set') self.card = Card.objects.create(set=self.set, name='Test Card') # Create Listing self.listing = CardListing.objects.create( card=self.card, seller=self.seller, price=10.00, quantity=1, condition='NM' ) self.client = Client() self.client.force_login(self.user) def test_edit_card_listing_image_upload(self): url = reverse('store:edit_card_listing', args=[self.listing.uuid]) # Create a small image file image_content = b'\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x80\x00\x00\x00\x00\x00\xff\xff\xff\x21\xf9\x04\x01\x00\x00\x00\x00\x2c\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x01\x44\x00\x3b' image = SimpleUploadedFile("test_image.gif", image_content, content_type="image/gif") data = { 'condition': 'LP', 'price': '15.00', 'quantity': 2, 'status': 'listed', 'image': image } response = self.client.post(url, data, format='multipart') # Check redirect self.assertRedirects(response, reverse('store:manage_listings')) # Reload listing self.listing.refresh_from_db() # Check updates self.assertEqual(self.listing.condition, 'LP') self.assertEqual(self.listing.price, 15.00) self.assertEqual(self.listing.quantity, 2) # Check image self.assertTrue(self.listing.image) self.assertTrue(self.listing.image.name.endswith('test_image.gif')) class SellerDashboardTests(TestCase): def setUp(self): # Create User and Seller self.user = User.objects.create_user(username='dashboard_seller', password='password') self.seller = Seller.objects.create( user=self.user, store_name='Dashboard Store', slug='dashboard-store' ) self.buyer_user = User.objects.create_user(username='buyer', password='password') from users.models import Buyer self.buyer = Buyer.objects.create(user=self.buyer_user) # Create Game, Set, Card self.game = Game.objects.create(name='Dashboard Game', slug='dashboard-game') self.set = Set.objects.create(game=self.game, name='Dashboard Set') self.card = Card.objects.create(set=self.set, name='Dashboard Card') # Create Listing self.listing = CardListing.objects.create( card=self.card, seller=self.seller, price=10.00, quantity=10, condition='NM' ) # Create Order & OrderItem from .models import Order, OrderItem self.order = Order.objects.create( buyer=self.buyer, status='paid', total_price=20.00 ) OrderItem.objects.create( order=self.order, listing=self.listing, price_at_purchase=10.00, quantity=2 ) self.client = Client() self.client.force_login(self.user) def test_dashboard_context_data(self): url = reverse('store:seller_dashboard') response = self.client.get(url) self.assertEqual(response.status_code, 200) # Check for new context keys self.assertIn('condition_labels', response.context) self.assertIn('condition_data', response.context) self.assertIn('set_labels', response.context) self.assertIn('set_data', response.context) self.assertIn('game_labels', response.context) self.assertIn('game_data', response.context) self.assertIn('all_games', response.context) # Check data correctness (we sold 2 NM items) import json cond_data = json.loads(response.context['condition_data']) cond_labels = json.loads(response.context['condition_labels']) self.assertIn('Near Mint', cond_labels) idx = cond_labels.index('Near Mint') self.assertEqual(cond_data[idx], 2) def test_dashboard_game_filter(self): from .models import OrderItem url = reverse('store:seller_dashboard') # Create another game/sale game2 = Game.objects.create(name='Other Game', slug='other-game') set2 = Set.objects.create(game=game2, name='Other Set') card2 = Card.objects.create(set=set2, name='Other Card') listing2 = CardListing.objects.create(card=card2, seller=self.seller, price=5, quantity=5, condition='LP') OrderItem.objects.create( order=self.order, listing=listing2, price_at_purchase=5.00, quantity=1 ) # 1. No Filter - Should see both response = self.client.get(url) import json game_labels = json.loads(response.context['game_labels']) self.assertIn('Dashboard Game', game_labels) self.assertIn('Other Game', game_labels) # 2. Filter by Dashboard Game response = self.client.get(url, {'game': 'Dashboard Game'}) game_labels = json.loads(response.context['game_labels']) self.assertIn('Dashboard Game', game_labels) self.assertNotIn('Other Game', game_labels) # Check condition data also filtered cond_labels = json.loads(response.context['condition_labels']) # Dashboard Game items were NM. Other Game items were LP. self.assertIn('Near Mint', cond_labels) self.assertNotIn('Lightly Played', cond_labels) class AdminRevenueTests(TestCase): def setUp(self): self.client = Client() self.staff_user = User.objects.create_user(username='staff', password='password', is_staff=True) self.seller_user = User.objects.create_user(username='seller2', password='password') self.seller = Seller.objects.create( user=self.seller_user, store_name='Revenue Store', slug='revenue-store', tax_id='123-45-6789', payout_details='Bank Acct 123' ) # Create Orders from users.models import Buyer buyer_user = User.objects.create_user(username='buyer2', password='password') buyer = Buyer.objects.create(user=buyer_user) # Order 1: $100 -> Fee: 5 + 0.70 = 5.70 order1 = Order.objects.create(buyer=buyer, status='paid', total_price=Decimal('100.00'), seller=self.seller) # Order 2: $1000 -> Fee: 50 + 0.70 = 50.70 -> Capped at 25.00 order2 = Order.objects.create(buyer=buyer, status='shipped', total_price=Decimal('1000.00'), seller=self.seller) # Order 3: Pending (should be ignored) order3 = Order.objects.create(buyer=buyer, status='pending', total_price=Decimal('50.00'), seller=self.seller) def test_encryption(self): # Refresh from db seller = Seller.objects.get(pk=self.seller.pk) # Verify decrypted values match self.assertEqual(seller.tax_id, '123-45-6789') self.assertEqual(seller.payout_details, 'Bank Acct 123') # Verify db values are encrypted (not plaintext and are bytes) self.assertIsInstance(seller.tax_id_encrypted, bytes) self.assertNotEqual(seller.tax_id_encrypted, b'123-45-6789') # Ensure it's not just the string encoded self.assertNotEqual(seller.tax_id_encrypted, '123-45-6789'.encode('utf-8')) def test_fee_calculation(self): from .utils import calculate_platform_fee # 5% + 0.70 self.assertEqual(calculate_platform_fee(Decimal('10.00')), Decimal('1.20')) # 0.50 + 0.70 self.assertEqual(calculate_platform_fee(Decimal('100.00')), Decimal('5.70')) # 5.00 + 0.70 # Cap at 25 # Threshold: (25 - 0.70) / 0.05 = 486.00 self.assertEqual(calculate_platform_fee(Decimal('486.00')), Decimal('25.00')) self.assertEqual(calculate_platform_fee(Decimal('1000.00')), Decimal('25.00')) def test_dashboard_view(self): self.client.force_login(self.staff_user) response = self.client.get(reverse('store:admin_revenue_dashboard')) self.assertEqual(response.status_code, 200) # Check context total_rev = response.context['total_platform_revenue'] # Order 1 (5.70) + Order 2 (25.00) = 30.70 self.assertEqual(total_rev, Decimal('30.70')) seller_data = response.context['seller_data'] self.assertEqual(len(seller_data), 1) self.assertEqual(seller_data[0]['total_revenue'], Decimal('1100.00')) self.assertEqual(seller_data[0]['platform_fees'], Decimal('30.70')) class CardListStockTests(TestCase): def setUp(self): # Create Data self.user = User.objects.create_user(username='stock_seller', password='password') self.seller = Seller.objects.create( user=self.user, store_name='Stock Store', slug='stock-store' ) self.game = Game.objects.create(name='Stock Game', slug='stock-game') self.set = Set.objects.create(game=self.game, name='Stock Set') self.card = Card.objects.create(set=self.set, name='Stock Card') def test_stock_aggregation(self): # 1. No listings url = reverse('store:card_list') response = self.client.get(url, {'hide_out_of_stock': 'off'}) # Expect card in context self.assertEqual(len(response.context['page_obj']), 1) self.assertEqual(response.context['page_obj'][0].total_quantity, 0) # 2. Add listings CardListing.objects.create(card=self.card, seller=self.seller, price=10, quantity=5, status='listed') CardListing.objects.create(card=self.card, seller=self.seller, price=10, quantity=3, status='listed') # Add a sold listing (should be ignored) CardListing.objects.create(card=self.card, seller=self.seller, price=10, quantity=2, status='sold') response = self.client.get(url, {'hide_out_of_stock': 'off'}) # Should be 5 + 3 = 8 self.assertEqual(response.context['page_obj'][0].total_quantity, 8) class SellerProfileRestrictionTests(TestCase): def setUp(self): self.user = User.objects.create_user(username='restricted_seller', password='password') self.seller = Seller.objects.create( user=self.user, store_name='Restricted Store', slug='restricted-store' # tax_id and payout_details are initially empty ) self.client = Client() self.client.force_login(self.user) def test_add_listing_incomplete_profile(self): url = reverse('store:add_card_listing') response = self.client.get(url) # Should redirect to edit profile self.assertRedirects(response, reverse('store:edit_seller_profile')) # Verify message (this requires session support in test client which is default) messages = list(response.wsgi_request._messages) self.assertEqual(len(messages), 1) self.assertIn("must complete your seller profile", str(messages[0])) def test_add_bounty_incomplete_profile(self): # Ensure feature flag is on if needed, or assume True based on settings # The view checks FEATURE_BOUNTY_BOARD, so we might need override_settings if it defaults to False # But settings.DEBUG is True in current env, so it should be on. url = reverse('store:bounty_create') response = self.client.get(url) self.assertRedirects(response, reverse('store:edit_seller_profile')) def test_complete_profile_allows_access(self): # Update profile self.seller.tax_id = '123' self.seller.payout_details = 'Bank' self.seller.save() url = reverse('store:add_card_listing') response = self.client.get(url) self.assertEqual(response.status_code, 200) url = reverse('store:bounty_create') response = self.client.get(url) self.assertEqual(response.status_code, 200)