Add tests

This commit is contained in:
Nathan Chapman 2022-05-05 18:17:53 -06:00
parent 46bc3ff0ff
commit 98b6f5e9c4
12 changed files with 197 additions and 63 deletions

View File

@ -0,0 +1 @@
[{"model": "accounts.address", "pk": 1, "fields": {"first_name": "Nathan", "last_name": "Chapman", "street_address_1": "1504 N 230 E", "street_address_2": "", "city": "North Logan", "state": "UT", "postal_code": "84341"}}, {"model": "accounts.address", "pk": 2, "fields": {"first_name": "Nathan", "last_name": "Chapman", "street_address_1": "1125 W 400 N", "street_address_2": "", "city": "Logan", "state": "UT", "postal_code": "84321"}}, {"model": "accounts.user", "pk": 1, "fields": {"password": "pbkdf2_sha256$320000$VLksxVkUXOtoAEthHFi6DB$xj+81uh6NwPfZFP+7agf2UAJQZN89j5Wt38gUXeJ3z0=", "last_login": "2022-05-03T23:58:18.358Z", "is_superuser": true, "username": "nathanchapman", "first_name": "Nathan", "last_name": "Chapman", "email": "contact@nathanjchapman.com", "is_staff": true, "is_active": true, "date_joined": "2022-04-28T01:24:47.591Z", "default_shipping_address": 1, "default_billing_address": null, "groups": [], "user_permissions": [], "addresses": []}}, {"model": "accounts.user", "pk": 13, "fields": {"password": "pbkdf2_sha256$320000$L6WDkOMJwmkjR9OVsXfsIj$otr4goV5Tz5Hy5l24UkSYcH0L9Y5hDD89GKYD6LGcZo=", "last_login": null, "is_superuser": false, "username": "john", "first_name": "John", "last_name": "Doe", "email": "john@example.com", "is_staff": false, "is_active": true, "date_joined": "2022-05-04T00:00:11Z", "default_shipping_address": null, "default_billing_address": null, "groups": [], "user_permissions": [], "addresses": []}}]

View File

@ -16,6 +16,7 @@ def get_or_create_customer(request, form, shipping_address):
if request.user.is_authenticated: if request.user.is_authenticated:
user = request.user user = request.user
user.addresses.add(address)
if not user.default_shipping_address: if not user.default_shipping_address:
user.default_shipping_address = address user.default_shipping_address = address
user.save() user.save()
@ -35,6 +36,7 @@ def get_or_create_customer(request, form, shipping_address):
if u_created: if u_created:
password = User.objects.make_random_password() password = User.objects.make_random_password()
user.set_password(password) user.set_password(password)
user.addresses.add(address)
user.save() user.save()
EmailAddress.objects.create(user=user, email=user.email, primary=True, verified=False) EmailAddress.objects.create(user=user, email=user.email, primary=True, verified=False)

View File

@ -0,0 +1 @@
[{"model": "core.product", "pk": 1, "fields": {"name": "Ethiopia", "description": "Spicy espresso reminiscent of Northern Italy, on the mild side. Perfect for espresso, and steamed milk drinks. Also, a full-bodied, earthy sweet drip or Americano. Contains organic beans from Indonesia, Africa and America.", "sku": "23468", "price": "13.40", "weight": "16.0:oz", "visible_in_listings": true, "sorting": 4, "created_at": "2022-02-19T20:15:36.292Z", "updated_at": "2022-03-28T17:29:26.300Z"}}, {"model": "core.product", "pk": 2, "fields": {"name": "Sumatra", "description": "Dark heavy-bodied roast with a lingering chocolatey taste. Organic Single origin.", "sku": "89765", "price": "13.40", "weight": "16.0:oz", "visible_in_listings": true, "sorting": 3, "created_at": "2022-02-19T20:15:59.741Z", "updated_at": "2022-03-28T17:29:08.706Z"}}, {"model": "core.product", "pk": 3, "fields": {"name": "Pantomime", "description": "Very Dark French Roast\r\nOur darkest drip. A blend of five different beans roasted two ways. Organic Africa, Indonesia, and South and Central America.", "sku": "565656", "price": "13.40", "weight": "16.0:oz", "visible_in_listings": true, "sorting": 1, "created_at": "2022-02-23T17:59:00.711Z", "updated_at": "2022-03-28T17:28:58.670Z"}}, {"model": "core.product", "pk": 4, "fields": {"name": "Decaf", "description": "French Roast (Water Processed)\r\n\r\n“I cant believe its decaf!”. The best-tasting Swiss water process decaf we have developed over the past 30 years, for an unbelievable espresso or drip coffee. Organic Africa, Indonesia and South and Central America.", "sku": "566565", "price": "13.40", "weight": "16.0:oz", "visible_in_listings": true, "sorting": 2, "created_at": "2022-02-23T17:59:32.099Z", "updated_at": "2022-03-28T17:28:45.512Z"}}, {"model": "core.product", "pk": 5, "fields": {"name": "Moka Java Blend", "description": "Dark Roast\r\n\r\nA classic Moka Java style blend dark roasted with organic beans for a perfect body and sweetness with a hint of citrus.", "sku": "56466", "price": "13.40", "weight": "16.0:oz", "visible_in_listings": false, "sorting": 5, "created_at": "2022-02-23T18:05:41.742Z", "updated_at": "2022-03-28T17:29:39.761Z"}}, {"model": "core.product", "pk": 6, "fields": {"name": "Loop d Loop", "description": "Mild Dark Roast\r\n\r\nOur most popular blend reminiscent of Central Italy. A dark, chocolaty flavor perfect for espresso or drip. Its dark Vienna roast properties make it ideal for steamed milk drinks. Organic Indonesia, Africa and America.", "sku": "53264", "price": "13.40", "weight": "16.0:oz", "visible_in_listings": true, "sorting": 6, "created_at": "2022-02-23T18:06:09.881Z", "updated_at": "2022-03-28T17:29:53.104Z"}}, {"model": "core.product", "pk": 7, "fields": {"name": "Dantes Tornado", "description": "Medium Roast\r\n\r\nFull City spicy espresso roast reminiscent of Northern Italy, on the mild side. A full- bodied, earthy sweet drip or Americano. Organic Indonesia, Africa and America.", "sku": "78945", "price": "13.40", "weight": "16.0:oz", "visible_in_listings": true, "sorting": 7, "created_at": "2022-02-23T18:06:35.593Z", "updated_at": "2022-03-28T17:30:11.445Z"}}, {"model": "core.product", "pk": 8, "fields": {"name": "Nicaragua", "description": "Mild Roast\r\n\r\nOur mildest roast with sweet and fruity notes, containing organic beans from Nicaragua. Single origin.", "sku": "12365", "price": "13.40", "weight": "16.0:oz", "visible_in_listings": true, "sorting": 8, "created_at": "2022-02-23T18:06:57.624Z", "updated_at": "2022-03-28T17:30:20.941Z"}}, {"model": "core.productphoto", "pk": 1, "fields": {"product": 1, "image": "products/images/slice2.png"}}, {"model": "core.productphoto", "pk": 2, "fields": {"product": 2, "image": "products/images/slice1.png"}}, {"model": "core.productphoto", "pk": 5, "fields": {"product": 5, "image": "products/images/moka_java.png"}}, {"model": "core.productphoto", "pk": 6, "fields": {"product": 6, "image": "products/images/loop_d_loop.png"}}, {"model": "core.productphoto", "pk": 7, "fields": {"product": 7, "image": "products/images/dantes_tornado.png"}}, {"model": "core.productphoto", "pk": 8, "fields": {"product": 8, "image": "products/images/nicaragua.png"}}, {"model": "core.productphoto", "pk": 15, "fields": {"product": 3, "image": "products/images/pantomime_800.png"}}, {"model": "core.productphoto", "pk": 16, "fields": {"product": 3, "image": "products/images/pantomime_beans.png"}}, {"model": "core.productphoto", "pk": 18, "fields": {"product": 2, "image": "products/images/pantomime_beans_J2bFBiH.png"}}, {"model": "core.productphoto", "pk": 19, "fields": {"product": 4, "image": "products/images/decaf_800.png"}}, {"model": "core.productphoto", "pk": 20, "fields": {"product": 4, "image": "products/images/pantomime_beans_Lo0hJRx.png"}}]

1
src/fixtures/db.json Normal file

File diff suppressed because one or more lines are too long

View File

View File

@ -0,0 +1,32 @@
import os, time
from selenium.webdriver.firefox.webdriver import WebDriver
from selenium.webdriver.common.keys import Keys
from selenium.common.exceptions import WebDriverException
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
class HomeTests(StaticLiveServerTestCase):
fixtures = ['accounts.json', 'products.json']
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.browser = WebDriver()
@classmethod
def tearDownClass(cls):
cls.browser.quit()
super().tearDownClass()
def test_home_page_has_product_list(self):
self.login()
self.assertTrue(
self.browser.find_element_by_css_selector('.product__list')
)
def login(self):
self.browser.get('%s%s' % (self.live_server_url, '/accounts/login/'))
username_input = self.browser.find_element_by_name("login")
username_input.send_keys('john@example.com')
password_input = self.browser.find_element_by_name("password")
password_input.send_keys('Bf25XBdP4vdt2X9L')
self.browser.find_element_by_xpath('//input[@value="Login"]').click()

View File

@ -172,7 +172,10 @@ class Cart:
def create_order(self): def create_order(self):
params = self.build_order_params() params = self.build_order_params()
logger.info(f'\nParams: {params}\n') logger.info(f'\nParams: {params}\n')
if settings.DEBUG:
response = CreateOrder().create_order(params, debug=True) response = CreateOrder().create_order(params, debug=True)
else:
response = CreateOrder().create_order(params)
return response return response
def build_bulk_list(self, order): def build_bulk_list(self, order):

View File

@ -11,6 +11,8 @@ from django.conf import settings
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from django.urls import reverse_lazy from django.urls import reverse_lazy
from core import CoffeeGrind
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class PayPalClient: class PayPalClient:
@ -74,9 +76,11 @@ class CreateOrder(PayPalClient):
# Shows within upper-right dropdown during payment approval # Shows within upper-right dropdown during payment approval
'name': f'{item["product"]}', 'name': f'{item["product"]}',
# Item details will also be in the completed paypal.com transaction view # Item details will also be in the completed paypal.com transaction view
'description': 'Coffee',
'description': ', '.join([next((f"{value['quantity']} x {v[1]}" for i, v in enumerate(CoffeeGrind.GRIND_CHOICES) if v[0] == key), None)
for key, value in item['variations'].items()]),
'unit_amount': { 'unit_amount': {
'currency_code': 'USD', 'currency_code': settings.DEFAULT_CURRENCY,
'value': f'{item["price"]}' 'value': f'{item["price"]}'
}, },
'quantity': f'{item["quantity"]}' 'quantity': f'{item["quantity"]}'
@ -143,18 +147,16 @@ class CreateOrder(PayPalClient):
request.request_body(self.build_request_body(params)) request.request_body(self.build_request_body(params))
response = self.client.execute(request) response = self.client.execute(request)
if debug: if debug:
logger.info(f'\nStatus Code: {response.status_code}', ) logger.debug(f'\nStatus Code: {response.status_code}', )
logger.info(f'\nStatus: {response.result.status}', ) logger.debug(f'\nStatus: {response.result.status}', )
logger.info(f'\nOrder ID: {response.result.id}', ) logger.debug(f'\nOrder ID: {response.result.id}', )
logger.info(f'\nIntent: {response.result.intent}', ) logger.debug(f'\nIntent: {response.result.intent}', )
logger.info(f"\njson_data: {response.result}") logger.debug(f"\njson_data: {response.result}")
return response return response
class CaptureOrder(PayPalClient): class CaptureOrder(PayPalClient):
"""this is the sample function performing payment capture on the order. Approved Order id should be passed as an argument to this function"""
def capture_order(self, order_id, debug=False): def capture_order(self, order_id, debug=False):
"""Method to capture order using order_id""" """Method to capture order using order_id"""
request = OrdersCaptureRequest(order_id) request = OrdersCaptureRequest(order_id)

View File

@ -0,0 +1,43 @@
class RequestFaker:
REQUEST_BODY = {
'intent': 'CAPTURE',
'application_context': {
'return_url': 'https://example.com/done/',
'cancel_url': 'https://example.com/canceled/',
'brand_name': 'example.com',
},
'purchase_units': [
{
'description': 'Coffee',
'amount': {
'currency_code': 'USD',
'value': '13.40',
'breakdown': {
'item_total': {'currency_code': 'USD', 'value': '13.40'},
'shipping': {'currency_code': 'USD', 'value': '0.00'},
'tax_total': {'currency_code': 'USD', 'value': '0'},
'discount': {'currency_code': 'USD', 'value': '0'},
},
},
'items': [
{
'name': 'Decaf',
'description': '1 x Whole Beans, 2 x Percolator',
'unit_amount': {'currency_code': 'USD', 'value': '13.40'},
'quantity': '3',
}
],
'shipping': {
'method': 'US POSTAL SERVICE',
'address': {
'address_line_1': '1504 N 230 E',
'address_line_2': '',
'admin_area_2': 'North Logan',
'admin_area_1': 'UT',
'postal_code': '84341',
'country_code': 'US',
},
},
}
],
}

View File

@ -10,22 +10,12 @@ from paypalcheckoutsdk.core import PayPalHttpClient, SandboxEnvironment
from accounts.models import User, Address from accounts.models import User, Address
from core.models import Product, Order from core.models import Product, Order
from core import CoffeeGrind
from .views import OrderCreateView from storefront.views import OrderCreateView
from .cart import Cart from storefront.cart import Cart
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
WHOLE = 'whole-beans'
ESPRESSO = 'espresso'
CONE_DRIP = 'cone-drip'
BASKET_DRIP = 'basket-drip'
FRENCH_PRESS = 'french-press'
STOVETOP_ESPRESSO = 'stovetop-espresso'
AEROPRESS = 'aeropress'
PERCOLATOR = 'percolator'
CAFE_STYLE = 'cafe-style'
class CartTest(TestCase): class CartTest(TestCase):
def setUp(self): def setUp(self):
self.client = Client() self.client = Client()
@ -48,35 +38,21 @@ class CartTest(TestCase):
) )
self.client.force_login(self.customer) self.client.force_login(self.customer)
self.client.session['shipping_address'] = {
def test_post_checkout_form(self):
url = reverse('storefront:order-create')
response = self.client.get(url)
self.assertTemplateUsed(response, 'storefront/order_form.html')
request = response.wsgi_request
cart = Cart(request)
cart.add(
request,
product=self.product,
quantity=1,
grind=WHOLE,
update_quantity=False
)
params = {
'email': 'nathanchapman@hey.com',
'first_name': 'Nathan', 'first_name': 'Nathan',
'last_name': 'Chapman', 'last_name': 'Chapman',
'total_net_amount': 26.80 'email': 'contact@nathanjchapman.com',
'street_address_1': '1504 N 230 E',
'street_address_2': '',
'city': 'North Logan',
'state': 'UT',
'postal_code': '84341'
} }
response = self.client.post(url, params)
self.assertContains(response, 'Checkout', status_code=200)
def test_cart_item_variations(self): def test_cart_item_variations(self):
cart_detail_url = reverse('storefront:cart-detail') cart_detail_url = reverse('storefront:cart-detail')
response = self.client.get(cart_detail_url) response = self.client.get(cart_detail_url, follow=True)
logger.debug(response.context['messages'])
request = response.wsgi_request request = response.wsgi_request
cart = Cart(request) cart = Cart(request)
@ -85,14 +61,14 @@ class CartTest(TestCase):
request, request,
product=self.product, product=self.product,
quantity=1, quantity=1,
grind=WHOLE, grind=CoffeeGrind.WHOLE,
update_quantity=False update_quantity=False
) )
cart.add( cart.add(
request, request,
product=self.product, product=self.product,
quantity=1, quantity=1,
grind=ESPRESSO, grind=CoffeeGrind.ESPRESSO,
update_quantity=False update_quantity=False
) )
for item in cart.cart.values(): for item in cart.cart.values():
@ -109,11 +85,11 @@ class CartTest(TestCase):
request, request,
product=self.product, product=self.product,
quantity=1, quantity=1,
grind=WHOLE, grind=CoffeeGrind.WHOLE,
update_quantity=False update_quantity=False
) )
self.assertEqual(cart.cart[f'{self.product.id}']['variations'][WHOLE]['quantity'], 1) self.assertEqual(cart.cart[f'{self.product.id}']['variations'][CoffeeGrind.WHOLE]['quantity'], 1)
self.assertEqual(len(cart), 1) self.assertEqual(len(cart), 1)
self.assertEqual(sum(cart.get_item_prices()), Decimal('13.4')) self.assertEqual(sum(cart.get_item_prices()), Decimal('13.4'))
self.assertEqual(cart.get_total_price(), Decimal('13.4')) self.assertEqual(cart.get_total_price(), Decimal('13.4'))
@ -121,20 +97,20 @@ class CartTest(TestCase):
request, request,
product=self.product, product=self.product,
quantity=1, quantity=1,
grind=WHOLE, grind=CoffeeGrind.WHOLE,
update_quantity=False update_quantity=False
) )
self.assertEqual(cart.cart[f'{self.product.id}']['variations'][WHOLE]['quantity'], 2) self.assertEqual(cart.cart[f'{self.product.id}']['variations'][CoffeeGrind.WHOLE]['quantity'], 2)
self.assertEqual(len(cart), 2) self.assertEqual(len(cart), 2)
cart.add( cart.add(
request, request,
product=self.product, product=self.product,
quantity=3, quantity=3,
grind=ESPRESSO, grind=CoffeeGrind.ESPRESSO,
update_quantity=False update_quantity=False
) )
self.assertEqual(cart.cart[f'{self.product.id}']['variations'][ESPRESSO]['quantity'], 3) self.assertEqual(cart.cart[f'{self.product.id}']['variations'][CoffeeGrind.ESPRESSO]['quantity'], 3)
self.assertEqual(len(cart), 5) self.assertEqual(len(cart), 5)
self.assertEqual(cart.get_total_price(), Decimal('67')) self.assertEqual(cart.get_total_price(), Decimal('67'))
@ -149,19 +125,19 @@ class CartTest(TestCase):
request, request,
product=self.product, product=self.product,
quantity=3, quantity=3,
grind=WHOLE, grind=CoffeeGrind.WHOLE,
update_quantity=False update_quantity=False
) )
self.assertEqual(cart.cart[f'{self.product.id}']['variations'][WHOLE]['quantity'], 3) self.assertEqual(cart.cart[f'{self.product.id}']['variations'][CoffeeGrind.WHOLE]['quantity'], 3)
cart.add( cart.add(
request, request,
product=self.product, product=self.product,
quantity=1, quantity=1,
grind=WHOLE, grind=CoffeeGrind.WHOLE,
update_quantity=True update_quantity=True
) )
self.assertEqual(cart.cart[f'{self.product.id}']['variations'][WHOLE]['quantity'], 1) self.assertEqual(cart.cart[f'{self.product.id}']['variations'][CoffeeGrind.WHOLE]['quantity'], 1)
def test_cart_remove_item(self): def test_cart_remove_item(self):
cart_detail_url = reverse('storefront:cart-detail') cart_detail_url = reverse('storefront:cart-detail')
@ -174,7 +150,7 @@ class CartTest(TestCase):
request, request,
product=self.product, product=self.product,
quantity=3, quantity=3,
grind=WHOLE, grind=CoffeeGrind.WHOLE,
update_quantity=False update_quantity=False
) )
self.assertEqual(len(cart), 3) self.assertEqual(len(cart), 3)
@ -192,9 +168,10 @@ class CartTest(TestCase):
request, request,
product=self.product, product=self.product,
quantity=3, quantity=3,
grind=WHOLE, grind=CoffeeGrind.WHOLE,
update_quantity=False update_quantity=False
) )
self.assertEqual(cart.get_total_weight(), Decimal(48)) self.assertEqual(cart.get_total_weight(), Decimal(48))
# 96oz def test_cart_(self):
pass

View File

@ -0,0 +1,73 @@
import logging
from decimal import Decimal
from measurement.measures import Weight
from django.test import TestCase, Client, RequestFactory
from django.urls import reverse
from django.conf import settings
from django.contrib.sessions.middleware import SessionMiddleware
from paypalcheckoutsdk.orders import OrdersCreateRequest, OrdersCaptureRequest
from paypalcheckoutsdk.core import PayPalHttpClient, SandboxEnvironment
from accounts.models import User, Address
from core.models import Product, Order
from core import CoffeeGrind
from storefront.views import OrderCreateView
from storefront.cart import Cart
from storefront.payments import CreateOrder
from . import RequestFaker
logger = logging.getLogger(__name__)
class CreateOrderTest(TestCase):
def setUp(self):
self.client = Client()
self.product = Product.objects.create(
name='Decaf',
description='Coffee',
sku='23987',
price=Decimal('13.40'),
weight=Weight(oz=16),
visible_in_listings=True
)
def test_build_request_body(self):
product_list_url = reverse('storefront:product-list')
response = self.client.get(product_list_url, follow=True)
request = response.wsgi_request
cart = Cart(request)
cart.add(
request,
product=self.product,
quantity=1,
grind=CoffeeGrind.WHOLE,
update_quantity=False
)
cart.add(
request,
product=self.product,
quantity=2,
grind=CoffeeGrind.PERCOLATOR,
update_quantity=False
)
params = {
'items': cart,
'total_price': '13.40',
'item_total': '13.40',
'discount': '0',
'shipping_price': '0.00',
'tax_total': '0',
'shipping_method': 'US POSTAL SERVICE',
'shipping_address': {
'address_line_1': '1504 N 230 E',
'address_line_2': '',
'admin_area_2': 'North Logan',
'admin_area_1': 'UT',
'postal_code': '84341',
'country_code': 'US',
},
}
request_body = CreateOrder().build_request_body(params)
self.assertDictEqual(RequestFaker.REQUEST_BODY, request_body)

View File

@ -231,7 +231,6 @@ class OrderCreateView(CreateView):
return context return context
def form_valid(self, form): def form_valid(self, form):
# TODO: make order status "Draft" and then in the PP capture set status appropriately
cart = Cart(self.request) cart = Cart(self.request)
shipping_address = self.request.session.get('shipping_address') shipping_address = self.request.session.get('shipping_address')
form.instance.customer, form.instance.shipping_address = get_or_create_customer(self.request, form, shipping_address) form.instance.customer, form.instance.shipping_address = get_or_create_customer(self.request, form, shipping_address)