import os import sys import django from django.test import Client from django.core.files.uploadedfile import SimpleUploadedFile from decimal import Decimal # Set up Django environment sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') django.setup() from django.conf import settings if 'testserver' not in settings.ALLOWED_HOSTS: settings.ALLOWED_HOSTS.append('testserver') from django.contrib.auth.models import User from expenses.models import Project, Category, Vendor, Expense, Document, SystemSetting, CurrencyEnum, TaxTypeEnum, ApprovalStatusEnum def run_test(): print("=" * 60) print("EFINTEL ERP — Expense Creation Flow Integration Test") print("=" * 60) # 1. Setup client and login print("Step 1: Setting up Django test Client & Authenticating...") client = Client() admin_user = User.objects.filter(username='admin').first() if not admin_user: print("Admin user 'admin' not found in database. Creating user...") admin_user = User.objects.create_superuser('admin', 'admin@example.com', 'admin') print("Admin user created successfully.") client.force_login(admin_user) print(f"Authenticated successfully as: {admin_user.username} (ID: {admin_user.id})") # Fetch required objects print("\nFetching Project (PRY-101) and Category (1201)...") project = Project.objects.filter(code='PRY-101').first() if not project: raise ValueError("Project PRY-101 not found! Please run project seed scripts first.") print(f"Found Project: {project.code} - '{project.name}' (ID: {project.id})") category = Category.objects.filter(code='1201').first() if not category: raise ValueError("Category 1201 not found! Please run category seed scripts first.") print(f"Found Category: {category.code} - '{category.name}' (ID: {category.id})") # Fetch/create SystemSetting to get exchange rate setting, created = SystemSetting.objects.get_or_create(id=1, defaults={'usd_exchange_rate': 950.0}) usd_rate = setting.usd_exchange_rate print(f"System setting exchange rate is: {usd_rate}") # Prepare Mock Files print("\nPreparing mock evidence files for upload...") file1 = SimpleUploadedFile("receipt1.pdf", b"pdf content mock", content_type="application/pdf") file2 = SimpleUploadedFile("receipt2.png", b"png content mock", content_type="image/png") # 2. Prepare POST data print("\nStep 2: Preparing POST request data for '/gastos/nuevo/'...") post_data = { 'project': project.id, 'category': category.id, 'amount_clp': '2.500.000', 'tax_type': TaxTypeEnum.IVA_19, 'tax_amount_clp': '475.000', # 19% of 2.500.000 'total_amount_clp': '2.975.000', # 2.500.000 + 475.000 'currency': CurrencyEnum.CLP, 'responsible_person': admin_user.id, 'date': '2026-08-21', 'description': 'Servicios de consultoría de prototipado rápido y PCBs', 'new_vendor_rut': '77.888.999-5', 'new_vendor_name': 'CONEXIONES SPA', 'toggle-new-vendor': 'on', 'region': 'Región Metropolitana', 'comuna': 'Santiago', 'receipt': [file1, file2], } # Clean existing data to avoid conflicts and guarantee clean assertion print("Cleaning any existing vendor/expense with target RUT to avoid duplication conflicts...") Vendor.objects.filter(rut='77.888.999-5').delete() # 3. Execute POST request print("\nStep 3: Executing POST request to '/gastos/nuevo/'...") response = client.post('/gastos/nuevo/', post_data, follow=False) # 4. Verify results print("\nStep 4: Verifying results...") # 4.1 Response Status and Redirect status_code = response.status_code location = response.get('Location', '') print(f" [Assertion 1] Response Status Code: {status_code} (Expected: 302)") print(f" [Assertion 1] Redirect Location: {location} (Expected: /gastos/)") assert status_code == 302, f"Expected status code 302, got {status_code}" assert '/gastos/' in location, f"Expected redirect to '/gastos/', got '{location}'" print(" => Response redirect validation: PASSED") # 4.2 New Vendor creation vendor = Vendor.objects.filter(rut='77.888.999-5').first() assert vendor is not None, "Vendor was not created!" print(f" [Assertion 2] Vendor Created: RUT={vendor.rut}, Name='{vendor.name}' (Expected: 'CONEXIONES SPA')") assert vendor.name == 'CONEXIONES SPA', f"Expected name 'CONEXIONES SPA', got '{vendor.name}'" print(" => Vendor DB validation: PASSED") # 4.3 New Expense creation expense = Expense.objects.filter(vendor=vendor, date='2026-08-21').first() assert expense is not None, "Expense was not created!" print(f" [Assertion 3] Expense Created with net amount={expense.amount_clp} CLP (Expected: 2500000.00 CLP)") assert expense.amount_clp == Decimal('2500000.00'), f"Expected net amount 2500000.00, got {expense.amount_clp}" print(" => Expense DB amount validation: PASSED") # 4.4 Associated Document verification documents = list(expense.documents.all()) print(f" [Assertion 4] Associated Documents Count: {len(documents)} (Expected: 2)") assert len(documents) == 2, f"Expected 2 documents, found {len(documents)}" filenames = [os.path.basename(doc.file.name) for doc in documents] print(f" [Assertion 4] Document filenames in DB: {filenames} (Expected containing 'receipt1.pdf' and 'receipt2.png')") assert any("receipt1" in fn and fn.endswith(".pdf") for fn in filenames), "receipt1.pdf not found in documents" assert any("receipt2" in fn and fn.endswith(".png") for fn in filenames), "receipt2.png not found in documents" print(" => Documents validation: PASSED") # 4.5 USD conversion calculation expected_usd = Decimal('2500000.00') / usd_rate print(f" [Assertion 5] Expense USD Amount in DB: {expense.amount_usd} (Expected: {expected_usd})") assert abs(expense.amount_usd - expected_usd) < Decimal('0.01'), f"USD conversion mismatch: DB={expense.amount_usd}, Expected={expected_usd}" print(" => USD exchange rate conversion validation: PASSED") # 5. Test bank transfer export print("\nStep 5: Testing 'export_bank_transfer_csv' endpoint...") # Change expense status to SOLICITADO_FINANZAS to make sure it is exported expense.approval_status = ApprovalStatusEnum.SOLICITADO_FINANZAS expense.save() export_response = client.get('/gastos/exportar/transferencias/') print(f" [Assertion 6] Export Status Code: {export_response.status_code} (Expected: 200)") assert export_response.status_code == 200, f"Expected 200, got {export_response.status_code}" # Decode using utf-8-sig to automatically handle BOM if present, or manually assert it content_raw = export_response.content assert content_raw.startswith(b'\xef\xbb\xbf'), "BOM bytes not found at the start of CSV!" content_str = content_raw.decode('utf-8') assert 'RUT Proveedor' in content_str, "CSV header mismatch!" assert 'CONEXIONES SPA' in content_str, "CONEXIONES SPA should be present in the export!" print(" => Bank Transfer CSV validation: PASSED") print("\n" + "=" * 60) print("🎉 ALL INTEGRATION VALIDATION STEPS PASSED SUCCESSFULLY! 🎉") print("=" * 60) if __name__ == '__main__': try: run_test() except Exception as e: import traceback traceback.print_exc() sys.exit(1)