Spaces:
Running
Running
| #!/usr/bin/env python3 | |
| """ | |
| Complete end-to-end taxonomy implementation test script. | |
| Tests all taxonomy categories from the provided JSON structure. | |
| This script demonstrates: | |
| 1. Creating taxonomy data with all categories | |
| 2. Using projection lists for performance optimization | |
| 3. Filtering by specific taxonomy types | |
| 4. UOM conversions management | |
| 5. CRUD operations on taxonomy data | |
| Based on the JSON structure: | |
| { | |
| "merchant_id": "company_cuatro_beauty_ltd", | |
| "brands": ["L'OrΓ©al Professional", "Schwarzkopf", "Kerastase", ...], | |
| "categories": ["Hair", "Skin", "Nails", "Spa", "Makeup", ...], | |
| "lines": ["Hair Care", "Hair Styling", "Facials", ...], | |
| "classes": ["Premium", "Luxury", "Basic", "VIP"], | |
| "subcategories": {"Hair": ["Hair Cut", "Hair Color", ...], ...}, | |
| "specializations": ["Hairdresser", "Nail technician", ...], | |
| "job_role": ["Senior Stylist", "Director", ...], | |
| "languages": ["English", "Hindi", "Tamil"], | |
| "customer_group": ["VIP", "Regular", "Premium", ...], | |
| "pos_tender_modes": ["Cash", "Credit Card", ...], | |
| "payment_types": ["Cash and Carry", "Credit"], | |
| "payment_methods": ["Bank Transfer", "Cash", "Cheque"], | |
| "asset_location": ["Store Floor", "Warehouse", ...], | |
| "asset_category": ["Electronics", "Furniture", "Equipment"], | |
| "stock_bin_location": ["A1-01", "B2-15", ...], | |
| "branch_types": ["Flagship", "Outlet", "Pop-up", "Franchise"], | |
| "expense_types": ["Travel", "Office Supplies"], | |
| "uom_conversions": [...] | |
| } | |
| """ | |
| import asyncio | |
| import json | |
| import sys | |
| import os | |
| from datetime import datetime | |
| from typing import Dict, List, Any | |
| # Add the app directory to Python path | |
| sys.path.append(os.path.join(os.path.dirname(__file__), 'app')) | |
| from motor.motor_asyncio import AsyncIOMotorClient | |
| from app.taxonomy.services.service import TaxonomyService | |
| from app.taxonomy.models.model import TaxonomyModel | |
| from app.taxonomy.schemas.schema import TaxonomyInfo, TaxonomyListRequest, UOMConversionGroup, UOMConversionDetail | |
| from app.constants.collections import SCM_TAXONOMY_COLLECTION | |
| from app.core.config import settings as app_settings | |
| class TaxonomyTestRunner: | |
| """Complete taxonomy implementation test runner.""" | |
| def __init__(self): | |
| self.client = None | |
| self.db = None | |
| self.service = None | |
| self.test_merchant_id = "company_cuatro_beauty_ltd" | |
| async def setup(self): | |
| """Setup test environment.""" | |
| print("π§ Setting up test environment...") | |
| # Create database connection | |
| self.client = AsyncIOMotorClient(app_settings.MONGODB_URI) | |
| self.db = self.client["scm_test_complete_taxonomy"] | |
| # Create service instance | |
| self.service = TaxonomyService.__new__(TaxonomyService) | |
| self.service.db = self.db | |
| self.service.model = TaxonomyModel.__new__(TaxonomyModel) | |
| self.service.model.db = self.db | |
| self.service.model.collection = self.db[SCM_TAXONOMY_COLLECTION] | |
| # Clear any existing test data | |
| await self.db[SCM_TAXONOMY_COLLECTION].delete_many({"merchant_id": self.test_merchant_id}) | |
| print("β Test environment setup complete") | |
| async def cleanup(self): | |
| """Cleanup test environment.""" | |
| print("π§Ή Cleaning up test environment...") | |
| try: | |
| await self.db[SCM_TAXONOMY_COLLECTION].delete_many({"merchant_id": self.test_merchant_id}) | |
| self.client.close() | |
| print("β Cleanup complete") | |
| except Exception as e: | |
| print(f"β οΈ Cleanup warning: {e}") | |
| def create_complete_taxonomy_data(self) -> TaxonomyInfo: | |
| """Create complete taxonomy data based on the provided JSON structure.""" | |
| # UOM conversions from the JSON | |
| uom_conversions = [ | |
| UOMConversionGroup( | |
| base_uom="g", | |
| conversions=[ | |
| UOMConversionDetail( | |
| alt_uom="kg", | |
| factor=1000.0, | |
| description="1 kg = 1000 g" | |
| ) | |
| ] | |
| ), | |
| UOMConversionGroup( | |
| base_uom="ml", | |
| conversions=[ | |
| UOMConversionDetail( | |
| alt_uom="L", | |
| factor=1000.0, | |
| description="1 L = 1000 ml" | |
| ), | |
| UOMConversionDetail( | |
| alt_uom="bottle", | |
| factor=650.0, | |
| description="650 ml bottle" | |
| ) | |
| ] | |
| ), | |
| UOMConversionGroup( | |
| base_uom="pcs", | |
| conversions=[ | |
| UOMConversionDetail( | |
| alt_uom="dozen", | |
| factor=12.0, | |
| description="1 dozen = 12 pcs" | |
| ), | |
| UOMConversionDetail( | |
| alt_uom="pack", | |
| factor=6.0, | |
| description="1 pack = 6 pcs (example)" | |
| ) | |
| ] | |
| ), | |
| UOMConversionGroup( | |
| base_uom="hr", | |
| conversions=[] | |
| ), | |
| UOMConversionGroup( | |
| base_uom="L", | |
| conversions=[ | |
| UOMConversionDetail( | |
| alt_uom="bottle", | |
| factor=1.0, | |
| description="1 bottle = 1 L (example SKU)" | |
| ) | |
| ] | |
| ) | |
| ] | |
| # Subcategories from the JSON | |
| subcategories = { | |
| "Hair": ["Hair Cut", "Hair Color", "Hair Spa", "Keratin Treatment", "Hair Smoothening"], | |
| "Skin": ["Clean-up", "Facial", "De-Tan", "Peel Treatments", "Skin Brightening"], | |
| "Nails": ["Manicure", "Pedicure", "Nail Art", "Gel Polish"], | |
| "Spa": ["Head Massage", "Full Body Massage", "Aromatherapy", "Deep Tissue Massage"], | |
| "Makeup": ["Bridal Makeup", "Party Makeup"], | |
| "Foot": ["Pedicure"], | |
| "H": ["Serum"] | |
| } | |
| return TaxonomyInfo( | |
| merchant_id=self.test_merchant_id, | |
| # Product Taxonomies | |
| brands=[ | |
| "L'OrΓ©al Professional", "Schwarzkopf", "Kerastase", "Olaplex", "Lotus", | |
| "b1", "g", " Services", "Brand1", "Brand2", "Brand3", "Brand4", | |
| "Glory", "NewBrand", "vb", "" | |
| ], | |
| categories=[ | |
| "Hair", "Skin", "Nails", "Spa", "Makeup", "Foot", "ea", "saddle", | |
| "d", "Glory", "Dummy123", "Alpha1", "Category1", "Hello", | |
| "test-category", "Child Hair Stylist", "Hair color", "hair sap" | |
| ], | |
| lines=[ | |
| "Hair Care", "Hair Styling", "Facials", "Body Treatments", | |
| "Manicure", "Pedicure", "Ayurveda", "GLoryt" | |
| ], | |
| classes=["Premium", "Luxury", "Basic", "Class1", "Class2", "VIP"], | |
| subcategories=subcategories, | |
| # Employee Taxonomies | |
| job_role=["Senior Stylist", "Director", "Massage therapist", "Esthetician"], | |
| specializations=["Hairdresser", "Nail technician", "Massage therapist", "Esthetician", "hair style"], | |
| languages=["English", "Hindi", "Tamil"], | |
| # Customer Taxonomies | |
| customer_group=["VIP", "Regular", "Premium", "Enterprise", "Gold"], | |
| # Operational Taxonomies - POS & Payment | |
| pos_tender_modes=["Cash", "Credit Card", "Debit Card", "Mobile Payment"], | |
| payment_types=["Cash and Carry", "Credit"], | |
| payment_methods=["Bank Transfer", "Cash", "Cheque"], | |
| # Operational Taxonomies - Asset & Inventory | |
| asset_location=["Store Floor", "Warehouse", "Back Office"], | |
| asset_category=["Electronics", "Furniture", "Equipment"], | |
| stock_bin_location=["A1-01", "B2-15", "C3-07", "Receiving"], | |
| # Operational Taxonomies - Business Structure | |
| branch_types=["Flagship", "Outlet", "Pop-up", "Franchise"], | |
| expense_types=["Travel", "Office Supplies"], | |
| # UOM Conversions | |
| uom_conversions=uom_conversions, | |
| # Metadata | |
| created_by="AST011", | |
| created_at=datetime(2025, 8, 19, 9, 50, 11, 927000), | |
| updated_at=datetime(2025, 12, 12, 6, 31, 8, 509000) | |
| ) | |
| async def test_create_complete_taxonomy(self): | |
| """Test 1: Create complete taxonomy data.""" | |
| print("\nπ Test 1: Creating complete taxonomy data...") | |
| taxonomy_data = self.create_complete_taxonomy_data() | |
| result = await self.service.create_or_update_taxonomy( | |
| data=taxonomy_data, | |
| created_by="AST011" | |
| ) | |
| assert result["success"] is True | |
| assert result["operation"] == "create" | |
| assert "taxonomy_id" in result | |
| print(f"β Created taxonomy with ID: {result['taxonomy_id']}") | |
| print(f" Operation: {result['operation']}") | |
| return result["taxonomy_id"] | |
| async def test_list_complete_taxonomy(self, taxonomy_id: str): | |
| """Test 2: List complete taxonomy data without projection.""" | |
| print("\nπ Test 2: Listing complete taxonomy data...") | |
| request = TaxonomyListRequest(merchant_id=self.test_merchant_id) | |
| result = await self.service.list_taxonomy(request) | |
| assert result["success"] is True | |
| assert result["projection_applied"] is False | |
| assert result["merchant_id"] == self.test_merchant_id | |
| data = result["data"] | |
| print(f"β Retrieved complete taxonomy data") | |
| print(f" Data type: {type(data)}") | |
| print(f" Has taxonomies: {'taxonomies' in data if isinstance(data, dict) else 'N/A'}") | |
| if isinstance(data, dict) and "taxonomies" in data: | |
| taxonomies = data["taxonomies"] | |
| print(f" Available taxonomy types: {list(taxonomies.keys())}") | |
| print(f" Brands count: {len(taxonomies.get('brands', []))}") | |
| print(f" Categories count: {len(taxonomies.get('categories', []))}") | |
| print(f" UOM conversions count: {len(taxonomies.get('uom_conversions', []))}") | |
| return data | |
| async def test_projection_list_performance(self): | |
| """Test 3: Test projection list for performance optimization.""" | |
| print("\nπ Test 3: Testing projection list performance...") | |
| # Test with specific fields projection | |
| projection_fields = ["merchant_id", "brands", "categories", "job_role", "created_at"] | |
| request = TaxonomyListRequest( | |
| merchant_id=self.test_merchant_id, | |
| projection_list=projection_fields | |
| ) | |
| result = await self.service.list_taxonomy(request) | |
| assert result["success"] is True | |
| assert result["projection_applied"] is True | |
| data = result["data"] | |
| print(f"β Projection applied successfully") | |
| print(f" Requested fields: {projection_fields}") | |
| print(f" Data type: {type(data)}") | |
| if isinstance(data, list) and data: | |
| doc = data[0] | |
| print(f" Returned fields: {list(doc.keys())}") | |
| # Verify only requested fields are present (plus any defaults) | |
| for field in projection_fields: | |
| if field in ["brands", "categories", "job_role"] and doc.get(field): | |
| print(f" β {field}: {len(doc[field])} items") | |
| return data | |
| async def test_taxonomy_type_filtering(self): | |
| """Test 4: Test filtering by specific taxonomy type.""" | |
| print("\nπ Test 4: Testing taxonomy type filtering...") | |
| # Test filtering by brands | |
| request = TaxonomyListRequest( | |
| merchant_id=self.test_merchant_id, | |
| taxonomy_type="brands" | |
| ) | |
| result = await self.service.list_taxonomy(request) | |
| assert result["success"] is True | |
| assert result["taxonomy_type"] == "brands" | |
| data = result["data"] | |
| print(f"β Filtered by taxonomy type: brands") | |
| print(f" Data type: {type(data)}") | |
| if isinstance(data, list) and data: | |
| doc = data[0] | |
| print(f" Document keys: {list(doc.keys())}") | |
| if "data" in doc: | |
| print(f" Brands count: {len(doc['data'])}") | |
| print(f" Sample brands: {doc['data'][:3]}") | |
| # Test filtering by UOM conversions | |
| request_uom = TaxonomyListRequest( | |
| merchant_id=self.test_merchant_id, | |
| taxonomy_type="uom_conversions" | |
| ) | |
| result_uom = await self.service.list_taxonomy(request_uom) | |
| assert result_uom["success"] is True | |
| print(f"β Filtered by taxonomy type: uom_conversions") | |
| return data | |
| async def test_projection_with_taxonomy_type(self): | |
| """Test 5: Test projection combined with taxonomy type filtering.""" | |
| print("\nπ― Test 5: Testing projection + taxonomy type filtering...") | |
| request = TaxonomyListRequest( | |
| merchant_id=self.test_merchant_id, | |
| taxonomy_type="categories", | |
| projection_list=["merchant_id", "categories", "created_at"] | |
| ) | |
| result = await self.service.list_taxonomy(request) | |
| assert result["success"] is True | |
| assert result["projection_applied"] is True | |
| assert result["taxonomy_type"] == "categories" | |
| data = result["data"] | |
| print(f"β Combined projection + filtering applied") | |
| print(f" Taxonomy type: categories") | |
| print(f" Projection fields: merchant_id, categories, created_at") | |
| print(f" Data type: {type(data)}") | |
| if isinstance(data, list) and data: | |
| doc = data[0] | |
| print(f" Returned fields: {list(doc.keys())}") | |
| return data | |
| async def test_uom_conversions_management(self): | |
| """Test 6: Test UOM conversions CRUD operations.""" | |
| print("\nβοΈ Test 6: Testing UOM conversions management...") | |
| # Test getting UOM conversions | |
| uom_result = await self.service.get_uom_conversions( | |
| merchant_id=self.test_merchant_id | |
| ) | |
| assert uom_result["success"] is True | |
| uom_data = uom_result["data"] | |
| print(f"β Retrieved UOM conversions") | |
| print(f" Conversion groups: {len(uom_data)}") | |
| print(f" Is default: {uom_result.get('is_default', False)}") | |
| # Print sample conversions | |
| for i, group in enumerate(uom_data[:2]): # Show first 2 groups | |
| if isinstance(group, dict): | |
| base_uom = group.get("base_uom") | |
| conversions = group.get("conversions", []) | |
| print(f" Group {i+1}: {base_uom} -> {len(conversions)} conversions") | |
| if conversions: | |
| sample = conversions[0] | |
| print(f" Sample: {sample.get('alt_uom')} (factor: {sample.get('factor')})") | |
| return uom_data | |
| async def test_update_taxonomy_data(self): | |
| """Test 7: Test updating taxonomy data.""" | |
| print("\nβοΈ Test 7: Testing taxonomy data updates...") | |
| # Add new brands | |
| update_data = TaxonomyInfo( | |
| merchant_id=self.test_merchant_id, | |
| brands=["New Brand 1", "New Brand 2", "Updated Brand"], | |
| categories=["New Category"], | |
| expense_types=["Marketing", "Training"], | |
| created_by="AST014" | |
| ) | |
| result = await self.service.create_or_update_taxonomy( | |
| data=update_data, | |
| created_by="AST014" | |
| ) | |
| assert result["success"] is True | |
| assert result["operation"] == "update" | |
| print(f"β Updated taxonomy data") | |
| print(f" Operation: {result['operation']}") | |
| print(f" Modified count: {result.get('modified_count', 0)}") | |
| # Verify the update | |
| request = TaxonomyListRequest( | |
| merchant_id=self.test_merchant_id, | |
| projection_list=["brands", "categories", "expense_types"] | |
| ) | |
| verify_result = await self.service.list_taxonomy(request) | |
| data = verify_result["data"] | |
| if isinstance(data, list) and data: | |
| doc = data[0] | |
| brands = doc.get("brands", []) | |
| expense_types = doc.get("expense_types", []) | |
| print(f" Updated brands count: {len(brands)}") | |
| print(f" Updated expense_types count: {len(expense_types)}") | |
| # Check if new items were added | |
| new_brands = ["New Brand 1", "New Brand 2", "Updated Brand"] | |
| new_expenses = ["Marketing", "Training"] | |
| for brand in new_brands: | |
| if brand in brands: | |
| print(f" β Added brand: {brand}") | |
| for expense in new_expenses: | |
| if expense in expense_types: | |
| print(f" β Added expense type: {expense}") | |
| return result | |
| async def test_delete_taxonomy_values(self): | |
| """Test 8: Test deleting specific taxonomy values.""" | |
| print("\nποΈ Test 8: Testing taxonomy value deletion...") | |
| # Delete specific brands using the delete flag | |
| delete_data = TaxonomyInfo( | |
| merchant_id=self.test_merchant_id, | |
| brands=["New Brand 1", "Updated Brand"], # These will be deleted | |
| is_delete=True, | |
| created_by="AST014" | |
| ) | |
| result = await self.service.create_or_update_taxonomy( | |
| data=delete_data, | |
| created_by="AST014" | |
| ) | |
| assert result["success"] is True | |
| assert result["operation"] == "delete" | |
| print(f"β Deleted taxonomy values") | |
| print(f" Operation: {result['operation']}") | |
| print(f" Modified count: {result.get('modified_count', 0)}") | |
| # Verify the deletion | |
| request = TaxonomyListRequest( | |
| merchant_id=self.test_merchant_id, | |
| projection_list=["brands"] | |
| ) | |
| verify_result = await self.service.list_taxonomy(request) | |
| data = verify_result["data"] | |
| if isinstance(data, list) and data: | |
| doc = data[0] | |
| brands = doc.get("brands", []) | |
| print(f" Remaining brands count: {len(brands)}") | |
| # Check if items were deleted | |
| deleted_brands = ["New Brand 1", "Updated Brand"] | |
| for brand in deleted_brands: | |
| if brand not in brands: | |
| print(f" β Deleted brand: {brand}") | |
| else: | |
| print(f" β οΈ Brand still exists: {brand}") | |
| return result | |
| async def test_performance_comparison(self): | |
| """Test 9: Compare performance with and without projection.""" | |
| print("\nβ‘ Test 9: Performance comparison...") | |
| import time | |
| # Test without projection (full data) | |
| start_time = time.time() | |
| request_full = TaxonomyListRequest(merchant_id=self.test_merchant_id) | |
| result_full = await self.service.list_taxonomy(request_full) | |
| full_time = time.time() - start_time | |
| # Test with projection (limited fields) | |
| start_time = time.time() | |
| request_projected = TaxonomyListRequest( | |
| merchant_id=self.test_merchant_id, | |
| projection_list=["merchant_id", "brands", "categories"] | |
| ) | |
| result_projected = await self.service.list_taxonomy(request_projected) | |
| projected_time = time.time() - start_time | |
| print(f"β Performance comparison completed") | |
| print(f" Full data query time: {full_time:.4f}s") | |
| print(f" Projected query time: {projected_time:.4f}s") | |
| # Calculate data size difference (rough estimate) | |
| full_data = result_full["data"] | |
| projected_data = result_projected["data"] | |
| if isinstance(full_data, dict) and isinstance(projected_data, list): | |
| full_str = json.dumps(full_data, default=str) | |
| projected_str = json.dumps(projected_data, default=str) | |
| full_size = len(full_str) | |
| projected_size = len(projected_str) | |
| size_reduction = ((full_size - projected_size) / full_size) * 100 if full_size > 0 else 0 | |
| print(f" Full data size: {full_size} chars") | |
| print(f" Projected data size: {projected_size} chars") | |
| print(f" Size reduction: {size_reduction:.1f}%") | |
| return { | |
| "full_time": full_time, | |
| "projected_time": projected_time, | |
| "full_data": full_data, | |
| "projected_data": projected_data | |
| } | |
| async def run_all_tests(self): | |
| """Run all taxonomy tests.""" | |
| print("π Starting Complete Taxonomy Implementation Tests") | |
| print("=" * 60) | |
| try: | |
| await self.setup() | |
| # Run all tests in sequence | |
| taxonomy_id = await self.test_create_complete_taxonomy() | |
| await self.test_list_complete_taxonomy(taxonomy_id) | |
| await self.test_projection_list_performance() | |
| await self.test_taxonomy_type_filtering() | |
| await self.test_projection_with_taxonomy_type() | |
| await self.test_uom_conversions_management() | |
| await self.test_update_taxonomy_data() | |
| await self.test_delete_taxonomy_values() | |
| await self.test_performance_comparison() | |
| print("\n" + "=" * 60) | |
| print("π All tests completed successfully!") | |
| print("\nπ Test Summary:") | |
| print("β Complete taxonomy creation") | |
| print("β Full data retrieval") | |
| print("β Projection list optimization") | |
| print("β Taxonomy type filtering") | |
| print("β Combined projection + filtering") | |
| print("β UOM conversions management") | |
| print("β Data updates") | |
| print("β Value deletion") | |
| print("β Performance comparison") | |
| except Exception as e: | |
| print(f"\nβ Test failed with error: {e}") | |
| import traceback | |
| traceback.print_exc() | |
| finally: | |
| await self.cleanup() | |
| async def main(): | |
| """Main test runner.""" | |
| runner = TaxonomyTestRunner() | |
| await runner.run_all_tests() | |
| if __name__ == "__main__": | |
| asyncio.run(main()) |