2026-05-20 10:50:30 +00:00

283 lines
12 KiB
Python

from unittest.mock import patch
from django.contrib.auth import get_user_model
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import TestCase, override_settings
from django.urls import reverse
from orders.models import Order, OrderItem
from products.models import Product
User = get_user_model()
SAMPLE_GIF = bytes.fromhex(
'47494638396101000100800000000000ffffff21f90401000000002c00000000010001000002024401003b'
)
class OrderPaymentFlowTests(TestCase):
def setUp(self):
self.user = User.objects.create_user(username='alice', password='password123', email='alice@example.com')
self.client.force_login(self.user)
image = SimpleUploadedFile('product.gif', SAMPLE_GIF, content_type='image/gif')
self.product = Product.objects.create(
name='Wireless Mouse',
description='A test product for order payments.',
price='1250.00',
listing_type='sale',
image=image,
category='Electronics',
stock=8,
)
self.order = Order.objects.create(
user=self.user,
total_price='1250.00',
full_name='Alice Example',
phone='+9779800000000',
address='Kathmandu',
)
OrderItem.objects.create(order=self.order, product=self.product, quantity=1, price='1250.00')
def test_payment_page_does_not_fake_pay_order(self):
response = self.client.get(reverse('payment', args=[self.order.id]))
self.assertEqual(response.status_code, 200)
self.order.refresh_from_db()
self.assertEqual(self.order.payment_status, 'Unpaid')
self.assertEqual(self.order.status, 'Pending')
@override_settings(
PAYMENT_CURRENCY='NPR',
ESEWA_SANDBOX=True,
ESEWA_PRODUCT_CODE='EPAYTEST',
ESEWA_SECRET_KEY='secret-key',
ESEWA_FORM_URL='https://esewa.test/form',
ESEWA_STATUS_URL='https://esewa.test/status/',
)
def test_posting_esewa_returns_redirect_form_and_sets_pending_state(self):
response = self.client.post(reverse('payment', args=[self.order.id]), {'payment': 'esewa'})
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'order/payment_redirect.html')
self.assertContains(response, 'https://esewa.test/form')
self.order.refresh_from_db()
self.assertEqual(self.order.payment_provider, 'esewa')
self.assertEqual(self.order.payment_method, 'eSewa')
self.assertEqual(self.order.payment_status, 'Pending')
self.assertEqual(self.order.payment_currency, 'NPR')
self.assertTrue(self.order.payment_session_id.startswith(f'ORD-{self.order.id}-'))
self.assertContains(response, self.order.payment_session_id)
@override_settings(PAYMENT_CURRENCY='NPR')
@patch('orders.views.verify_esewa_payment')
def test_esewa_return_marks_order_paid_only_after_verified_status(self, mock_verify_esewa_payment):
self.order.payment_method = 'eSewa'
self.order.payment_provider = 'esewa'
self.order.payment_status = 'Pending'
self.order.payment_session_id = 'ORD-1-ABC123'
self.order.payment_currency = 'NPR'
self.order.save()
mock_verify_esewa_payment.return_value = {
'status': 'Paid',
'reference': 'ESEWA-REF-123',
'session_id': 'ORD-1-ABC123',
'currency': 'NPR',
'message': 'eSewa payment verified successfully.',
}
response = self.client.get(reverse('esewa_return') + f'?order_id={self.order.id}&data=dummy')
self.assertEqual(response.status_code, 302)
self.assertEqual(response['Location'], reverse('success') + f'?order_id={self.order.id}')
self.order.refresh_from_db()
self.assertEqual(self.order.payment_status, 'Paid')
self.assertEqual(self.order.status, 'Paid')
self.assertEqual(self.order.payment_reference, 'ESEWA-REF-123')
self.assertEqual(self.order.payment_provider, 'esewa')
self.assertIsNotNone(self.order.paid_at)
@override_settings(
PAYMENT_CURRENCY='NPR',
KHALTI_SECRET_KEY='test_secret',
KHALTI_INITIATE_URL='https://khalti.test/initiate/',
KHALTI_LOOKUP_URL='https://khalti.test/lookup/',
)
@patch('orders.views.initiate_khalti_payment')
def test_posting_khalti_redirects_to_payment_url(self, mock_initiate_khalti_payment):
mock_initiate_khalti_payment.return_value = {
'payment_url': 'https://pay.khalti.test/session/123',
'session_id': 'pidx_test_123',
'reference': 'ORDER-1-TEST123',
'currency': 'NPR',
}
response = self.client.post(reverse('payment', args=[self.order.id]), {'payment': 'khalti'})
self.assertEqual(response.status_code, 302)
self.assertEqual(response['Location'], 'https://pay.khalti.test/session/123')
self.order.refresh_from_db()
self.assertEqual(self.order.payment_provider, 'khalti')
self.assertEqual(self.order.payment_method, 'Khalti')
self.assertEqual(self.order.payment_status, 'Pending')
self.assertEqual(self.order.payment_session_id, 'pidx_test_123')
self.assertEqual(self.order.payment_reference, 'ORDER-1-TEST123')
@override_settings(PAYMENT_CURRENCY='NPR')
@patch('orders.views.verify_khalti_payment')
def test_khalti_return_marks_order_paid_only_after_verified_lookup(self, mock_verify_khalti_payment):
self.order.payment_method = 'Khalti'
self.order.payment_provider = 'khalti'
self.order.payment_status = 'Pending'
self.order.payment_session_id = 'pidx_test_123'
self.order.payment_reference = 'ORDER-1-TEST123'
self.order.payment_currency = 'NPR'
self.order.save()
mock_verify_khalti_payment.return_value = {
'status': 'Paid',
'reference': 'TXN-456',
'session_id': 'pidx_test_123',
'currency': 'NPR',
'message': 'Khalti payment verified successfully.',
}
response = self.client.get(reverse('khalti_return') + f'?order_id={self.order.id}&pidx=pidx_test_123')
self.assertEqual(response.status_code, 302)
self.assertEqual(response['Location'], reverse('success') + f'?order_id={self.order.id}')
self.order.refresh_from_db()
self.assertEqual(self.order.payment_status, 'Paid')
self.assertEqual(self.order.status, 'Paid')
self.assertEqual(self.order.payment_reference, 'TXN-456')
self.assertEqual(self.order.payment_provider, 'khalti')
self.assertIsNotNone(self.order.paid_at)
def test_cash_on_delivery_does_not_mark_order_as_paid(self):
response = self.client.post(reverse('payment', args=[self.order.id]), {'payment': 'cod'})
self.assertEqual(response.status_code, 302)
self.order.refresh_from_db()
self.assertEqual(self.order.payment_method, 'Cash on Delivery')
self.assertEqual(self.order.payment_status, 'Pending')
self.assertEqual(self.order.status, 'Pending')
class CheckoutLocationFlowTests(TestCase):
def setUp(self):
self.user = User.objects.create_user(username='buyer', password='password123', email='buyer@example.com')
self.user.profile.phone = '+9779811111111'
self.user.profile.location_label = 'Baneshwor, Kathmandu'
self.user.profile.default_address = 'Old Baneshwor Chowk\nKathmandu'
self.user.profile.latitude = '27.693400'
self.user.profile.longitude = '85.335000'
self.user.profile.location_accuracy_m = '20.50'
self.user.profile.save()
image = SimpleUploadedFile('checkout.gif', SAMPLE_GIF, content_type='image/gif')
self.product = Product.objects.create(
name='Smart Watch',
description='Checkout location test product.',
price='2500.00',
listing_type='sale',
image=image,
category='Electronics',
stock=5,
)
self.client.force_login(self.user)
session = self.client.session
session['cart'] = {str(self.product.id): 1}
session.save()
def test_checkout_prefills_saved_profile_delivery_details(self):
response = self.client.get(reverse('checkout'))
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['delivery']['phone'], '+9779811111111')
self.assertEqual(response.context['delivery']['location_label'], 'Baneshwor, Kathmandu')
self.assertEqual(response.context['delivery']['address'], 'Old Baneshwor Chowk\nKathmandu')
self.assertEqual(response.context['delivery']['latitude'], '27.693400')
self.assertEqual(response.context['delivery']['longitude'], '85.335000')
def test_checkout_saves_order_delivery_snapshot_and_updates_profile(self):
response = self.client.post(
reverse('checkout'),
{
'full_name': 'Buyer Example',
'phone': '+9779801234567',
'location_label': 'Lalitpur, Kupondole',
'address': 'Kupondole Height\nLalitpur',
'delivery_notes': 'Call before reaching the gate',
'latitude': '27.685100',
'longitude': '85.316700',
'location_accuracy_m': '12.75',
'save_as_default': 'on',
},
)
order = Order.objects.get(user=self.user)
self.assertEqual(response.status_code, 302)
self.assertEqual(response['Location'], reverse('payment', args=[order.id]))
self.assertEqual(order.phone, '+9779801234567')
self.assertEqual(order.location_label, 'Lalitpur, Kupondole')
self.assertEqual(order.delivery_notes, 'Call before reaching the gate')
self.assertIsNotNone(order.latitude)
self.assertIsNotNone(order.longitude)
self.user.profile.refresh_from_db()
self.assertEqual(self.user.profile.phone, '+9779801234567')
self.assertEqual(self.user.profile.location_label, 'Lalitpur, Kupondole')
self.assertEqual(self.user.profile.default_address, 'Kupondole Height\nLalitpur')
def test_checkout_can_prefill_from_recent_delivery_shortcut(self):
recent_order = Order.objects.create(
user=self.user,
total_price='2500.00',
status='Delivered',
payment_status='Paid',
payment_method='Cash on Delivery',
full_name='Buyer Example',
phone='+9779809999999',
address='Naxal Chowk\nKathmandu',
location_label='Naxal, Kathmandu',
latitude='27.717300',
longitude='85.331900',
location_accuracy_m='11.25',
delivery_notes='Ring the bell once',
)
response = self.client.get(reverse('checkout') + f'?delivery_shortcut=order-{recent_order.id}')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['delivery']['phone'], '+9779809999999')
self.assertEqual(response.context['delivery']['location_label'], 'Naxal, Kathmandu')
self.assertEqual(response.context['delivery']['address'], 'Naxal Chowk\nKathmandu')
self.assertEqual(response.context['delivery']['delivery_notes'], 'Ring the bell once')
self.assertEqual(response.context['delivery']['selected_shortcut_id'], f'order-{recent_order.id}')
self.assertTrue(any(shortcut['selected'] for shortcut in response.context['delivery_shortcuts']))
def test_checkout_save_as_default_clears_stale_profile_gps_when_manual_address_has_no_coordinates(self):
response = self.client.post(
reverse('checkout'),
{
'full_name': 'Buyer Example',
'phone': '+9779801234567',
'location_label': 'Bhaktapur Durbar Area',
'address': 'Taumadhi Square\nBhaktapur',
'delivery_notes': 'Call after arrival',
'save_as_default': 'on',
},
)
order = Order.objects.get(user=self.user)
self.assertEqual(response.status_code, 302)
self.assertEqual(response['Location'], reverse('payment', args=[order.id]))
self.user.profile.refresh_from_db()
self.assertEqual(self.user.profile.default_address, 'Taumadhi Square\nBhaktapur')
self.assertIsNone(self.user.profile.latitude)
self.assertIsNone(self.user.profile.longitude)
self.assertIsNone(self.user.profile.location_accuracy_m)
self.assertIsNone(self.user.profile.location_updated_at)