#51 feat: servicio de importacion externa de categorias de productos
Issue #51: discriminacion y administracion de categorias de productos. - CategoryTrytonService.import_from_external: lee categorias asociadas a productos salables, hace upsert por external_id (fallback nombre), preserva description/alternative_name/active, reporta checked/created/updated/untouched/failed - upsert_category compartido y reutilizado por ProductTrytonService
This commit is contained in:
200
tienda_ilusion/don_confiao/tests/test_categories_from_tryton.py
Normal file
200
tienda_ilusion/don_confiao/tests/test_categories_from_tryton.py
Normal file
@@ -0,0 +1,200 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from ..models.products import ProductCategory
|
||||
from ..services.tryton.categories import CategoryTrytonService
|
||||
|
||||
|
||||
class TestCategoryTrytonImport(TestCase):
|
||||
def _service(self, mock_call):
|
||||
fake_client = type("FakeClient", (), {"call": mock_call})()
|
||||
return CategoryTrytonService(fake_client)
|
||||
|
||||
def _empty_report(self):
|
||||
return {
|
||||
"checked_tryton_categories": [],
|
||||
"created_categories": [],
|
||||
"updated_categories": [],
|
||||
"untouched_categories": [],
|
||||
"failed_categories": [],
|
||||
}
|
||||
|
||||
@patch("sabatron_tryton_rpc_client.client.Client.call")
|
||||
def test_import_creates_only_referenced_categories(self, mock_call):
|
||||
def fake_call(*args, **kwargs):
|
||||
if args[0] == "model.product.product.search":
|
||||
return [1, 2]
|
||||
if args[0] == "model.product.product.read":
|
||||
return [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Producto 1",
|
||||
"template.": {"id": 999, "categories": [6]},
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Producto 2",
|
||||
"template.": {"id": 999, "categories": [7]},
|
||||
},
|
||||
]
|
||||
if args[0] == "model.product.category.read":
|
||||
return [
|
||||
{"id": 6, "name": "Alimentos"},
|
||||
{"id": 7, "name": "Verduras"},
|
||||
]
|
||||
raise Exception(f"args no esperados: {args}")
|
||||
|
||||
mock_call.side_effect = fake_call
|
||||
result = self._service(mock_call).import_from_external()
|
||||
|
||||
self.assertEqual(
|
||||
result["checked_tryton_categories"],
|
||||
[
|
||||
{"id": 6, "name": "Alimentos"},
|
||||
{"id": 7, "name": "Verduras"},
|
||||
],
|
||||
)
|
||||
self.assertEqual(
|
||||
result["created_categories"],
|
||||
[
|
||||
{
|
||||
"id": ProductCategory.objects.get(external_id="6").pk,
|
||||
"name": "Alimentos",
|
||||
"external_id": "6",
|
||||
},
|
||||
{
|
||||
"id": ProductCategory.objects.get(external_id="7").pk,
|
||||
"name": "Verduras",
|
||||
"external_id": "7",
|
||||
},
|
||||
],
|
||||
)
|
||||
self.assertEqual(result["updated_categories"], [])
|
||||
self.assertEqual(result["untouched_categories"], [])
|
||||
self.assertEqual(result["failed_categories"], [])
|
||||
self.assertEqual(ProductCategory.objects.count(), 2)
|
||||
|
||||
@patch("sabatron_tryton_rpc_client.client.Client.call")
|
||||
def test_import_preserves_don_confiao_fields_on_update(self, mock_call):
|
||||
local_category = ProductCategory.objects.create(
|
||||
name="Granos",
|
||||
external_id="7",
|
||||
description="Granos enteros",
|
||||
alternative_name="Cereales",
|
||||
active=False,
|
||||
)
|
||||
|
||||
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": [7]},
|
||||
},
|
||||
]
|
||||
if args[0] == "model.product.category.read":
|
||||
return [{"id": 7, "name": "Cereales y Granos"}]
|
||||
raise Exception(f"args no esperados: {args}")
|
||||
|
||||
mock_call.side_effect = fake_call
|
||||
result = self._service(mock_call).import_from_external()
|
||||
|
||||
self.assertEqual(result["created_categories"], [])
|
||||
self.assertEqual(
|
||||
result["updated_categories"],
|
||||
[
|
||||
{
|
||||
"id": local_category.pk,
|
||||
"name": "Cereales y Granos",
|
||||
"external_id": "7",
|
||||
"detail": "Nombre: Granos → Cereales y Granos",
|
||||
}
|
||||
],
|
||||
)
|
||||
refreshed = ProductCategory.objects.get(pk=local_category.pk)
|
||||
self.assertEqual(refreshed.name, "Cereales y Granos")
|
||||
self.assertEqual(refreshed.description, "Granos enteros")
|
||||
self.assertEqual(refreshed.alternative_name, "Cereales")
|
||||
self.assertFalse(refreshed.active)
|
||||
|
||||
@patch("sabatron_tryton_rpc_client.client.Client.call")
|
||||
def test_import_counts_untouched_categories(self, mock_call):
|
||||
local_category = ProductCategory.objects.create(
|
||||
name="Alimentos", external_id="6"
|
||||
)
|
||||
|
||||
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
|
||||
result = self._service(mock_call).import_from_external()
|
||||
|
||||
self.assertEqual(result["created_categories"], [])
|
||||
self.assertEqual(result["updated_categories"], [])
|
||||
self.assertEqual(
|
||||
result["untouched_categories"],
|
||||
[
|
||||
{
|
||||
"id": local_category.pk,
|
||||
"name": "Alimentos",
|
||||
"external_id": "6",
|
||||
}
|
||||
],
|
||||
)
|
||||
self.assertEqual(result["failed_categories"], [])
|
||||
self.assertEqual(ProductCategory.objects.count(), 1)
|
||||
|
||||
@patch("sabatron_tryton_rpc_client.client.Client.call")
|
||||
def test_import_without_products_returns_empty_report(self, mock_call):
|
||||
def fake_call(*args, **kwargs):
|
||||
if args[0] == "model.product.product.search":
|
||||
return []
|
||||
raise Exception(f"args no esperados: {args}")
|
||||
|
||||
mock_call.side_effect = fake_call
|
||||
result = self._service(mock_call).import_from_external()
|
||||
self.assertEqual(result, self._empty_report())
|
||||
self.assertEqual(ProductCategory.objects.count(), 0)
|
||||
|
||||
@patch("sabatron_tryton_rpc_client.client.Client.call")
|
||||
def test_import_failed_category_reported(self, mock_call):
|
||||
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": None}]
|
||||
raise Exception(f"args no esperados: {args}")
|
||||
|
||||
mock_call.side_effect = fake_call
|
||||
result = self._service(mock_call).import_from_external()
|
||||
|
||||
self.assertEqual(result["created_categories"], [])
|
||||
self.assertEqual(len(result["failed_categories"]), 1)
|
||||
failure = result["failed_categories"][0]
|
||||
self.assertEqual(failure["external_id"], 6)
|
||||
self.assertEqual(failure["name"], None)
|
||||
self.assertIn("Error", failure["error"])
|
||||
Reference in New Issue
Block a user