def test_parallel_transaction(account1, account2, bucket): if not have_freezetime: return zero = create_decimal(0) # test lots of transactions all happening in parallel total1 = zero total2 = zero start1 = account1.balance() start2 = account2.balance() rlock = RLock() def perform_transaction(key, result): delta1 = zero delta2 = zero auth = Authorisation() # need to work with thread-local copies of the accounts my_account1 = Account(uid=account1.uid()) my_account2 = Account(uid=account2.uid()) for i in range(0, 5): transaction = Transaction(value=create_decimal(random.random()), description="Transaction %d" % i) if random.randint(0, 1): Ledger.perform(transaction, my_account1, my_account2, auth) delta1 -= transaction.value() delta2 += transaction.value() else: Ledger.perform(transaction, my_account2, my_account1, auth) delta1 += transaction.value() delta2 -= transaction.value() with rlock: result[key] = (delta1, delta2) threads = [] result = {} for i in range(0, 5): t = Thread(target=perform_transaction, args=[i, result]) t.start() threads.append(t) total1 = zero total2 = zero for i, thread in enumerate(threads): thread.join() total1 += result[i][0] total2 += result[i][1] assert (account1.balance() == start1 + total1) assert (account2.balance() == start2 + total2)
def random_transaction(): value = create_decimal(1000.0 * random.random()) description = "%s transaction" % value transaction = Transaction(value, description) assert (transaction.value() == value) assert (transaction.description() == description) return transaction
def random_transaction(account1, account2): value = create_decimal(1000.0 * random.random()) description = "%s transaction" % value transaction = Transaction(value, description) assert (transaction.value() == value) assert (transaction.description() == description) assert (account1.get_overdraft_limit() == account1_overdraft_limit) assert (account2.get_overdraft_limit() == account2_overdraft_limit) if random.randint(0, 1): return (transaction, account1, account2) else: return (transaction, account2, account1)
def perform_transaction(key, result): delta1 = zero delta2 = zero # need to work with thread-local copies of the accounts my_account1 = Account(uid=account1.uid()) my_account2 = Account(uid=account2.uid()) for i in range(0, 10): transaction = Transaction(value=create_decimal(random.random()), description="Transaction %d" % i) if random.randint(0, 1): auth = Authorisation( resource=transaction.fingerprint(), testing_key=testing_key, testing_user_guid=my_account1.group_name()) Ledger.perform(transaction=transaction, debit_account=my_account1, credit_account=my_account2, authorisation=auth) delta1 -= transaction.value() delta2 += transaction.value() else: auth = Authorisation( resource=transaction.fingerprint(), testing_key=testing_key, testing_user_guid=my_account2.group_name()) Ledger.perform(transaction=transaction, debit_account=my_account2, credit_account=my_account1, authorisation=auth) delta1 += transaction.value() delta2 -= transaction.value() with rlock: result[key] = (delta1, delta2)
def perform_transaction(key, result): delta1 = zero delta2 = zero auth = Authorisation() # need to work with thread-local copies of the accounts my_account1 = Account(uid=account1.uid()) my_account2 = Account(uid=account2.uid()) for i in range(0, 5): transaction = Transaction(value=create_decimal(random.random()), description="Transaction %d" % i) if random.randint(0, 1): Ledger.perform(transaction, my_account1, my_account2, auth) delta1 -= transaction.value() delta2 += transaction.value() else: Ledger.perform(transaction, my_account2, my_account1, auth) delta1 += transaction.value() delta2 -= transaction.value() with rlock: result[key] = (delta1, delta2)
def test_temporal_transactions(account1, account2, bucket): if not have_freezetime: return zero = create_decimal(0) balance1 = zero balance2 = zero final_balance1 = zero final_balance2 = zero liability1 = zero liability2 = zero receivable1 = zero receivable2 = zero # generate some random times for the transactions random_dates = [] now = get_datetime_now() for i in range(0, 50): if i == 0: # this is an evil edge-case datetime s = "2019-01-20 20:59:59.092627+00:00" r = datetime.datetime.fromisoformat(s) else: r = start_time + random.random() * (now - start_time) while (r.minute == 59 and r.second >= 58) or \ (r.minute == 0 and r.second == 0 and r.microsecond < 10): r = r + datetime.timedelta(seconds=1) random_dates.append(r) random_dates.sort() # (which must be applied in time order!) random_dates.sort() provisionals = [] for (i, transaction_time) in enumerate(random_dates): with freeze_time(transaction_time) as _frozen_datetime: now = get_datetime_now() assert (transaction_time == now) is_provisional = (random.randint(0, 3) <= 2) # check search for transaction is not O(n^2) lookup scanning # through the keys... transaction = Transaction(25 * random.random(), "test transaction %d" % i) if random.randint(0, 1): debit_account = account1 credit_account = account2 if is_provisional: liability1 += transaction.value() receivable2 += transaction.value() else: balance1 -= transaction.value() balance2 += transaction.value() final_balance1 -= transaction.value() final_balance2 += transaction.value() else: debit_account = account2 credit_account = account1 if is_provisional: receivable1 += transaction.value() liability2 += transaction.value() else: balance1 += transaction.value() balance2 -= transaction.value() final_balance1 += transaction.value() final_balance2 -= transaction.value() auth = Authorisation(resource=transaction.fingerprint(), testing_key=testing_key, testing_user_guid=debit_account.group_name()) records = Ledger.perform(transaction=transaction, debit_account=debit_account, credit_account=credit_account, authorisation=auth, is_provisional=is_provisional, bucket=bucket) for record in records: assert (record.datetime() == now) if is_provisional: for record in records: provisionals.append((credit_account, record)) elif (random.randint(0, 3) <= 2): # receipt pending transactions balance1 = Balance(balance=balance1, liability=liability1, receivable=receivable1) balance2 = Balance(balance=balance2, liability=liability2, receivable=receivable2) assert (account1.balance() == balance1) assert (account2.balance() == balance2) for (credit_account, record) in provisionals: credit_note = record.credit_note() auth = Authorisation( resource=credit_note.fingerprint(), testing_key=testing_key, testing_user_guid=credit_account.group_name()) receipted_value = create_decimal( random.random() * float(credit_note.value())) delta_value = credit_note.value() - receipted_value Ledger.receipt(Receipt(credit_note=credit_note, receipted_value=receipted_value, authorisation=auth), bucket=bucket) if credit_note.debit_account_uid() == account1.uid(): final_balance1 += delta_value final_balance2 -= delta_value else: final_balance2 += delta_value final_balance1 -= delta_value assert (account1.balance() == Balance(balance=final_balance1)) assert (account2.balance() == Balance(balance=final_balance2)) provisionals = [] balance1 = final_balance1 balance2 = final_balance2 liability1 = zero liability2 = zero receivable1 = zero receivable2 = zero balance1 = Balance(balance=balance1, liability=liability1, receivable=receivable1) balance2 = Balance(balance=balance2, liability=liability2, receivable=receivable2) assert (account1.balance() == balance1) assert (account2.balance() == balance2) for (credit_account, record) in provisionals: credit_note = record.credit_note() auth = Authorisation(resource=record.credit_note().fingerprint(), testing_key=testing_key, testing_user_guid=credit_account.group_name()) receipted_value = create_decimal(random.random() * float(credit_note.value())) delta_value = credit_note.value() - receipted_value Ledger.receipt(Receipt(credit_note=credit_note, authorisation=auth, receipted_value=receipted_value), bucket=bucket) if credit_note.debit_account_uid() == account1.uid(): final_balance1 += delta_value final_balance2 -= delta_value else: final_balance2 += delta_value final_balance1 -= delta_value assert (account1.balance() == Balance(balance=final_balance1)) assert (account2.balance() == Balance(balance=final_balance2))
def test_pending_transactions(random_transaction): (transaction, account1, account2) = random_transaction starting_balance1 = account1.balance() starting_balance2 = account2.balance() authorisation = Authorisation(resource=transaction.fingerprint(), testing_key=testing_key, testing_user_guid=account1.group_name()) records = Ledger.perform(transactions=transaction, debit_account=account1, credit_account=account2, authorisation=authorisation, is_provisional=True) assert (len(records) == 1) record = records[0] ending_balance1 = account1.balance() ending_balance2 = account2.balance() assert (ending_balance1.liability() == starting_balance1.liability() + transaction.value()) assert (ending_balance2.receivable() == starting_balance2.receivable() + transaction.value()) assert (starting_balance1.balance() == ending_balance1.balance()) assert (starting_balance2.balance() == ending_balance2.balance()) assert (starting_balance2.liability() == ending_balance2.liability()) assert (starting_balance1.receivable() == ending_balance1.receivable()) assert (record.debit_account_uid() == account1.uid()) assert (record.credit_account_uid() == account2.uid()) debit_note = record.debit_note() credit_note = record.credit_note() assert (not debit_note.is_null()) assert (not credit_note.is_null()) assert (debit_note.account_uid() == account1.uid()) assert (credit_note.account_uid() == account2.uid()) assert (debit_note.is_provisional()) assert (credit_note.is_provisional()) assert (debit_note.value() == transaction.value()) assert (credit_note.value() == transaction.value()) now = get_datetime_now() assert (debit_note.datetime() < now) assert (credit_note.datetime() < now) assert (debit_note.datetime() <= credit_note.datetime()) assert_packable(debit_note) assert_packable(credit_note) # now receipt a random amount of the transaction authorisation = Authorisation(resource=credit_note.fingerprint(), testing_key=testing_key, testing_user_guid=account2.group_name()) with pytest.raises(ValueError): receipt = Receipt( credit_note, authorisation, create_decimal(random.random()) + credit_note.value()) if random.randint(0, 1): value = credit_note.value() receipt = Receipt(credit_note, authorisation) else: value = create_decimal( create_decimal(random.random()) * credit_note.value()) receipt = Receipt(credit_note, authorisation, value) assert (not receipt.is_null()) assert (receipt.authorisation() == authorisation) assert (receipt.receipted_value() == value) assert (receipt.credit_note() == credit_note) assert_packable(receipt) rrecords = Ledger.receipt(receipt) assert (len(rrecords) == 1) rrecord = rrecords[0] assert (not rrecord.is_null()) assert_packable(rrecord) assert (not rrecord.is_provisional()) assert (rrecord.is_direct()) assert (rrecord.get_receipt_info() == receipt) assert (rrecord.is_receipt()) assert (rrecord.original_transaction() == transaction) # the original transaction record has now been updated to # say that it has been receipted... assert (record.is_provisional()) record.reload() assert (record.is_receipted()) assert (rrecord.original_transaction_record() == record) ending_balance1 = account1.balance() ending_balance2 = account2.balance() assert (ending_balance1.liability() == starting_balance1.liability()) assert (ending_balance2.receivable() == starting_balance2.receivable()) assert (starting_balance1.balance() - value == ending_balance1.balance()) assert (starting_balance2.balance() + value == ending_balance2.balance()) assert (starting_balance2.liability() == ending_balance2.liability()) assert (starting_balance1.receivable() == ending_balance1.receivable())
def test_temporal_transactions(account1, account2, bucket): if not have_freezetime: return zero = create_decimal(0) balance1 = zero balance2 = zero final_balance1 = zero final_balance2 = zero liability1 = zero liability2 = zero receivable1 = zero receivable2 = zero # generate some random times for the transactions random_dates = [] now = datetime.datetime.now() for i in range(0, 100): random_dates.append(start_time + random.random() * (now - start_time)) # (which must be applied in time order!) random_dates.sort() records = [] for (i, transaction_time) in enumerate(random_dates): with freeze_time(transaction_time) as frozen_datetime: now = datetime.datetime.now() assert (frozen_datetime() == now) is_provisional = random.randint(0, 5) transaction = Transaction(25 * random.random(), "test transaction %d" % i) auth = Authorisation() if random.randint(0, 10): record = Ledger.perform(transaction, account1, account2, auth, is_provisional, bucket=bucket) if is_provisional: liability1 += transaction.value() receivable2 += transaction.value() else: balance1 -= transaction.value() balance2 += transaction.value() final_balance1 -= transaction.value() final_balance2 += transaction.value() else: record = Ledger.perform(transaction, account2, account1, auth, is_provisional, bucket=bucket) if is_provisional: receivable1 += transaction.value() liability2 += transaction.value() else: balance1 += transaction.value() balance2 -= transaction.value() final_balance1 += transaction.value() final_balance2 -= transaction.value() if is_provisional: records.append(record) assert (record.timestamp() == now.timestamp()) assert (account1.balance() == balance1) assert (account2.balance() == balance2) assert (account1.liability() == liability1) assert (account1.receivable() == receivable1) assert (account2.liability() == liability2) assert (account2.receivable() == receivable2) for record in records: Ledger.receipt(Receipt(record.credit_note(), Authorisation()), bucket=bucket) assert (account1.balance() == final_balance1) assert (account2.balance() == final_balance2) assert (account1.liability() == zero) assert (account1.receivable() == zero) assert (account2.liability() == zero) assert (account2.receivable() == zero)
def test_pending_transactions(random_transaction): (transaction, account1, account2) = random_transaction starting_balance1 = account1.balance() starting_liability1 = account1.liability() starting_receivable1 = account1.receivable() starting_balance2 = account2.balance() starting_liability2 = account2.liability() starting_receivable2 = account2.receivable() record = Ledger.perform(transaction, account1, account2, Authorisation(), is_provisional=True) ending_balance1 = account1.balance() ending_liability1 = account1.liability() ending_receivable1 = account1.receivable() ending_balance2 = account2.balance() ending_liability2 = account2.liability() ending_receivable2 = account2.receivable() assert (ending_liability1 == starting_liability1 + transaction.value()) assert (ending_receivable2 == starting_receivable2 + transaction.value()) assert (starting_balance1 == ending_balance1) assert (starting_balance2 == ending_balance2) assert (starting_liability2 == ending_liability2) assert (starting_receivable1 == ending_receivable1) assert (record.debit_account_uid() == account1.uid()) assert (record.credit_account_uid() == account2.uid()) debit_note = record.debit_note() credit_note = record.credit_note() assert (not debit_note.is_null()) assert (not credit_note.is_null()) assert (debit_note.account_uid() == account1.uid()) assert (credit_note.account_uid() == account2.uid()) assert (debit_note.is_provisional()) assert (credit_note.is_provisional()) assert (debit_note.value() == transaction.value()) assert (credit_note.value() == transaction.value()) now = datetime.datetime.now() assert (debit_note.timestamp() < now.timestamp()) assert (credit_note.timestamp() < now.timestamp()) assert (debit_note.timestamp() <= credit_note.timestamp()) assert_packable(debit_note) assert_packable(credit_note) # now receipt a random amount of the transaction auth = Authorisation() with pytest.raises(ValueError): receipt = Receipt( credit_note, auth, create_decimal(random.random()) + credit_note.value()) if random.randint(0, 1): value = credit_note.value() receipt = Receipt(credit_note, auth) else: value = create_decimal( create_decimal(random.random()) * credit_note.value()) receipt = Receipt(credit_note, auth, value) assert (not receipt.is_null()) assert (receipt.authorisation() == auth) assert (receipt.receipted_value() == value) assert (receipt.credit_note() == credit_note) assert_packable(receipt) rrecord = Ledger.receipt(receipt) assert (not rrecord.is_null()) assert_packable(rrecord) assert (not rrecord.is_provisional()) assert (rrecord.is_direct()) assert (rrecord.get_receipt_info() == receipt) assert (rrecord.is_receipt()) assert (rrecord.original_transaction() == transaction) # the original transaction record has now been updated to # say that it has been receipted... assert (record.is_provisional()) record.reload() assert (record.is_receipted()) assert (rrecord.original_transaction_record() == record) ending_balance1 = account1.balance() ending_liability1 = account1.liability() ending_receivable1 = account1.receivable() ending_balance2 = account2.balance() ending_liability2 = account2.liability() ending_receivable2 = account2.receivable() assert (ending_liability1 == starting_liability1) assert (ending_receivable2 == starting_receivable2) assert (starting_balance1 - value == ending_balance1) assert (starting_balance2 + value == ending_balance2) assert (starting_liability2 == ending_liability2) assert (starting_receivable1 == ending_receivable1)