#51 feat: API de categorias de productos
Issue #51: discriminacion y administracion de categorias de productos. - ProductCategoryView en /api/product_categories (list/retrieve IsNotPublico, mutaciones admin): filtro ?active, PATCH de description/alternative_name/active - POST deactivate/activate con body {ids} -> {deactivated|activated, failed} (ignora ids inexistentes) - POST import_from_external admin via CategoryTrytonService - create/destroy deshabilitados (la fuente de categorias es Tryton)
This commit is contained in:
224
tienda_ilusion/don_confiao/tests/test_product_categories_api.py
Normal file
224
tienda_ilusion/don_confiao/tests/test_product_categories_api.py
Normal file
@@ -0,0 +1,224 @@
|
||||
import json
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import TestCase
|
||||
from rest_framework.test import APIClient
|
||||
from rest_framework_simplejwt.tokens import RefreshToken
|
||||
|
||||
from ..models.products import ProductCategory
|
||||
|
||||
|
||||
class ProductCategoryApiTest(TestCase):
|
||||
def setUp(self):
|
||||
self.user = User.objects.create_superuser(
|
||||
username="admin",
|
||||
email="admin@example.com",
|
||||
password="adminpass",
|
||||
)
|
||||
refresh = RefreshToken.for_user(self.user)
|
||||
self.client = APIClient()
|
||||
self.client.credentials(
|
||||
HTTP_AUTHORIZATION=f"Bearer {refresh.access_token}"
|
||||
)
|
||||
|
||||
def login_as_regular_user(self):
|
||||
user = User.objects.create_user(
|
||||
username="vendedor", password="vendedorpass"
|
||||
)
|
||||
refresh = RefreshToken.for_user(user)
|
||||
self.client.credentials(
|
||||
HTTP_AUTHORIZATION=f"Bearer {refresh.access_token}"
|
||||
)
|
||||
|
||||
def test_list_categories_filters_by_active(self):
|
||||
ProductCategory.objects.create(name="Granos", external_id="6")
|
||||
ProductCategory.objects.create(
|
||||
name="Verduras", external_id="7", active=False
|
||||
)
|
||||
|
||||
response = self.client.get(
|
||||
"/don_confiao/api/product_categories/?active=all"
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(len(response.data), 2)
|
||||
|
||||
response = self.client.get(
|
||||
"/don_confiao/api/product_categories/?active=true"
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(len(response.data), 1)
|
||||
self.assertEqual(response.data[0]["name"], "Granos")
|
||||
|
||||
response = self.client.get(
|
||||
"/don_confiao/api/product_categories/?active=false"
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(len(response.data), 1)
|
||||
self.assertEqual(response.data[0]["name"], "Verduras")
|
||||
|
||||
def test_patch_updates_don_confiao_fields_only(self):
|
||||
category = ProductCategory.objects.create(
|
||||
name="Granos", external_id="6"
|
||||
)
|
||||
|
||||
response = self.client.patch(
|
||||
f"/don_confiao/api/product_categories/{category.pk}/",
|
||||
{
|
||||
"description": "Granos enteros",
|
||||
"alternative_name": "Cereales",
|
||||
"active": False,
|
||||
"name": "Nombre intentado",
|
||||
"external_id": "999",
|
||||
},
|
||||
format="json",
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
refreshed = ProductCategory.objects.get(pk=category.pk)
|
||||
self.assertEqual(refreshed.description, "Granos enteros")
|
||||
self.assertEqual(refreshed.alternative_name, "Cereales")
|
||||
self.assertFalse(refreshed.active)
|
||||
self.assertEqual(refreshed.name, "Granos")
|
||||
self.assertEqual(refreshed.external_id, "6")
|
||||
|
||||
def test_deactivate_categories(self):
|
||||
category = ProductCategory.objects.create(
|
||||
name="Granos", external_id="6"
|
||||
)
|
||||
|
||||
response = self.client.post(
|
||||
"/don_confiao/api/product_categories/deactivate/",
|
||||
{"ids": [category.pk]},
|
||||
format="json",
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(
|
||||
response.data,
|
||||
{
|
||||
"deactivated": [{"id": category.pk, "name": "Granos"}],
|
||||
"failed": [],
|
||||
},
|
||||
)
|
||||
self.assertFalse(
|
||||
ProductCategory.objects.get(pk=category.pk).active
|
||||
)
|
||||
|
||||
def test_activate_categories(self):
|
||||
category = ProductCategory.objects.create(
|
||||
name="Granos", external_id="6", active=False
|
||||
)
|
||||
|
||||
response = self.client.post(
|
||||
"/don_confiao/api/product_categories/activate/",
|
||||
{"ids": [category.pk]},
|
||||
format="json",
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(
|
||||
response.data,
|
||||
{
|
||||
"activated": [{"id": category.pk, "name": "Granos"}],
|
||||
"failed": [],
|
||||
},
|
||||
)
|
||||
self.assertTrue(ProductCategory.objects.get(pk=category.pk).active)
|
||||
|
||||
def test_deactivate_ignores_nonexistent_ids(self):
|
||||
category = ProductCategory.objects.create(
|
||||
name="Granos", external_id="6"
|
||||
)
|
||||
|
||||
response = self.client.post(
|
||||
"/don_confiao/api/product_categories/deactivate/",
|
||||
{"ids": [category.pk, 99999]},
|
||||
format="json",
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(
|
||||
response.data["deactivated"],
|
||||
[{"id": category.pk, "name": "Granos"}],
|
||||
)
|
||||
self.assertEqual(response.data["failed"], [])
|
||||
self.assertFalse(
|
||||
ProductCategory.objects.get(pk=category.pk).active
|
||||
)
|
||||
|
||||
@patch("sabatron_tryton_rpc_client.client.Client.call")
|
||||
@patch("sabatron_tryton_rpc_client.client.Client.connect")
|
||||
def test_import_from_external(self, mock_connect, mock_call):
|
||||
mock_connect.return_value = None
|
||||
|
||||
def fake_call(*args, **kwargs):
|
||||
if args[0] == "model.product.product.search":
|
||||
return [1]
|
||||
if args[0] == "model.product.product.read":
|
||||
return [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Producto 1",
|
||||
"template.": {"id": 999, "categories": [6]},
|
||||
},
|
||||
]
|
||||
if args[0] == "model.product.category.read":
|
||||
return [{"id": 6, "name": "Alimentos"}]
|
||||
raise Exception(f"args no esperados: {args}")
|
||||
|
||||
mock_call.side_effect = fake_call
|
||||
|
||||
response = self.client.post(
|
||||
"/don_confiao/api/product_categories/import_from_external/"
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
content = json.loads(response.content.decode("utf-8"))
|
||||
self.assertTrue(
|
||||
set(
|
||||
[
|
||||
"checked_tryton_categories",
|
||||
"created_categories",
|
||||
"updated_categories",
|
||||
"untouched_categories",
|
||||
"failed_categories",
|
||||
]
|
||||
).issubset(content.keys())
|
||||
)
|
||||
self.assertEqual(
|
||||
content["created_categories"],
|
||||
[
|
||||
{
|
||||
"id": ProductCategory.objects.get(external_id="6").pk,
|
||||
"name": "Alimentos",
|
||||
"external_id": "6",
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
@patch("sabatron_tryton_rpc_client.client.Client.call")
|
||||
@patch("sabatron_tryton_rpc_client.client.Client.connect")
|
||||
def test_mutations_require_administrator(
|
||||
self, mock_connect, mock_call
|
||||
):
|
||||
mock_connect.return_value = None
|
||||
self.login_as_regular_user()
|
||||
category = ProductCategory.objects.create(
|
||||
name="Granos", external_id="6"
|
||||
)
|
||||
|
||||
response = self.client.post(
|
||||
"/don_confiao/api/product_categories/deactivate/",
|
||||
{"ids": [category.pk]},
|
||||
format="json",
|
||||
)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
response = self.client.post(
|
||||
"/don_confiao/api/product_categories/import_from_external/"
|
||||
)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
response = self.client.patch(
|
||||
f"/don_confiao/api/product_categories/{category.pk}/",
|
||||
{"description": "x"},
|
||||
format="json",
|
||||
)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
Reference in New Issue
Block a user