159 lines
7.4 KiB
Python
159 lines
7.4 KiB
Python
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)
|