Last bit of major changes

Closes #1
Closes #5
Closes #6
Closes #8
Closes #9
Closes #10
This commit is contained in:
2026-01-26 04:11:38 -06:00
parent 1cd87156bd
commit 739d136209
24 changed files with 1157 additions and 410 deletions

View File

@@ -0,0 +1,90 @@
from django.test import TestCase, Client
from django.urls import reverse
from users.models import User, Address, PaymentMethod
from store.models import Seller, CardListing, Card, Set, Game, Cart, Order, OrderItem
from django.utils.text import slugify
class CheckoutFlowTest(TestCase):
def setUp(self):
self.client = Client()
# Create Buyer
self.user = User.objects.create_user(username='buyer', password='password')
self.client.login(username='buyer', password='password')
# Create Address
self.address = Address.objects.create(
user=self.user,
name='Buyer Name',
street='123 Initial St',
city='New York',
state='NY',
zip_code='10001',
address_type='shipping'
)
# Create Payment Method
self.pm = PaymentMethod.objects.create(
user=self.user,
brand='Visa',
last4='4242', # Mock
exp_month=12,
exp_year=2030,
billing_address=self.address
)
self.pm.card_number = '4242424242424242' # Encrypts
self.pm.save()
# Create Seller and items
self.seller_user = User.objects.create_user(username='seller', password='password')
self.seller = Seller.objects.create(
user=self.seller_user,
store_name='Test Store',
slug='test-store',
minimum_order_amount=200,
shipping_cost=5
)
self.game = Game.objects.create(name='Magic', slug='magic')
self.set = Set.objects.create(game=self.game, name='Alpha')
self.card = Card.objects.create(set=self.set, name='Black Lotus')
self.listing = CardListing.objects.create(
card=self.card,
seller=self.seller,
price=100.00,
quantity=1,
status='listed'
)
def test_checkout_process(self):
# Add to cart (requires manual cart creation or view call, let's create cart manually for speed)
from users.models import Buyer
buyer, _ = Buyer.objects.get_or_create(user=self.user)
cart = Cart.objects.create(buyer=buyer)
from store.models import CartItem
CartItem.objects.create(cart=cart, listing=self.listing, quantity=1)
# Get checkout page
response = self.client.get(reverse('store:checkout'))
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'Checkout')
self.assertContains(response, 'Total: $100.00')
# Post checkout
data = {
'shipping_address': self.address.id,
'payment_method': self.pm.id
}
response = self.client.post(reverse('store:checkout'), data)
self.assertEqual(response.status_code, 302) # Redirect to vault
# Verify Order
order = Order.objects.filter(buyer=buyer).first()
self.assertIsNotNone(order)
self.assertEqual(order.status, 'paid')
self.assertIn('123 Initial St', order.shipping_address)
self.assertEqual(order.total_price, 105.00) # 100 + 5 shipping
# Verify Stock
self.listing.refresh_from_db()
self.assertEqual(self.listing.quantity, 0)

View File

@@ -0,0 +1,316 @@
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)