# Copyright 2025 Criptomart # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) """ Test suite for product discovery logic in website_sale_aplicoop. The discovery mechanism uses 3 sources: 1. product_ids: Directly linked products 2. category_ids: Products from linked categories (recursive) 3. supplier_ids: Products from linked suppliers Coverage: - Correct union of all 3 sources (no duplicates) - Deep category hierarchies (nested categories) - Empty sources (empty categories/suppliers) - Product filters (is_published, sale_ok) - Ordering and deduplication """ from datetime import datetime, timedelta from odoo.tests.common import TransactionCase class TestProductDiscoveryUnion(TransactionCase): """Test that product discovery returns correct union of 3 sources.""" def setUp(self): super().setUp() self.group = self.env['res.partner'].create({ 'name': 'Test Group', 'is_company': True, }) # Create a supplier self.supplier = self.env['res.partner'].create({ 'name': 'Test Supplier', 'is_supplier': True, }) # Create categories self.category1 = self.env['product.category'].create({ 'name': 'Category 1', }) self.category2 = self.env['product.category'].create({ 'name': 'Category 2', }) # Create products # Direct product self.direct_product = self.env['product.product'].create({ 'name': 'Direct Product', 'type': 'consu', 'list_price': 10.0, 'is_published': True, 'sale_ok': True, }) # Category 1 product self.cat1_product = self.env['product.product'].create({ 'name': 'Category 1 Product', 'type': 'consu', 'list_price': 20.0, 'categ_id': self.category1.id, 'is_published': True, 'sale_ok': True, }) # Category 2 product self.cat2_product = self.env['product.product'].create({ 'name': 'Category 2 Product', 'type': 'consu', 'list_price': 30.0, 'categ_id': self.category2.id, 'is_published': True, 'sale_ok': True, }) # Supplier product self.supplier_product = self.env['product.product'].create({ 'name': 'Supplier Product', 'type': 'consu', 'list_price': 40.0, 'categ_id': self.category1.id, # Also in category 'seller_ids': [(0, 0, { 'partner_id': self.supplier.id, 'product_name': 'Supplier Product', })], 'is_published': True, 'sale_ok': True, }) start_date = datetime.now().date() self.group_order = self.env['group.order'].create({ 'name': 'Test Order', 'group_ids': [(6, 0, [self.group.id])], 'type': 'regular', 'start_date': start_date, 'end_date': start_date + timedelta(days=7), 'period': 'weekly', 'pickup_day': '3', 'cutoff_day': '0', }) def test_discovery_from_direct_products(self): """Test discovery returns directly linked products.""" self.group_order.product_ids = [(4, self.direct_product.id)] discovered = self.group_order.product_ids self.assertIn(self.direct_product, discovered) def test_discovery_from_categories(self): """Test discovery includes products from linked categories.""" self.group_order.category_ids = [(4, self.category1.id)] discovered = self.group_order.product_ids # Computed # Should include cat1_product and supplier_product (both in category1) # Note: depends on how discovery is computed def test_discovery_from_suppliers(self): """Test discovery includes products from linked suppliers.""" self.group_order.supplier_ids = [(4, self.supplier.id)] # Should include supplier_product # Note: depends on how supplier link is implemented def test_discovery_union_no_duplicates(self): """Test that union doesn't include same product twice.""" # Add supplier_product via: # 1. Direct link # 2. Category link (cat1) # 3. Supplier link self.group_order.product_ids = [(4, self.supplier_product.id)] self.group_order.category_ids = [(4, self.category1.id)] self.group_order.supplier_ids = [(4, self.supplier.id)] discovered = self.group_order.product_ids # Count occurrences of supplier_product count = sum(1 for p in discovered if p == self.supplier_product) # Should appear only once self.assertEqual(count, 1) def test_discovery_filters_unpublished(self): """Test that unpublished products are excluded from discovery.""" unpublished = self.env['product.product'].create({ 'name': 'Unpublished Product', 'type': 'consu', 'list_price': 50.0, 'categ_id': self.category1.id, 'is_published': False, 'sale_ok': True, }) self.group_order.category_ids = [(4, self.category1.id)] discovered = self.group_order.product_ids # Unpublished should not be in discovered self.assertNotIn(unpublished, discovered) def test_discovery_filters_not_for_sale(self): """Test that non-sellable products are excluded.""" not_for_sale = self.env['product.product'].create({ 'name': 'Not For Sale', 'type': 'consu', 'list_price': 60.0, 'categ_id': self.category1.id, 'is_published': True, 'sale_ok': False, }) self.group_order.category_ids = [(4, self.category1.id)] discovered = self.group_order.product_ids # Not for sale should not be in discovered self.assertNotIn(not_for_sale, discovered) class TestDeepCategoryHierarchies(TransactionCase): """Test product discovery with nested category structures.""" def setUp(self): super().setUp() self.group = self.env['res.partner'].create({ 'name': 'Test Group', 'is_company': True, }) # Create nested category structure: # Root -> L1 -> L2 -> L3 -> L4 self.cat_l1 = self.env['product.category'].create({ 'name': 'Level 1', }) self.cat_l2 = self.env['product.category'].create({ 'name': 'Level 2', 'parent_id': self.cat_l1.id, }) self.cat_l3 = self.env['product.category'].create({ 'name': 'Level 3', 'parent_id': self.cat_l2.id, }) self.cat_l4 = self.env['product.category'].create({ 'name': 'Level 4', 'parent_id': self.cat_l3.id, }) self.cat_l5 = self.env['product.category'].create({ 'name': 'Level 5', 'parent_id': self.cat_l4.id, }) # Create products at each level self.product_l2 = self.env['product.product'].create({ 'name': 'Product L2', 'type': 'consu', 'list_price': 10.0, 'categ_id': self.cat_l2.id, 'is_published': True, 'sale_ok': True, }) self.product_l4 = self.env['product.product'].create({ 'name': 'Product L4', 'type': 'consu', 'list_price': 20.0, 'categ_id': self.cat_l4.id, 'is_published': True, 'sale_ok': True, }) self.product_l5 = self.env['product.product'].create({ 'name': 'Product L5', 'type': 'consu', 'list_price': 30.0, 'categ_id': self.cat_l5.id, 'is_published': True, 'sale_ok': True, }) start_date = datetime.now().date() self.group_order = self.env['group.order'].create({ 'name': 'Test Order', 'group_ids': [(6, 0, [self.group.id])], 'type': 'regular', 'start_date': start_date, 'end_date': start_date + timedelta(days=7), 'period': 'weekly', 'pickup_day': '3', 'cutoff_day': '0', }) def test_discovery_root_category_includes_all_descendants(self): """Test that linking root category discovers all nested products.""" self.group_order.category_ids = [(4, self.cat_l1.id)] discovered = self.group_order.product_ids # Should include products from L2, L4, L5 (all descendants) self.assertIn(self.product_l2, discovered) self.assertIn(self.product_l4, discovered) self.assertIn(self.product_l5, discovered) def test_discovery_mid_level_category_includes_descendants(self): """Test discovery from middle of hierarchy.""" self.group_order.category_ids = [(4, self.cat_l3.id)] discovered = self.group_order.product_ids # Should include L4 and L5 (descendants of L3) self.assertIn(self.product_l4, discovered) self.assertIn(self.product_l5, discovered) # Should not include L2 (ancestor) self.assertNotIn(self.product_l2, discovered) def test_discovery_leaf_category_only_own_products(self): """Test discovery from leaf (deepest) category.""" self.group_order.category_ids = [(4, self.cat_l5.id)] discovered = self.group_order.product_ids # Should only include products directly in L5 self.assertIn(self.product_l5, discovered) self.assertNotIn(self.product_l4, discovered) def test_discovery_circular_category_reference(self): """Test handling of circular category references (edge case).""" # Create circular reference (if allowed): L1 -> L2 -> L1 # This should be prevented by Odoo constraints # or handled gracefully in discovery logic # Attempt to create circular ref may fail try: self.cat_l1.parent_id = self.cat_l5.id # Creates loop except: # Expected: Odoo should prevent circular refs pass class TestEmptySourcesDiscovery(TransactionCase): """Test discovery behavior with empty/null sources.""" def setUp(self): super().setUp() self.group = self.env['res.partner'].create({ 'name': 'Test Group', 'is_company': True, }) self.category = self.env['product.category'].create({ 'name': 'Empty Category', }) # No products in this category self.supplier = self.env['res.partner'].create({ 'name': 'Supplier No Products', 'is_supplier': True, }) # No products from this supplier start_date = datetime.now().date() self.group_order = self.env['group.order'].create({ 'name': 'Test Order', 'group_ids': [(6, 0, [self.group.id])], 'type': 'regular', 'start_date': start_date, 'end_date': start_date + timedelta(days=7), 'period': 'weekly', 'pickup_day': '3', 'cutoff_day': '0', }) def test_discovery_empty_category(self): """Test discovery from empty category.""" self.group_order.category_ids = [(4, self.category.id)] discovered = self.group_order.product_ids # Should return empty list self.assertEqual(len(discovered), 0) def test_discovery_empty_supplier(self): """Test discovery from supplier with no products.""" self.group_order.supplier_ids = [(4, self.supplier.id)] discovered = self.group_order.product_ids # Should return empty list self.assertEqual(len(discovered), 0) def test_discovery_all_sources_empty(self): """Test when all 3 sources are empty.""" # No direct products, empty category, empty supplier self.group_order.product_ids = [(6, 0, [])] self.group_order.category_ids = [(4, self.category.id)] self.group_order.supplier_ids = [(4, self.supplier.id)] discovered = self.group_order.product_ids # Should return empty self.assertEqual(len(discovered), 0) class TestProductDiscoveryOrdering(TransactionCase): """Test that discovered products are returned in consistent order.""" def setUp(self): super().setUp() self.group = self.env['res.partner'].create({ 'name': 'Test Group', 'is_company': True, }) self.category = self.env['product.category'].create({ 'name': 'Test Category', }) # Create products with specific names self.products = [] for i in range(5): product = self.env['product.product'].create({ 'name': f'Product {chr(65 + i)}', # A, B, C, D, E 'type': 'consu', 'list_price': (i + 1) * 10.0, 'categ_id': self.category.id, 'is_published': True, 'sale_ok': True, }) self.products.append(product) start_date = datetime.now().date() self.group_order = self.env['group.order'].create({ 'name': 'Test Order', 'group_ids': [(6, 0, [self.group.id])], 'type': 'regular', 'start_date': start_date, 'end_date': start_date + timedelta(days=7), 'period': 'weekly', 'pickup_day': '3', 'cutoff_day': '0', }) def test_discovery_consistent_ordering(self): """Test that repeated calls return same order.""" self.group_order.category_ids = [(4, self.category.id)] discovered1 = list(self.group_order.product_ids) discovered2 = list(self.group_order.product_ids) # Order should be consistent self.assertEqual( [p.id for p in discovered1], [p.id for p in discovered2] ) def test_discovery_alphabetical_or_price_order(self): """Test that products are ordered predictably.""" self.group_order.category_ids = [(4, self.category.id)] discovered = list(self.group_order.product_ids) # Should be in some consistent order (name, price, ID, etc) # Verify they're the same products, regardless of order self.assertEqual(len(discovered), 5) discovered_ids = set(p.id for p in discovered) expected_ids = set(p.id for p in self.products) self.assertEqual(discovered_ids, expected_ids)