from __future__ import annotations from itertools import product as iproduct from random import randint from typing import TYPE_CHECKING from uuid import uuid4 import pytest from pydantic import PositiveInt from generalresearch.currency import LedgerCurrency from generalresearch.managers.base import Permission from generalresearch.managers.thl.ledger_manager.exceptions import ( LedgerAccountDoesntExistError, ) from generalresearch.managers.thl.ledger_manager.ledger import LedgerManager from generalresearch.models.custom_types import UUIDStr from generalresearch.models.thl.ledger import ( AccountType, Direction, LedgerAccount, LedgerEntry, ) if TYPE_CHECKING: from generalresearch.models.thl.ledger import ( LedgerTransaction, ) @pytest.mark.parametrize( argnames="currency, kind, acct_id", argvalues=list( iproduct( ["USD", "test", "EUR"], ["expense", "wallet", "revenue", "cash"], [uuid4().hex for _ in range(3)], ) ), ) class TestLedgerAccountManagerNoResults: def test_get_account_no_results( self, currency: LedgerCurrency, kind: str, acct_id: UUIDStr, ledger_manager: LedgerManager, ): """Try to query for accounts that we know don't exist and confirm that we either get the expected None result or it raises the correct exception """ qn = f"{currency}:{kind}:{acct_id}" # (1) .get_account is just a wrapper for .get_account_many_ but # call it either way assert ( ledger_manager.get_account(qualified_name=qn, raise_on_error=False) is None ) with pytest.raises(expected_exception=LedgerAccountDoesntExistError): ledger_manager.get_account(qualified_name=qn, raise_on_error=True) # (2) .get_account_if_exists is another wrapper assert ( ledger_manager.get_account(qualified_name=qn, raise_on_error=False) is None ) def test_get_account_no_results_many( self, currency: LedgerCurrency, kind: str, acct_id: UUIDStr, ledger_manager: LedgerManager, ): qn = f"{currency}:{kind}:{acct_id}" # (1) .get_many_ assert ( ledger_manager.get_account_many_(qualified_names=[qn], raise_on_error=False) == [] ) with pytest.raises(expected_exception=LedgerAccountDoesntExistError): ledger_manager.get_account_many_(qualified_names=[qn], raise_on_error=True) # (2) .get_many assert ( ledger_manager.get_account_many(qualified_names=[qn], raise_on_error=False) == [] ) with pytest.raises(expected_exception=LedgerAccountDoesntExistError): ledger_manager.get_account_many(qualified_names=[qn], raise_on_error=True) # (3) .get_accounts(..) assert ledger_manager.get_accounts_if_exists(qualified_names=[qn]) == [] with pytest.raises(expected_exception=LedgerAccountDoesntExistError): ledger_manager.get_accounts(qualified_names=[qn]) @pytest.mark.parametrize( argnames="currency, account_type, direction", argvalues=list( iproduct( list(LedgerCurrency), list(AccountType), list(Direction), ) ), ) class TestLedgerAccountManagerCreate: def test_create_account_error_permission( self, currency: LedgerCurrency, account_type: AccountType, direction: Direction, ledger_manager: LedgerManager, ): """Confirm that the Permission values that are set on the Ledger Manger allow the Creation action to occur. """ acct_uuid = uuid4().hex account = LedgerAccount( display_name=f"test-{uuid4().hex}", currency=currency, qualified_name=f"{currency.value}:{account_type.value}:{acct_uuid}", account_type=account_type, normal_balance=direction, ) # (1) With no Permissions defined test_lm = LedgerManager( pg_config=ledger_manager.pg_config, permissions=[], redis_config=ledger_manager.redis_config, cache_prefix=ledger_manager.cache_prefix, testing=ledger_manager.testing, ) with pytest.raises(expected_exception=AssertionError) as excinfo: test_lm.create_account(account=account) assert ( str(excinfo.value) == "LedgerManager does not have sufficient permissions" ) # (2) With Permissions defined, but not CREATE test_lm = LedgerManager( pg_config=ledger_manager.pg_config, permissions=[Permission.READ, Permission.UPDATE, Permission.DELETE], redis_config=ledger_manager.redis_config, cache_prefix=ledger_manager.cache_prefix, testing=ledger_manager.testing, ) with pytest.raises(expected_exception=AssertionError) as excinfo: test_lm.create_account(account=account) assert ( str(excinfo.value) == "LedgerManager does not have sufficient permissions" ) def test_create( self, currency: LedgerCurrency, account_type: AccountType, direction: Direction, ledger_manager: LedgerManager, ): """Confirm that the Permission values that are set on the Ledger Manger allow the Creation action to occur. """ acct_uuid = uuid4().hex qn = f"{currency.value}:{account_type.value}:{acct_uuid}" acct_model = LedgerAccount( uuid=acct_uuid, display_name=f"test-{uuid4().hex}", currency=currency, qualified_name=qn, account_type=account_type, normal_balance=direction, ) account = ledger_manager.create_account(account=acct_model) assert isinstance(account, LedgerAccount) # Query for, and make sure the Account was saved in the DB res = ledger_manager.get_account(qualified_name=qn, raise_on_error=True) assert res is not None assert account.uuid == res.uuid def test_get_or_create( self, currency: LedgerCurrency, account_type: AccountType, direction: Direction, ledger_manager: LedgerManager, ): """Confirm that the Permission values that are set on the Ledger Manger allow the Creation action to occur. """ acct_uuid = uuid4().hex qn = f"{currency.value}:{account_type.value}:{acct_uuid}" acct_model = LedgerAccount( uuid=acct_uuid, display_name=f"test-{uuid4().hex}", currency=currency, qualified_name=qn, account_type=account_type, normal_balance=direction, ) account = ledger_manager.get_account_or_create(account=acct_model) assert isinstance(account, LedgerAccount) # Query for, and make sure the Account was saved in the DB res = ledger_manager.get_account(qualified_name=qn, raise_on_error=True) assert res is not None assert account.uuid == res.uuid class TestLedgerAccountManagerGet: def test_get(self, ledger_account: LedgerAccount, ledger_manager: LedgerManager): res = ledger_manager.get_account(qualified_name=ledger_account.qualified_name) assert res is not None assert res.uuid == ledger_account.uuid res = ledger_manager.get_account_many( qualified_names=[ledger_account.qualified_name] ) assert len(res) == 1 assert res[0].uuid == ledger_account.uuid res = ledger_manager.get_accounts( qualified_names=[ledger_account.qualified_name] ) assert len(res) == 1 assert res[0].uuid == ledger_account.uuid # TODO: I can't test the get_balance without first having Transaction # creation working def test_get_balance_empty( self, ledger_account: LedgerAccount, ledger_account_credit: LedgerAccount, ledger_account_debit: LedgerAccount, ledger_tx: LedgerTransaction, ledger_manager: LedgerManager, ): res = ledger_manager.get_account_balance(account=ledger_account) assert res == 0 res = ledger_manager.get_account_balance(account=ledger_account_credit) assert res == 100 res = ledger_manager.get_account_balance(account=ledger_account_debit) assert res == 100 @pytest.mark.parametrize("n_times", range(5)) def test_get_account_filtered_balance( self, ledger_account: LedgerAccount, ledger_account_credit: LedgerAccount, ledger_account_debit: LedgerAccount, ledger_tx: LedgerTransaction, n_times: PositiveInt, ledger_manager: LedgerManager, ): """Try searching for random metadata and confirm it's always 0 because Tx can be found. """ rand_key = f"key-{uuid4().hex[:10]}" rand_value = uuid4().hex assert ( ledger_manager.get_account_filtered_balance( account=ledger_account, metadata_key=rand_key, metadata_value=rand_value ) == 0 ) # Let's create a transaction with this metadata to confirm it saves # and that we can filter it back rand_amount = randint(10, 1_000) ledger_manager.create_tx( entries=[ LedgerEntry( direction=Direction.CREDIT, account_uuid=ledger_account_credit.uuid, amount=rand_amount, ), LedgerEntry( direction=Direction.DEBIT, account_uuid=ledger_account_debit.uuid, amount=rand_amount, ), ], metadata={rand_key: rand_value}, ) assert ( ledger_manager.get_account_filtered_balance( account=ledger_account_credit, metadata_key=rand_key, metadata_value=rand_value, ) == rand_amount ) assert ( ledger_manager.get_account_filtered_balance( account=ledger_account_debit, metadata_key=rand_key, metadata_value=rand_value, ) == rand_amount ) def test_get_balance_timerange_empty( self, ledger_account: LedgerAccount, ledger_manager: LedgerManager ): res = ledger_manager.get_account_balance_timerange(account=ledger_account) assert res == 0