This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
|
||||
class TestBackofficeAccess(TestCase):
|
||||
def test_anonymous_user_is_redirected_to_login(self):
|
||||
response = self.client.get(reverse('backoffice:dashboard'))
|
||||
assert response.status_code == 302
|
||||
assert reverse('users:login') in response.url
|
||||
|
||||
def test_authenticated_non_staff_user_gets_forbidden(self):
|
||||
user = User.objects.create_user('luke', 'luke@rebels.com', 'ihatesand', is_staff=False)
|
||||
self.client.force_login(user)
|
||||
|
||||
response = self.client.get(reverse('backoffice:dashboard'))
|
||||
assert response.status_code == 403
|
||||
|
||||
def test_superuser_can_access_dashboard(self):
|
||||
user = User.objects.create_superuser('vader', 'vader@empire.com', 'ihatesand')
|
||||
self.client.force_login(user)
|
||||
|
||||
response = self.client.get(reverse('backoffice:dashboard'))
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_staff_without_specific_permission_gets_forbidden_on_product_list(self):
|
||||
user = User.objects.create_user('han', 'han@falcon.com', 'ihatesand', is_staff=True)
|
||||
self.client.force_login(user)
|
||||
|
||||
response = self.client.get(reverse('backoffice:product_list'))
|
||||
assert response.status_code == 403
|
||||
@@ -0,0 +1,134 @@
|
||||
import json
|
||||
from io import BytesIO
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.files.base import ContentFile
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from PIL import Image
|
||||
|
||||
from shop.models import Product, ProductImage, ProductVariant
|
||||
from shop.tests.mixins import CreateProductsMixin
|
||||
|
||||
|
||||
class TestBackofficeHtmxContract(TestCase, CreateProductsMixin):
|
||||
def setUp(self):
|
||||
self.superuser = User.objects.create_superuser('vader', 'vader@empire.com', 'ihatesand')
|
||||
self.client.force_login(self.superuser)
|
||||
self.product = self.create_product()
|
||||
|
||||
def test_htmx_get_create_form_returns_fragment_not_full_page(self):
|
||||
response = self.client.get(reverse('backoffice:product_create'), HTTP_HX_REQUEST='true')
|
||||
assert response.status_code == 200
|
||||
body = response.content.decode()
|
||||
assert '<html' not in body
|
||||
assert 'Añadir producto' in body
|
||||
|
||||
def test_htmx_create_success_returns_204_and_triggers_modal_close_and_refresh(self):
|
||||
response = self.client.post(
|
||||
reverse('backoffice:product_create'),
|
||||
{
|
||||
'sku': 'HTMX-1',
|
||||
'name': 'Producto htmx',
|
||||
'description': '',
|
||||
'stock': '0',
|
||||
'categories': [],
|
||||
'tags': [],
|
||||
},
|
||||
HTTP_HX_REQUEST='true',
|
||||
)
|
||||
assert response.status_code == 204
|
||||
trigger = json.loads(response['HX-Trigger'])
|
||||
assert trigger == {'backoffice:modal-close': True, 'backoffice:list-changed': True}
|
||||
assert Product.objects.filter(sku='HTMX-1').exists()
|
||||
|
||||
def test_htmx_create_invalid_returns_200_with_errors_not_204(self):
|
||||
response = self.client.post(
|
||||
reverse('backoffice:product_create'),
|
||||
{'sku': '', 'name': '', 'description': '', 'stock': '0', 'categories': [], 'tags': []},
|
||||
HTTP_HX_REQUEST='true',
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert 'HX-Trigger' not in response
|
||||
|
||||
def test_non_htmx_create_still_redirects(self):
|
||||
response = self.client.post(
|
||||
reverse('backoffice:product_create'),
|
||||
{
|
||||
'sku': 'PLAIN-1',
|
||||
'name': 'Producto normal',
|
||||
'description': '',
|
||||
'stock': '0',
|
||||
'categories': [],
|
||||
'tags': [],
|
||||
},
|
||||
)
|
||||
assert response.status_code == 302
|
||||
assert 'HX-Trigger' not in response
|
||||
|
||||
def test_htmx_variant_create_triggers_refresh(self):
|
||||
size_m = self.create_attribute_value('Talla', 'M')
|
||||
|
||||
response = self.client.post(
|
||||
reverse('backoffice:product_variant_create', args=[self.product.pk]),
|
||||
{'sku': 'HTMX-V1', 'stock': '5', 'attribute_values': [size_m.pk]},
|
||||
HTTP_HX_REQUEST='true',
|
||||
)
|
||||
assert response.status_code == 204
|
||||
trigger = json.loads(response['HX-Trigger'])
|
||||
assert trigger['backoffice:modal-close'] is True
|
||||
assert trigger['backoffice:list-changed'] is True
|
||||
assert ProductVariant.objects.filter(sku='HTMX-V1').exists()
|
||||
|
||||
def test_htmx_variant_delete_stays_and_refreshes(self):
|
||||
size_m = self.create_attribute_value('Talla', 'M')
|
||||
variant = self.create_product_variant(self.product, sku='HTMX-V2', attribute_values=[size_m])
|
||||
|
||||
response = self.client.post(
|
||||
reverse('backoffice:product_variant_delete', args=[variant.pk]), HTTP_HX_REQUEST='true'
|
||||
)
|
||||
assert response.status_code == 204
|
||||
assert 'HX-Redirect' not in response
|
||||
trigger = json.loads(response['HX-Trigger'])
|
||||
assert trigger['backoffice:list-changed'] is True
|
||||
assert not ProductVariant.objects.filter(pk=variant.pk).exists()
|
||||
|
||||
def test_htmx_image_upload_triggers_refresh(self):
|
||||
image = Image.new('RGB', (64, 64), '#ACACAC')
|
||||
buffer = BytesIO()
|
||||
image.save(fp=buffer, format='WEBP')
|
||||
file = ContentFile(buffer.getvalue(), name='test.webp')
|
||||
|
||||
response = self.client.post(
|
||||
reverse('backoffice:product_image_create', args=[self.product.pk]),
|
||||
{'original': file},
|
||||
format='multipart',
|
||||
HTTP_HX_REQUEST='true',
|
||||
)
|
||||
assert response.status_code == 204
|
||||
trigger = json.loads(response['HX-Trigger'])
|
||||
assert trigger == {'backoffice:modal-close': True, 'backoffice:list-changed': True}
|
||||
assert ProductImage.objects.filter(product=self.product).exists()
|
||||
|
||||
def test_htmx_product_delete_uses_hx_redirect_instead_of_trigger(self):
|
||||
product = self.create_product(sku='HTMX-DEL')
|
||||
|
||||
response = self.client.post(reverse('backoffice:product_delete', args=[product.pk]), HTTP_HX_REQUEST='true')
|
||||
assert response.status_code == 200
|
||||
assert response['HX-Redirect'] == reverse('backoffice:product_list')
|
||||
assert 'HX-Trigger' not in response
|
||||
assert not Product.objects.filter(pk=product.pk).exists()
|
||||
|
||||
def test_htmx_list_self_refresh_returns_fragment(self):
|
||||
response = self.client.get(reverse('backoffice:product_list'), HTTP_HX_REQUEST='true')
|
||||
assert response.status_code == 200
|
||||
body = response.content.decode()
|
||||
assert '<html' not in body
|
||||
assert 'id="backoffice-list"' in body
|
||||
|
||||
def test_htmx_product_detail_self_refresh_returns_fragment(self):
|
||||
response = self.client.get(reverse('backoffice:product_detail', args=[self.product.pk]), HTTP_HX_REQUEST='true')
|
||||
assert response.status_code == 200
|
||||
body = response.content.decode()
|
||||
assert '<html' not in body
|
||||
assert 'id="backoffice-detail"' in body
|
||||
@@ -0,0 +1,50 @@
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from shop.models import Order
|
||||
from shop.tests.mixins import CreateProductsMixin
|
||||
|
||||
|
||||
class TestBackofficeOrders(TestCase, CreateProductsMixin):
|
||||
def setUp(self):
|
||||
self.superuser = User.objects.create_superuser('vader', 'vader@empire.com', 'ihatesand')
|
||||
self.client.force_login(self.superuser)
|
||||
self.product = self.create_product()
|
||||
self.order = Order.objects.create(
|
||||
email='client@example.com',
|
||||
billing_address='Tatooine',
|
||||
billing_city='Mos Eisley',
|
||||
billing_state='Tatooine',
|
||||
billing_country='Tatooine',
|
||||
billing_zip='00001',
|
||||
shipping_address='Tatooine',
|
||||
shipping_city='Mos Eisley',
|
||||
shipping_state='Tatooine',
|
||||
shipping_country='Tatooine',
|
||||
shipping_zip='00001',
|
||||
)
|
||||
|
||||
def test_order_list(self):
|
||||
response = self.client.get(reverse('backoffice:order_list'))
|
||||
assert response.status_code == 200
|
||||
assert self.order.code in response.content.decode()
|
||||
|
||||
def test_order_list_filtered_by_status(self):
|
||||
response = self.client.get(reverse('backoffice:order_list'), {'status': Order.Statuses.STATUS_PAID})
|
||||
assert response.status_code == 200
|
||||
assert self.order.code not in response.content.decode()
|
||||
|
||||
def test_order_detail(self):
|
||||
response = self.client.get(reverse('backoffice:order_detail', args=[self.order.pk]))
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_update_shipping_status(self):
|
||||
response = self.client.post(
|
||||
reverse('backoffice:order_update_shipping_status', args=[self.order.pk]),
|
||||
{'shipping_status': Order.ShippingStatuses.STATUS_SENT},
|
||||
)
|
||||
assert response.status_code == 302
|
||||
|
||||
self.order.refresh_from_db()
|
||||
assert self.order.shipping_status == Order.ShippingStatuses.STATUS_SENT
|
||||
@@ -0,0 +1,116 @@
|
||||
from decimal import Decimal
|
||||
from io import BytesIO
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.files.base import ContentFile
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from PIL import Image
|
||||
|
||||
from shop.models import Product, ProductImage, ProductPrice, ProductVariant, Tax
|
||||
from shop.tests.mixins import CreateProductsMixin
|
||||
|
||||
|
||||
class TestBackofficeProducts(TestCase, CreateProductsMixin):
|
||||
def setUp(self):
|
||||
self.superuser = User.objects.create_superuser('vader', 'vader@empire.com', 'ihatesand')
|
||||
self.client.force_login(self.superuser)
|
||||
self.product = self.create_product()
|
||||
|
||||
def test_product_list(self):
|
||||
response = self.client.get(reverse('backoffice:product_list'))
|
||||
assert response.status_code == 200
|
||||
assert self.product.name in response.content.decode()
|
||||
|
||||
def test_create_product(self):
|
||||
response = self.client.post(
|
||||
reverse('backoffice:product_create'),
|
||||
{
|
||||
'sku': 'NEW-1',
|
||||
'name': 'Producto nuevo',
|
||||
'description': '',
|
||||
'stock': '0',
|
||||
'categories': [],
|
||||
'tags': [],
|
||||
},
|
||||
)
|
||||
assert response.status_code == 302
|
||||
assert Product.objects.filter(sku='NEW-1').exists()
|
||||
|
||||
def test_create_variant_for_product(self):
|
||||
size_m = self.create_attribute_value('Talla', 'M')
|
||||
|
||||
response = self.client.post(
|
||||
reverse('backoffice:product_variant_create', args=[self.product.pk]),
|
||||
{'sku': 'V-M', 'stock': '5', 'attribute_values': [size_m.pk]},
|
||||
)
|
||||
assert response.status_code == 302
|
||||
variant = ProductVariant.objects.get(sku='V-M')
|
||||
assert variant.product == self.product
|
||||
assert size_m in variant.attribute_values.all()
|
||||
|
||||
def test_create_duplicated_variant_combination_fails(self):
|
||||
size_m = self.create_attribute_value('Talla', 'M')
|
||||
self.create_product_variant(self.product, sku='V-M1', attribute_values=[size_m])
|
||||
|
||||
response = self.client.post(
|
||||
reverse('backoffice:product_variant_create', args=[self.product.pk]),
|
||||
{'sku': 'V-M2', 'stock': '5', 'attribute_values': [size_m.pk]},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert not ProductVariant.objects.filter(sku='V-M2').exists()
|
||||
|
||||
def test_create_price_for_product(self):
|
||||
tax, created = Tax.objects.get_or_create(code='IVA', value=21)
|
||||
|
||||
response = self.client.post(
|
||||
reverse('backoffice:product_price_create', args=[self.product.pk]),
|
||||
{'price': '9.99', 'tax': tax.pk, 'current': 'on'},
|
||||
)
|
||||
assert response.status_code == 302
|
||||
assert ProductPrice.objects.filter(product=self.product, price=Decimal('9.99')).exists()
|
||||
|
||||
def test_create_price_for_variant(self):
|
||||
tax, created = Tax.objects.get_or_create(code='IVA', value=21)
|
||||
size_m = self.create_attribute_value('Talla', 'M')
|
||||
variant = self.create_product_variant(self.product, sku='V-M', attribute_values=[size_m])
|
||||
|
||||
response = self.client.post(
|
||||
reverse('backoffice:product_variant_price_create', args=[variant.pk]),
|
||||
{'price': '19.99', 'tax': tax.pk, 'current': 'on'},
|
||||
)
|
||||
assert response.status_code == 302
|
||||
assert ProductPrice.objects.filter(variant=variant, price=Decimal('19.99')).exists()
|
||||
|
||||
def create_image_file(self, name='test.webp'):
|
||||
image = Image.new('RGB', (256, 256), '#ACACAC')
|
||||
buffer = BytesIO()
|
||||
image.save(fp=buffer, format='WEBP')
|
||||
return ContentFile(buffer.getvalue(), name=name)
|
||||
|
||||
def test_create_image_for_product(self):
|
||||
response = self.client.post(
|
||||
reverse('backoffice:product_image_create', args=[self.product.pk]),
|
||||
{'original': self.create_image_file()},
|
||||
format='multipart',
|
||||
)
|
||||
assert response.status_code == 302
|
||||
assert ProductImage.objects.filter(product=self.product).exists()
|
||||
|
||||
def test_delete_image(self):
|
||||
image = ProductImage.objects.create(product=self.product, original=self.create_image_file())
|
||||
|
||||
response = self.client.post(reverse('backoffice:product_image_delete', args=[image.pk]))
|
||||
assert response.status_code == 302
|
||||
assert not ProductImage.objects.filter(pk=image.pk).exists()
|
||||
|
||||
def test_delete_batch_reduces_stock(self):
|
||||
self.product.stock = Decimal('10')
|
||||
self.product.save()
|
||||
batch = self.product.productbatch_set.create(code='B1', quantity=Decimal('3'))
|
||||
|
||||
response = self.client.post(reverse('backoffice:product_batch_delete', args=[batch.pk]))
|
||||
assert response.status_code == 302
|
||||
|
||||
self.product.refresh_from_db()
|
||||
assert self.product.stock == Decimal('7')
|
||||
@@ -0,0 +1,40 @@
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from shop.models import ShopSettings
|
||||
from web.models import WebSettings
|
||||
|
||||
|
||||
class TestBackofficeSettings(TestCase):
|
||||
def setUp(self):
|
||||
self.superuser = User.objects.create_superuser('vader', 'vader@empire.com', 'ihatesand')
|
||||
self.client.force_login(self.superuser)
|
||||
|
||||
def test_get_brand_settings(self):
|
||||
response = self.client.get(reverse('backoffice:brand_settings'))
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_update_brand_settings(self):
|
||||
response = self.client.post(
|
||||
reverse('backoffice:brand_settings'),
|
||||
{'web_title': 'Mi tienda', 'web_description': '', 'bg_color': '', 'theme_color': ''},
|
||||
)
|
||||
assert response.status_code == 302
|
||||
|
||||
settings = WebSettings.objects.get(pk=1)
|
||||
assert settings.web_title == 'Mi tienda'
|
||||
|
||||
def test_get_shop_settings(self):
|
||||
response = self.client.get(reverse('backoffice:shop_settings'))
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_update_shop_settings(self):
|
||||
response = self.client.post(
|
||||
reverse('backoffice:shop_settings'),
|
||||
{'merchant_code': '999008881', 'currency_code': '978', 'terminal': '001', 'shared_secret': 'secret'},
|
||||
)
|
||||
assert response.status_code == 302
|
||||
|
||||
settings = ShopSettings.objects.get(pk=1)
|
||||
assert settings.merchant_code == '999008881'
|
||||
Reference in New Issue
Block a user