"""Configuration for functional tests.""" from contextlib import contextmanager from typing import Any from typing import Callable import pytest from sqlalchemy import delete from starlette.testclient import TestClient from moneyhub.api import create_app from moneyhub.config import Config from moneyhub.connectors.mysql import db from moneyhub.connectors.snowflake import db as snowflake_db from moneyhub.constants.constants import ContractType from moneyhub.constants.constants import JWTKeys from moneyhub.constants.constants import NumberFormat from moneyhub.constants.constants import StatementAttachmentFileType from moneyhub.constants.constants import StatementAttachmentStatus from moneyhub.constants.constants import StatementAttachmentType from moneyhub.models import CombinedPayments from moneyhub.models import Contracts from moneyhub.models import Expenses from moneyhub.models import ExpensesByArtist from moneyhub.models import ExpensesByImprint from moneyhub.models.mysql_base import BaseModel as Base from moneyhub.models.snowflake_base import BaseModel from moneyhub.models.snowflake_base import BaseModel as SnowflakeBase from moneyhub.utils.request import extract_token DISTRIBUTION_FEE_INVOICE = StatementAttachmentType.DISTRIBUTION_FEE_INVOICE SELF_BILLING_INVOICE = StatementAttachmentType.SELF_BILLING_INVOICE REVENUE_DETAIL = StatementAttachmentType.REVENUE_DETAIL class TestConfig(Config): """Test configuration.""" # create all tables Base.metadata.create_all(db.engine) SnowflakeBase.metadata.create_all(snowflake_db.engine) def insert_mock_data(data: dict, database: str = 'mysql'): """Insert mock data into the database tables. The format is a dict where the key is the table to insert into and the value is either a dict or a list of dicts that contain the column:value pairs. Args: data (dict): The data to insert database: database type whether sql or snowflake """ for table_name, entries in data.items(): if database == 'mysql': model = Base.metadata.tables.get(table_name) else: model = SnowflakeBase.metadata.tables.get(table_name) if model is None: raise ValueError(f"Table '{table_name}' does not exist in the database.") if not isinstance(entries, list): entries = [entries] for entry in entries: if database == 'mysql': db.engine.execute(model.insert().values(**entry)) else: snowflake_db.engine.execute(model.insert().values(**entry)) async def override_extract_token_dependency(): """Override extract_token function.""" return { JWTKeys.ORCHARD_IDENTITY_ID: 'me', JWTKeys.PROFILE_ID: 1111, JWTKeys.PROFILE_TYPE: 'MoneyhubProfile', JWTKeys.ROLES: ['role']} def using_mock_snowflake_table( model_class: BaseModel, mock_data: dict | None = None ) -> Callable: """Make a decorator which creates and fills a Snowflake table based off a model. Args: model_class (BaseModel): A model class mock_data (dict | None): Optional mock data to fill the database with Returns: Callable: Decorator function """ def decorator(function: Callable) -> Callable: """Create wrapper function which does the table creation/filling. Args: function (Callable): Function to call Returns: Callable: Wrapper function """ def wrapper(*args: list, **kwargs: dict) -> Any: """Create and fill the table before calling the function, then remove table. Args: args (list): Argument list kwargs (dict): Keyword arguments Returns: Any: Whatever the wrapped function returns """ # insert data if mock_data: insert_mock_data(mock_data, database='snowflake') try: # Run the test result = function(*args, **kwargs) except Exception as e: raise e # test failures are raised as Exceptions finally: # Commit (to avoid query locks) and then delete the table snowflake_db.session.execute(delete(model_class)) snowflake_db.session.commit() snowflake_db.session.close() return result return wrapper return decorator @pytest.fixture(scope='session', autouse=True) def app(): """Create app fixture.""" app_instance = create_app(TestConfig) app_instance.dependency_overrides[extract_token] = override_extract_token_dependency return app_instance @pytest.fixture(scope='session', autouse=True) def fixture_client(app): """Test client fixture.""" with TestClient(app) as c: yield c @pytest.fixture(autouse=True) def fresh_db(): """Refresh the test database.""" db.session.execute('SET FOREIGN_KEY_CHECKS = 0;') for table in reversed(Base.metadata.sorted_tables): db.session.execute(delete(table)) db.session.commit() db.session.close() def insert_mock_account(account_id=1): """Insert account data.""" insert_mock_data({ 'account': { 'account_id': account_id, 'account_name': f'Test 1 {account_id}', } }) def insert_mock_statement_period( statement_period_id=1, statement_period_status='closed'): """Insert statement_period data.""" insert_mock_data({ 'statement_period': { 'statement_period_id': statement_period_id, 'statement_period_name': f'Statement Period {statement_period_id}', 'statement_period_status': statement_period_status, } }) def insert_mock_reference_payment_entity(): """Insert reference payment entity data.""" insert_mock_data({ 'reference_payment_entity': { 'reference_payment_entity_id': 1, 'payment_entity_name': 'The Entity', }, }) def insert_mock_reference_signing_entity(): """Insert reference signing entity data.""" insert_mock_data({ 'reference_signing_entity': { 'reference_signing_entity_id': 1, 'reference_payment_entity_id': 1, 'reference_sap_profit_center_id': 1, 'company_code': '1234', 'tax_entity_company_code': '1234', 'legal_name': 'Company', 'vat_number': '12341234', 'company_registration_number': '12341234', 'address': None, }, }) def insert_mock_reference_sap_profit_center(): """Insert reference sap profit center data.""" insert_mock_data({ 'reference_sap_profit_center': { 'reference_sap_profit_center_id': 1, 'profit_center': 'UK1234', 'company_code': '1234', 'business_group': 'ORC', }, }) def insert_mock_contracts(): """Insert contract data.""" insert_mock_data({ 'contract': [ { 'contract_id': 1, 'reference_signing_entity_id': 1, 'contract_name': 'Test Contract 1', 'contract_type': ContractType.DISTRIBUTION, }, { 'contract_id': 2, 'reference_signing_entity_id': 1, 'contract_name': 'Test Contract 2', 'contract_type': ContractType.NEIGHBOURING_RIGHTS, }, { 'contract_id': 3, 'reference_signing_entity_id': 1, 'contract_name': 'Test Contract 3', 'contract_type': ContractType.DISTRIBUTION, }, { 'contract_id': 4, 'reference_signing_entity_id': 1, 'contract_name': 'Test Contract 4', 'contract_type': ContractType.NEIGHBOURING_RIGHTS, }, ] }) def insert_mock_statement_attachment( account_id=1, contract_id=1, statement_period_id=1, invoice_number='test1234', file_location=None, subaccount_id=None): """Insert mock statement attachment data.""" insert_mock_data({ 'statement_attachment': { 'account_id': account_id, 'subaccount_id': subaccount_id, 'contract_id': contract_id, 'statement_period_id': statement_period_id, 'invoice_number': invoice_number, 'file_location': file_location, 'file_type': StatementAttachmentFileType.PDF, 'number_format': NumberFormat.US, 'statement_attachment_status': StatementAttachmentStatus.IN_PROGRESS, 'statement_attachment_type': StatementAttachmentType.DISTRIBUTION_FEE_INVOICE, 'created_at': '2010-09-08 07:06:05', 'created_by': 'me', }, }) @contextmanager def _using_mock_expenses_data(mock_data: dict): """Create the necessary mock tables and fills them with the data. Args: mock_data (dict): Data to enter. """ ExpensesByArtist.__table__.drop(snowflake_db.engine, checkfirst=True) ExpensesByImprint.__table__.drop(snowflake_db.engine, checkfirst=True) Expenses.__table__.drop(snowflake_db.engine, checkfirst=True) snowflake_db.session.commit() ExpensesByArtist.__table__.create(snowflake_db.engine) ExpensesByImprint.__table__.create(snowflake_db.engine) Expenses.__table__.create(snowflake_db.engine) insert_mock_data(mock_data, database='snowflake') yield snowflake_db.session.commit() ExpensesByArtist.__table__.drop(snowflake_db.engine) ExpensesByImprint.__table__.drop(snowflake_db.engine) Expenses.__table__.drop(snowflake_db.engine) snowflake_db.session.close() @contextmanager def _using_mock_account_statements_data(mock_data: dict): """Create the necessary mock tables and fills them with the data. Args: mock_data (dict): Data to enter. """ CombinedPayments.__table__.drop(snowflake_db.engine, checkfirst=True) Contracts.__table__.drop(snowflake_db.engine, checkfirst=True) snowflake_db.session.commit() CombinedPayments.__table__.create(snowflake_db.engine) Contracts.__table__.create(snowflake_db.engine) insert_mock_data(mock_data, database='snowflake') yield snowflake_db.session.commit() CombinedPayments.__table__.drop(snowflake_db.engine) Contracts.__table__.drop(snowflake_db.engine) snowflake_db.session.close()