Files
TimeTracker/tests/test_invoice_currency_fix.py
Dries Peeters 90dde470da style: standardize code formatting and normalize line endings
- Normalize line endings from CRLF to LF across all files to match .editorconfig
- Standardize quote style from single quotes to double quotes
- Normalize whitespace and formatting throughout codebase
- Apply consistent code style across 372 files including:
  * Application code (models, routes, services, utils)
  * Test files
  * Configuration files
  * CI/CD workflows

This ensures consistency with the project's .editorconfig settings and
improves code maintainability.
2025-11-28 20:05:37 +01:00

292 lines
11 KiB
Python

"""
Test suite for invoice currency fix
Tests that invoices use the currency from Settings instead of hard-coded EUR
"""
import pytest
import os
from datetime import datetime, timedelta, date
from decimal import Decimal
from app import create_app, db
from app.models import User, Project, Client, Invoice, InvoiceItem, Settings
from factories import UserFactory, ClientFactory, ProjectFactory, InvoiceFactory, InvoiceItemFactory
@pytest.fixture
def app():
"""Create and configure a test app instance"""
# Create app with test configuration
test_config = {
"TESTING": True,
"SQLALCHEMY_DATABASE_URI": "sqlite:///:memory:",
"SQLALCHEMY_TRACK_MODIFICATIONS": False,
"WTF_CSRF_ENABLED": False,
"SECRET_KEY": "test-secret-key-do-not-use-in-production",
"SERVER_NAME": "localhost:5000",
}
app = create_app(test_config)
with app.app_context():
db.create_all()
# Create test settings with USD currency
settings = Settings(currency="USD")
db.session.add(settings)
db.session.commit()
yield app
db.session.remove()
db.drop_all()
@pytest.fixture
def client_fixture(app):
"""Create test client"""
return app.test_client()
@pytest.fixture
def test_user(app):
"""Create a test user"""
with app.app_context():
user = UserFactory(username="testuser", role="admin", email="test@example.com")
db.session.add(user)
db.session.commit()
db.session.refresh(user) # Refresh to keep object in session
return user
@pytest.fixture
def test_client_model(app, test_user):
"""Create a test client"""
with app.app_context():
# Re-query user to get it in this session
user = db.session.get(User, test_user.id)
client = ClientFactory(name="Test Client", email="client@example.com")
db.session.add(client)
db.session.commit()
db.session.refresh(client) # Refresh to keep object in session
return client
@pytest.fixture
def test_project(app, test_user, test_client_model):
"""Create a test project"""
with app.app_context():
# Re-query user and client to get them in this session
user = db.session.get(User, test_user.id)
client = db.session.get(Client, test_client_model.id)
project = ProjectFactory(name="Test Project", client_id=client.id, billable=True, hourly_rate=Decimal("100.00"))
project.created_by = user.id
project.status = "active"
db.session.add(project)
db.session.commit()
db.session.refresh(project) # Refresh to keep object in session
return project
class TestInvoiceCurrencyFix:
"""Test that invoices use correct currency from Settings"""
def test_new_invoice_uses_settings_currency(self, app, test_user, test_project, test_client_model):
"""Test that a new invoice uses the currency from Settings"""
with app.app_context():
# Get settings - should have USD currency
settings = Settings.get_settings()
assert settings.currency == "USD"
# Create invoice via model (simulating route behavior)
invoice = InvoiceFactory(
invoice_number="TEST-001",
project_id=test_project.id,
client_name=test_client_model.name,
due_date=date.today() + timedelta(days=30),
created_by=test_user.id,
client_id=test_client_model.id,
status="draft",
currency_code=settings.currency,
)
db.session.add(invoice)
db.session.commit()
# Verify invoice has USD currency
assert invoice.currency_code == "USD"
def test_invoice_creation_via_route(self, app, client_fixture, test_user, test_project, test_client_model):
"""Test that invoice creation via route uses correct currency"""
with app.app_context():
# Login
client_fixture.post(
"/login", data={"username": "testuser", "password": "password123"}, follow_redirects=True
)
# Create invoice via route
response = client_fixture.post(
"/invoices/create",
data={
"project_id": test_project.id,
"client_name": test_client_model.name,
"client_email": test_client_model.email,
"due_date": (date.today() + timedelta(days=30)).strftime("%Y-%m-%d"),
"tax_rate": "0",
},
follow_redirects=True,
)
assert response.status_code == 200
# Get the created invoice
invoice = Invoice.query.first()
assert invoice is not None
assert invoice.currency_code == "USD"
def test_invoice_with_different_currency_setting(self, app, test_user, test_project, test_client_model):
"""Test invoice creation with different currency settings"""
with app.app_context():
# Change settings currency to GBP
settings = Settings.get_settings()
settings.currency = "GBP"
db.session.commit()
# Create invoice
invoice = InvoiceFactory(
invoice_number="TEST-002",
project_id=test_project.id,
client_name=test_client_model.name,
due_date=date.today() + timedelta(days=30),
created_by=test_user.id,
client_id=test_client_model.id,
status="draft",
currency_code=settings.currency,
)
db.session.add(invoice)
db.session.commit()
# Verify invoice has GBP currency
assert invoice.currency_code == "GBP"
def test_invoice_duplicate_preserves_currency(self, app, test_user, test_project, test_client_model):
"""Test that duplicating an invoice preserves the currency"""
with app.app_context():
# Create original invoice with JPY currency
original_invoice = InvoiceFactory(
invoice_number="ORIG-001",
project_id=test_project.id,
client_name=test_client_model.name,
due_date=date.today() + timedelta(days=30),
created_by=test_user.id,
client_id=test_client_model.id,
status="draft",
currency_code="JPY",
)
db.session.add(original_invoice)
db.session.commit()
# Simulate duplication (like in duplicate_invoice route)
new_invoice = InvoiceFactory(
invoice_number="DUP-001",
project_id=original_invoice.project_id,
client_name=original_invoice.client_name,
due_date=original_invoice.due_date + timedelta(days=30),
created_by=test_user.id,
client_id=original_invoice.client_id,
status="draft",
currency_code=original_invoice.currency_code,
)
db.session.add(new_invoice)
db.session.commit()
# Verify duplicated invoice has same currency
assert new_invoice.currency_code == "JPY"
def test_invoice_items_display_with_currency(self, app, test_user, test_project, test_client_model):
"""Test that invoice items display correctly with currency"""
with app.app_context():
# Create invoice
invoice = InvoiceFactory(
invoice_number="TEST-003",
project_id=test_project.id,
client_name=test_client_model.name,
due_date=date.today() + timedelta(days=30),
created_by=test_user.id,
client_id=test_client_model.id,
status="draft",
currency_code="EUR",
)
db.session.add(invoice)
db.session.flush()
# Add invoice item
item = InvoiceItemFactory(
invoice_id=invoice.id,
description="Test Service",
quantity=Decimal("10.00"),
unit_price=Decimal("100.00"),
)
db.session.add(item)
db.session.commit()
# Verify invoice and item
assert invoice.currency_code == "EUR"
assert item.total_amount == Decimal("1000.00")
def test_settings_currency_default(self, app):
"""Test that Settings default currency matches configuration"""
with app.app_context():
# Clear existing settings
Settings.query.delete()
db.session.commit()
# Get settings (should create new with defaults)
settings = Settings.get_settings()
# Should have some currency set (from Config or default)
assert settings.currency is not None
assert len(settings.currency) == 3 # Currency codes are 3 characters
def test_invoice_model_init_with_currency_kwarg(self, app, test_user, test_project, test_client_model):
"""Test that Invoice __init__ properly accepts currency_code kwarg"""
with app.app_context():
# Create invoice with explicit currency_code
invoice = Invoice(
invoice_number="TEST-004",
project_id=test_project.id,
client_name=test_client_model.name,
due_date=date.today() + timedelta(days=30),
created_by=test_user.id,
client_id=test_client_model.id,
currency_code="CAD",
)
# Verify currency is set correctly
assert invoice.currency_code == "CAD"
def test_invoice_to_dict_includes_currency(self, app, test_user, test_project, test_client_model):
"""Test that invoice to_dict includes currency information"""
with app.app_context():
# Create invoice
invoice = Invoice(
invoice_number="TEST-005",
project_id=test_project.id,
client_name=test_client_model.name,
due_date=date.today() + timedelta(days=30),
created_by=test_user.id,
client_id=test_client_model.id,
currency_code="AUD",
)
db.session.add(invoice)
db.session.commit()
# Convert to dict
invoice_dict = invoice.to_dict()
# Verify currency is included (though it may not be in to_dict currently)
# This test documents expected behavior
assert invoice.currency_code == "AUD"
if __name__ == "__main__":
pytest.main([__file__, "-v"])