Repository: fidals/shopelectro

Issue:
Test update_pack command
Created in #864


Discussion:

Resolving patch:
diff --git a/shopelectro/management/commands/_update_catalog/update_pack.py b/shopelectro/management/commands/_update_catalog/update_pack.py
--- a/shopelectro/management/commands/_update_catalog/update_pack.py
+++ b/shopelectro/management/commands/_update_catalog/update_pack.py
@@ -6,39 +6,34 @@
 2. Multiply product prices by in_pack value and save.
 """
 import logging
-import typing
 
 from django.conf import settings
 from django.db import models, transaction
 
 from catalog.models_expressions import Substring
 
+from shopelectro.exception import UpdateCatalogException
 from shopelectro.models import TagQuerySet, TagGroup
 
 logger = logging.getLogger(__name__)
 PRICES = ['price', 'purchase_price', 'wholesale_small', 'wholesale_medium', 'wholesale_large']
 
 
-def find_pack_group() -> typing.Optional[TagGroup]:
-    pack_group = TagGroup.objects.filter(uuid=settings.PACK_GROUP_UUID).first()
-
-    # @todo #864:60m Raise errors in find_pack_group.
-    #  Remove Optional type as returning value and test find_pack_group.
-    if not pack_group:
-        logger.error(
-            f'Couldn\'t find "{settings.PACK_GROUP_NAME}" tag group by'
-            f'UUID="{settings.PACK_GROUP_UUID}".\n'
-            'Update the PACK_GROUP_UUID django settings variable to set the new relevant UUID.'
+def find_pack_group() -> TagGroup:
+    try:
+        pack_group = TagGroup.objects.get_pack()
+    except TagGroup.DoesNotExist as error:
+        raise UpdateCatalogException(
+            'Update the PACK_GROUP_UUID django settings variable to set the new relevant UUID. '
+            + str(error)
         )
-        pack_group = None
-    if not settings.PACK_GROUP_NAME.lower() not in pack_group.name.lower():
-        logger.error(
+    if settings.PACK_GROUP_NAME.lower() not in pack_group.name.lower():
+        raise UpdateCatalogException(
             'The pack group name isn\'t matched with the set name:'
             f' Pack group name: {pack_group.name}\n'
             f' Set name: {settings.PACK_GROUP_NAME}\n'
             'Update the PACK_GROUP_NAME django settings variable to set the new relevant name.'
         )
-        pack_group = None
 
     return pack_group
 
@@ -70,12 +65,6 @@ def update_prices(packs: TagQuerySet):
 
 
 def main(*args, **kwargs):
-    pack_group = find_pack_group()
-    if not pack_group:
-        return
-
-    return
-
-    packs = pack_group.tags.all().prefetch_related('products')
+    packs = find_pack_group().tags.all().prefetch_related('products')
     update_in_packs(packs)
     update_prices(packs)
diff --git a/shopelectro/models.py b/shopelectro/models.py
--- a/shopelectro/models.py
+++ b/shopelectro/models.py
@@ -232,8 +232,15 @@ class Meta(pages_models.ModelPage.Meta):  # Ignore PycodestyleBear (E303)
     )
 
 
+class TagGroupManager(models.Manager):
+
+    def get_pack(self):
+        return self.get_queryset().get(uuid=settings.PACK_GROUP_UUID)
+
+
 class TagGroup(catalog_models.TagGroup):
-    pass
+
+    objects = TagGroupManager()
 
 
 class TagQuerySet(catalog_models.TagQuerySet):
@@ -244,7 +251,9 @@ def products(self):
 
 
 class TagManager(catalog_models.TagManager.from_queryset(TagQuerySet)):
-    pass
+
+    def get_packs(self):
+        return TagGroup.objects.get_pack().tags.all()
 
 
 class Tag(catalog_models.Tag):


Test patch:
diff --git a/shopelectro/management/commands/test_db.py b/shopelectro/management/commands/test_db.py
--- a/shopelectro/management/commands/test_db.py
+++ b/shopelectro/management/commands/test_db.py
@@ -40,14 +40,18 @@ def __init__(self):
         super(BaseCommand, self).__init__()
         self._product_id = 0
         self.group_names = [
-            'Напряжение', 'Сила тока',
-            'Мощность', settings.BRAND_TAG_GROUP_NAME,
+            'Напряжение',
+            'Сила тока',
+            'Мощность',
+            settings.BRAND_TAG_GROUP_NAME,
+            settings.PACK_GROUP_NAME,
         ]
         self.tag_names = [
             ['6 В', '24 В'],
             ['1.2 А', '10 А'],
             ['7.2 Вт', '240 Вт'],
             ['Apple', 'Microsoft'],
+            ['2 в блистере', '2 в стяжке'],
         ]
 
     def handle(self, *args, **options):
@@ -63,6 +67,7 @@ def handle(self, *args, **options):
         tags = self.create_tags(groups)
 
         self.create_products(deep_children, tags)
+        self.pack_products()
         self.create_page()
         self.create_order()
         self.create_feedbacks()
@@ -175,6 +180,9 @@ def create_tag_groups(self):
                 name=name,
                 position=i,
             )
+        pack = se_models.TagGroup.objects.get(name=settings.PACK_GROUP_NAME)
+        pack.uuid = settings.PACK_GROUP_UUID
+        pack.save()
 
     def create_tags(self, groups):
         def create_tag(group_, position, name):
@@ -241,6 +249,9 @@ def create_templates():
         se_models.ProductPage.objects.update(template=page_template)
         se_models.CategoryPage.objects.update(template=page_template)
 
+    def pack_products(self):
+        se_models.Tag.objects.get_packs().products().update(in_pack=2)
+
     @staticmethod
     def rebuild_mptt_tree():
         se_models.Category.objects.rebuild()
diff --git a/shopelectro/tests/tests_commands.py b/shopelectro/tests/tests_commands.py
--- a/shopelectro/tests/tests_commands.py
+++ b/shopelectro/tests/tests_commands.py
@@ -17,6 +17,7 @@
 from django.core.management import call_command
 from django.test import TestCase, override_settings, tag
 
+from shopelectro.exception import UpdateCatalogException
 from shopelectro.management.commands._update_catalog import (
     update_products, update_tags, update_pack,
 )
@@ -43,21 +44,46 @@ class UpdatePack(TestCase):
 
     fixtures = ['dump.json']
 
+    def assert_prices(self, old: Product, new: Product):
+        for price in update_pack.PRICES:
+            self.assertEqual(getattr(new, price), getattr(old, price) * new.in_pack)
+
     def test_command(self):
         """The command updates only packed products."""
-        # @todo #864:30m Test update_pack command.
+        products = list(Product.objects.all().order_by('id'))
+
+        update_pack.main()
+
+        for old, new in zip(products, Product.objects.all().order_by('id')):
+            self.assert_prices(old, new)
+
+    def test_find_pack_group(self):
+        pack = update_pack.find_pack_group()
+        self.assertEqual(str(pack.uuid), settings.PACK_GROUP_UUID)
+        self.assertEqual(pack.name, settings.PACK_GROUP_NAME)
+
+        pack.name = 'new name'
+        pack.save()
+        with self.assertRaises(UpdateCatalogException):
+            update_pack.find_pack_group()
+
+        pack.delete()
+        with self.assertRaises(UpdateCatalogException):
+            update_pack.find_pack_group()
 
     def test_update_in_packs(self):
         name_in_pack_map = {
-            'в пакете': 1,
-            '6+2 в блистере': 8,
-            '2 в блистере': 2,
-            '10 в стяжке': 10,
+            '? в упаковке': 1,
+            '1+2 в блистере': 3,
+            '42 в блистере': 42,
         }
 
-        for pack, name in zip(TagGroup.objects.first().tags.all(), name_in_pack_map):
-            pack.name = name
-            pack.save()
+        tags = Tag.objects.bulk_create([
+            Tag(name=name) for name in name_in_pack_map
+        ])
+        for product, pack in zip(Product.objects.filter(in_pack=1), tags):
+            product.tags.add(pack)
+            product.save()
 
         def get_packs():
             return Tag.objects.filter(name__in=name_in_pack_map)
@@ -73,20 +99,14 @@ def get_packs():
                 )
 
     def test_update_prices(self):
-        # @todo #859:60m Create fixture products with in_pack = 2
-        #  Resuse the fixture in related tests.
-
-        mul = 2
-        tags = TagGroup.objects.first().tags.all()
-        tags.products().update(in_pack=mul)
+        tags = Tag.objects.get_packs()
         products = list(tags.products())
 
         update_pack.update_prices(tags)
 
-        for new, old in zip(tags.products(), products):
-            self.assertEqual(new.id, old.id)
-            for price in update_pack.PRICES:
-                self.assertEqual(getattr(new, price), getattr(old, price) * mul)
+        for old, new in zip(products, tags.products()):
+            self.assertEqual(old.id, new.id)
+            self.assert_prices(old, new)
 
 
 @tag('fast')
diff --git a/shopelectro/tests/tests_views.py b/shopelectro/tests/tests_views.py
--- a/shopelectro/tests/tests_views.py
+++ b/shopelectro/tests/tests_views.py
@@ -23,8 +23,7 @@
 from catalog.helpers import reverse_catalog_url
 from pages import models as pages_models
 from pages.urls import reverse_custom_page
-from shopelectro import models
-from shopelectro import views
+from shopelectro import models, views
 from shopelectro.views.service import generate_md5_for_ya_kassa, YANDEX_REQUEST_PARAM
 
 CANONICAL_HTML_TAG = '<link rel="canonical" href="{base_url}{path}">'
@@ -877,28 +876,29 @@ class InPack(ViewsTestCase):
 
     fixtures = ['dump.json']
 
-    def pack_product(self):
-        product = models.Product.objects.first()
-        product.in_pack = 2
-        product.save()
-        return product
+    def test_category_packs(self):
+        category = (
+            models.Product.objects
+            .annotate(category_size=Count('category__products'))
+            .filter(in_pack__gt=1, category_size__lt=settings.PRODUCTS_ON_PAGE_PC)
+            .first()
+            .category
+        )
 
-    def assert_in_pack_units(self, soup, product):
+        soup = self.get_category_soup(category)
         results = soup.find_all(string=re.compile('упаковка'))
 
-        self.assertEqual(len(results), 1, results)
-        self.assertIn(str(int(product.price)), results[0], results[0])
-
-    def test_catalog_in_pack_units(self):
-        product = self.pack_product()
-        self.assert_in_pack_units(
-            self.get_category_soup(product.category),
-            product,
+        self.assertEqual(
+            len(results),
+            category.products.filter(in_pack__gt=1).count(),
+            results,
         )
 
-    def test_product_in_pack_units(self):
-        product = self.pack_product()
-        self.assert_in_pack_units(
-            self.get_product_soup(product),
-            product,
-        )
+    def test_product_pack(self):
+        product = models.Product.objects.filter(in_pack__gt=1).first()
+
+        soup = self.get_product_soup(product)
+        results = soup.find_all(string=re.compile('упаковка'))
+
+        self.assertEqual(len(results), 1, results)
+        self.assertIn(str(int(product.price)), results[0], results[0])