def test_process_pending_ledger_update_missing_last_transfer(account, max_count, current_ts): def get_ledger_update_entries_count(): p.process_pending_log_entries(C_ID) return len(LogEntry.query.filter_by(object_type_hint=LogEntry.OTH_ACCOUNT_LEDGER).all()) creation_date = date(2020, 1, 2) assert get_ledger_update_entries_count() == 0 p.process_account_update_signal( debtor_id=D_ID, creditor_id=C_ID, creation_date=creation_date, last_change_ts=current_ts, last_change_seqnum=1, principal=1000, interest=0.0, interest_rate=0.0, last_interest_rate_change_ts=models.TS0, transfer_note_max_bytes=500, last_config_ts=current_ts, last_config_seqnum=0, negligible_amount=10.0, config_flags=models.DEFAULT_CONFIG_FLAGS, config_data='', account_id=str(C_ID), debtor_info_iri=None, debtor_info_content_type=None, debtor_info_sha256=None, last_transfer_number=3, last_transfer_committed_at=current_ts - timedelta(days=20), ts=current_ts, ttl=10000, ) assert len(PendingLedgerUpdate.query.all()) == 1 max_delay = timedelta(days=30) while not p.process_pending_ledger_update(C_ID, D_ID, max_count=max_count, max_delay=max_delay): pass assert len(PendingLedgerUpdate.query.all()) == 0 lue_count = get_ledger_update_entries_count() assert lue_count == 0 data = p.get_account_ledger(C_ID, D_ID) assert data.ledger_principal == 0 assert data.ledger_last_entry_id == 0 assert data.ledger_last_transfer_number == 0 assert data.ledger_latest_update_id == 1 assert len(PendingLedgerUpdate.query.all()) == 0 max_delay = timedelta(days=10) p.ensure_pending_ledger_update(C_ID, D_ID) assert len(PendingLedgerUpdate.query.all()) == 1 while not p.process_pending_ledger_update(C_ID, D_ID, max_count=max_count, max_delay=max_delay): pass lue_count = get_ledger_update_entries_count() assert lue_count == 1 data = p.get_account_ledger(C_ID, D_ID) assert data.ledger_principal == 1000 assert data.ledger_last_entry_id == 1 assert data.ledger_last_transfer_number == 3 assert data.ledger_latest_update_id == 2
def test_delete_account(db_session, account, current_ts): with pytest.raises(p.AccountDoesNotExist): p.delete_account(C_ID, 1234) params = { 'debtor_id': D_ID, 'creditor_id': C_ID, 'last_change_ts': current_ts, 'last_change_seqnum': 1, 'principal': 1000, 'interest': 0.0, 'interest_rate': 5.0, 'last_interest_rate_change_ts': current_ts, 'last_transfer_number': 1, 'last_transfer_committed_at': current_ts, 'last_config_ts': current_ts, 'last_config_seqnum': 1, 'creation_date': date(2020, 1, 15), 'negligible_amount': 0.0, 'ts': current_ts, 'ttl': 1000000, 'account_id': str(C_ID), 'config_data': '', 'config_flags': 0, 'debtor_info_iri': 'http://example.com', 'debtor_info_content_type': None, 'debtor_info_sha256': None, 'transfer_note_max_bytes': 500, } p.process_account_update_signal(**params) with pytest.raises(p.UnsafeAccountDeletion): p.delete_account(C_ID, D_ID) p.update_account_config( C_ID, D_ID, is_scheduled_for_deletion=True, negligible_amount=0.0, allow_unsafe_deletion=False, latest_update_id=2) config = p.get_account_config(C_ID, D_ID) params['last_change_seqnum'] += 1 params['last_config_ts'] = config.last_config_ts params['last_config_seqnum'] = config.last_config_seqnum params['negligible_amount'] = config.negligible_amount params['config_flags'] = config.config_flags p.process_account_update_signal(**params) p.process_account_purge_signal(debtor_id=D_ID, creditor_id=C_ID, creation_date=date(2020, 1, 15)) p.delete_account(C_ID, D_ID) assert not p.get_account(C_ID, D_ID)
def test_process_pending_ledger_update(account, max_count, current_ts): def get_ledger_update_entries_count(): p.process_pending_log_entries(C_ID) return len(LogEntry.query.filter_by(object_type_hint=LogEntry.OTH_ACCOUNT_LEDGER).all()) creation_date = date(2020, 1, 2) params = { 'debtor_id': D_ID, 'creditor_id': C_ID, 'creation_date': creation_date, 'transfer_number': 1, 'coordinator_type': 'direct', 'sender': '666', 'recipient': str(C_ID), 'acquired_amount': 1000, 'transfer_note_format': 'json', 'transfer_note': '{"message": "test"}', 'committed_at': current_ts, 'principal': 1100, 'ts': current_ts, 'previous_transfer_number': 0, 'retention_interval': timedelta(days=5), } p.process_account_transfer_signal(**params) params['transfer_number'] = 20 params['previous_transfer_number'] = 1 params['principal'] = 2100 p.process_account_transfer_signal(**params) params['transfer_number'] = 22 params['previous_transfer_number'] = 21 params['principal'] = 4150 p.process_account_transfer_signal(**params) assert get_ledger_update_entries_count() == 0 assert p.get_pending_ledger_updates() == [] p.process_account_update_signal( debtor_id=D_ID, creditor_id=C_ID, creation_date=creation_date, last_change_ts=current_ts, last_change_seqnum=1, principal=0, interest=0.0, interest_rate=0.0, last_interest_rate_change_ts=models.TS0, transfer_note_max_bytes=500, last_config_ts=current_ts, last_config_seqnum=0, negligible_amount=10.0, config_flags=models.DEFAULT_CONFIG_FLAGS, config_data='', account_id=str(C_ID), debtor_info_iri='http://example.com', debtor_info_content_type=None, debtor_info_sha256=None, last_transfer_number=0, last_transfer_committed_at=models.TS0, ts=current_ts, ttl=10000, ) assert get_ledger_update_entries_count() == 0 assert p.get_pending_ledger_updates() == [(C_ID, D_ID)] assert len(p.get_account_ledger_entries(C_ID, D_ID, prev=1000, count=1000)) == 0 assert p.process_pending_ledger_update(2222, D_ID, max_count=max_count, max_delay=timedelta(days=10000)) assert p.process_pending_ledger_update(C_ID, 1111, max_count=max_count, max_delay=timedelta(days=10000)) n = 0 while not p.process_pending_ledger_update(C_ID, D_ID, max_count=max_count, max_delay=timedelta(days=10000)): x = len(p.get_account_ledger_entries(C_ID, D_ID, prev=1000)) assert x > n n = x lue_count = get_ledger_update_entries_count() assert lue_count > 0 assert len(p.get_account_ledger_entries(C_ID, D_ID, prev=1000, count=1000)) == 3 assert p.get_pending_ledger_updates() == [] params['transfer_number'] = 21 params['previous_transfer_number'] = 20 params['principal'] = 3150 p.process_account_transfer_signal(**params) assert p.get_pending_ledger_updates() == [(C_ID, D_ID)] while not p.process_pending_ledger_update(C_ID, D_ID, max_count=max_count, max_delay=timedelta(days=10000)): pass assert get_ledger_update_entries_count() > lue_count assert p.get_pending_ledger_updates() == [] assert len(p.get_account_ledger_entries(C_ID, D_ID, prev=1000, count=1000)) == 6 log_entry = p.get_log_entries(C_ID, count=1000)[0][-1] assert log_entry.creditor_id == C_ID assert log_entry.object_type_hint == LogEntry.OTH_ACCOUNT_LEDGER assert log_entry.debtor_id == D_ID assert log_entry.object_update_id > 2 assert not log_entry.is_deleted
def test_process_account_transfer_signal(db_session, account, current_ts): def get_committed_tranfer_entries_count(): p.process_pending_log_entries(C_ID) return len(LogEntry.query.filter_by(object_type_hint=LogEntry.OTH_COMMITTED_TRANSFER).all()) def has_pending_ledger_update(): return len(PendingLedgerUpdate.query.filter_by(creditor_id=C_ID, debtor_id=D_ID).all()) > 0 def delete_pending_ledger_update(): PendingLedgerUpdate.query.filter_by(creditor_id=C_ID, debtor_id=D_ID).delete() db_session.commit() assert not has_pending_ledger_update() p.process_account_update_signal( debtor_id=D_ID, creditor_id=C_ID, creation_date=date(2020, 1, 2), last_change_ts=current_ts, last_change_seqnum=1, principal=1000, interest=12.0, interest_rate=5.0, last_interest_rate_change_ts=current_ts - timedelta(days=1), transfer_note_max_bytes=500, last_config_ts=current_ts, last_config_seqnum=0, negligible_amount=100.0, config_flags=models.DEFAULT_CONFIG_FLAGS, config_data='', account_id=str(C_ID), debtor_info_iri='http://example.com', debtor_info_content_type=None, debtor_info_sha256=None, last_transfer_number=123, last_transfer_committed_at=current_ts - timedelta(days=2), ts=current_ts, ttl=10000, ) assert has_pending_ledger_update() delete_pending_ledger_update() params = { 'debtor_id': D_ID, 'creditor_id': C_ID, 'creation_date': date(2020, 1, 2), 'transfer_number': 1, 'coordinator_type': 'direct', 'sender': '666', 'recipient': str(C_ID), 'acquired_amount': 100, 'transfer_note_format': 'json', 'transfer_note': '{"message": "test"}', 'committed_at': current_ts, 'principal': 1000, 'ts': current_ts - timedelta(days=6), 'previous_transfer_number': 0, 'retention_interval': timedelta(days=5), } p.process_account_transfer_signal(**params) assert len(CommittedTransfer.query.all()) == 0 assert get_committed_tranfer_entries_count() == 0 assert not has_pending_ledger_update() params['retention_interval'] = timedelta(days=7) p.process_account_transfer_signal(**params) ct = CommittedTransfer.query.one() assert ct.debtor_id == D_ID assert ct.creditor_id == C_ID assert ct.creation_date == params['creation_date'] assert ct.transfer_number == 1 assert ct.coordinator_type == 'direct' assert ct.sender == '666' assert ct.recipient == str(C_ID) assert ct.acquired_amount == 100 assert ct.transfer_note_format == params['transfer_note_format'] assert ct.transfer_note == params['transfer_note'] assert ct.committed_at == current_ts assert ct.principal == 1000 assert ct.previous_transfer_number == 0 assert get_committed_tranfer_entries_count() == 1 assert has_pending_ledger_update() delete_pending_ledger_update() params['retention_interval'] = timedelta(days=7) p.process_account_transfer_signal(**params) assert len(CommittedTransfer.query.all()) == 1 assert get_committed_tranfer_entries_count() == 1 le = LogEntry.query.filter_by(object_type_hint=LogEntry.OTH_COMMITTED_TRANSFER).one() assert le.creditor_id == C_ID assert le.debtor_id == D_ID assert le.creation_date == params['creation_date'] assert le.transfer_number == 1 assert not le.is_deleted assert le.object_update_id is None assert not has_pending_ledger_update() params['creditor_id'] = 1235 p.process_account_transfer_signal(**params) assert len(CommittedTransfer.query.all()) == 1 assert get_committed_tranfer_entries_count() == 1
def test_update_account_config(account, current_ts): def get_data(): return AccountData.query.filter_by(creditor_id=C_ID, debtor_id=D_ID).one() def get_info_entries_count(): p.process_pending_log_entries(C_ID) return len(LogEntry.query.filter_by(object_type='AccountInfo').all()) creation_date = current_ts.date() data = get_data() assert not data.is_config_effectual assert not data.is_deletion_safe assert not data.has_server_account assert get_info_entries_count() == 0 p.update_account_config(C_ID, D_ID, is_scheduled_for_deletion=True, negligible_amount=1e30, allow_unsafe_deletion=False, latest_update_id=2) data = get_data() assert not data.is_config_effectual assert not data.is_deletion_safe assert not data.has_server_account assert get_info_entries_count() == 0 data = get_data() params = { 'debtor_id': D_ID, 'creditor_id': C_ID, 'creation_date': creation_date, 'last_change_ts': current_ts, 'last_change_seqnum': 1, 'principal': 0, 'interest': 0.0, 'interest_rate': 0.0, 'last_interest_rate_change_ts': models.TS0, 'transfer_note_max_bytes': 500, 'last_config_ts': data.last_config_ts, 'last_config_seqnum': data.last_config_seqnum, 'negligible_amount': data.negligible_amount, 'config_flags': data.config_flags, 'config_data': '', 'account_id': str(C_ID), 'debtor_info_iri': 'http://example.com', 'debtor_info_content_type': None, 'debtor_info_sha256': None, 'last_transfer_number': 0, 'last_transfer_committed_at': models.TS0, 'ts': current_ts, 'ttl': 10000, } p.process_account_update_signal(**params) data = get_data() assert data.is_config_effectual assert not data.is_deletion_safe assert data.has_server_account assert get_info_entries_count() == 1 p.process_account_purge_signal(debtor_id=D_ID, creditor_id=C_ID, creation_date=creation_date) data = get_data() assert data.is_config_effectual assert data.is_deletion_safe assert not data.has_server_account assert get_info_entries_count() == 2 p.update_account_config(C_ID, D_ID, is_scheduled_for_deletion=True, negligible_amount=1e30, allow_unsafe_deletion=False, latest_update_id=3) data = get_data() assert not data.is_config_effectual assert not data.is_deletion_safe assert not data.has_server_account assert get_info_entries_count() == 3
def test_process_account_update_signal(db_session, account): AccountData.query.filter_by(creditor_id=C_ID, debtor_id=D_ID).update({ 'ledger_principal': 1001, 'ledger_last_entry_id': 88, 'ledger_last_transfer_number': 888, }) def get_data(): return AccountData.query.filter_by(creditor_id=C_ID, debtor_id=D_ID).one() ad = get_data() assert not ad.is_config_effectual assert ad.ledger_principal == 1001 assert ad.ledger_last_entry_id == 88 assert ad.negligible_amount == models.DEFAULT_NEGLIGIBLE_AMOUNT assert ad.config_flags == models.DEFAULT_CONFIG_FLAGS assert ad.last_change_seqnum == 0 assert ad.config_error is None last_ts = ad.last_config_ts last_seqnum = ad.last_config_seqnum negligible_amount = ad.negligible_amount config_flags = ad.config_flags last_heartbeat_ts = ad.last_heartbeat_ts creation_date = date(2020, 1, 15) time.sleep(0.1) current_ts = datetime.now(tz=timezone.utc) assert last_heartbeat_ts < current_ts params = { 'debtor_id': D_ID, 'creditor_id': C_ID, 'creation_date': creation_date, 'last_change_ts': current_ts, 'last_change_seqnum': 1, 'principal': 1000, 'interest': 12.0, 'interest_rate': 5.0, 'last_interest_rate_change_ts': current_ts - timedelta(days=1), 'transfer_note_max_bytes': 500, 'last_config_ts': last_ts, 'last_config_seqnum': last_seqnum, 'negligible_amount': negligible_amount, 'config_flags': config_flags, 'config_data': '', 'account_id': str(C_ID), 'debtor_info_iri': 'http://example.com', 'debtor_info_content_type': 'text/plain', 'debtor_info_sha256': 32 * b'\xff', 'last_transfer_number': 22, 'last_transfer_committed_at': current_ts - timedelta(days=2), 'ts': current_ts, 'ttl': 0, } p.process_account_update_signal(**params) ad = get_data() assert last_heartbeat_ts == ad.last_heartbeat_ts assert ad.last_change_seqnum == 0 assert not ad.is_config_effectual assert ad.config_error is None params['ttl'] = 10000 p.process_account_update_signal(**params) ad = get_data() assert ad.last_heartbeat_ts > last_heartbeat_ts assert ad.is_config_effectual assert ad.creation_date == creation_date assert ad.last_change_ts == current_ts assert ad.last_change_seqnum == 1 assert ad.principal == 1000 assert ad.interest == 12.0 assert ad.interest_rate == 5.0 assert ad.last_interest_rate_change_ts == current_ts - timedelta(days=1) assert ad.transfer_note_max_bytes == 500 assert ad.last_config_ts == last_ts assert ad.last_config_seqnum == last_seqnum assert ad.account_id == str(C_ID) assert ad.debtor_info_iri == 'http://example.com' assert ad.debtor_info_content_type == 'text/plain' assert ad.debtor_info_sha256 == 32 * b'\xff' assert ad.last_transfer_number == 22 assert ad.last_transfer_committed_at == current_ts - timedelta(days=2) assert ad.config_error is None p.process_account_update_signal(**params) assert ad.last_change_seqnum == 1 p.process_rejected_config_signal( debtor_id=D_ID, creditor_id=C_ID, config_ts=last_ts, config_seqnum=last_seqnum, negligible_amount=negligible_amount, config_data='', config_flags=config_flags, rejection_code='TEST_CONFIG_ERROR', ) ad = get_data() assert ad.config_error == 'TEST_CONFIG_ERROR' params['last_change_seqnum'] = 2 params['principal'] = 1100 params['negligible_amount'] = 3.33 params['config_flags'] = 77 p.process_account_update_signal(**params) ad = get_data() assert ad.last_change_seqnum == 2 assert ad.principal == 1100 assert ad.negligible_amount == negligible_amount assert ad.config_flags == config_flags assert not ad.is_config_effectual assert ad.config_error == 'TEST_CONFIG_ERROR' params['last_change_seqnum'] = 3 params['negligible_amount'] = negligible_amount params['config_flags'] = config_flags p.process_account_update_signal(**params) ad = get_data() assert ad.last_change_seqnum == 3 assert ad.is_config_effectual assert ad.config_error is None params['creation_date'] = creation_date + timedelta(days=2) p.process_account_update_signal(**params) ad = get_data() assert ad.last_change_seqnum == 3 assert ad.is_config_effectual assert ad.creation_date == creation_date + timedelta(days=2) assert ad.config_error is None assert ad.ledger_principal == 0 assert ad.ledger_last_entry_id == 89 assert ad.ledger_last_transfer_number == 0 # Discard orphaned account. params['debtor_id'] = 1235 params['last_change_seqnum'] = 1 params['negligible_amount'] = 2.0 p.process_account_update_signal(**params) cas = ConfigureAccountSignal.query.filter_by(creditor_id=C_ID, debtor_id=1235).one() assert cas.negligible_amount > 1e22 assert cas.config_flags & AccountData.CONFIG_SCHEDULED_FOR_DELETION_FLAG params['last_change_seqnum'] = 2 params['negligible_amount'] = models.DEFAULT_NEGLIGIBLE_AMOUNT params['config_flags'] = AccountData.CONFIG_SCHEDULED_FOR_DELETION_FLAG p.process_account_update_signal(**params) assert ConfigureAccountSignal.query.filter_by(creditor_id=C_ID, debtor_id=1235).one() assert list(p.get_creditors_with_pending_log_entries()) == [(C_ID,)] p.process_pending_log_entries(1235) p.process_pending_log_entries(C_ID) assert len(models.LogEntry.query.filter_by(object_type='AccountInfo').all()) == 3 assert len(models.LogEntry.query.filter_by(object_type_hint=LogEntry.OTH_ACCOUNT_LEDGER).all()) == 1
def test_process_ledger_entries(app, db_session, current_ts): _create_new_creditor(C_ID, activate=True) p.create_new_account(C_ID, D_ID) params = { 'debtor_id': D_ID, 'creditor_id': C_ID, 'creation_date': date(2020, 1, 1), 'last_change_ts': current_ts, 'last_change_seqnum': 1, 'principal': 1000, 'interest': 0.0, 'interest_rate': 5.0, 'last_interest_rate_change_ts': current_ts, 'transfer_note_max_bytes': 500, 'last_config_ts': current_ts, 'last_config_seqnum': 1, 'negligible_amount': 0.0, 'config_flags': 0, 'config_data': '', 'account_id': str(C_ID), 'debtor_info_iri': 'http://example.com', 'debtor_info_content_type': None, 'debtor_info_sha256': None, 'last_transfer_number': 0, 'last_transfer_committed_at': current_ts, 'ts': current_ts, 'ttl': 100000, } p.process_account_update_signal(**params) params = { 'debtor_id': D_ID, 'creditor_id': C_ID, 'creation_date': date(2020, 1, 1), 'transfer_number': 1, 'coordinator_type': 'direct', 'sender': '666', 'recipient': str(C_ID), 'acquired_amount': 200, 'transfer_note_format': 'json', 'transfer_note': '{"message": "test"}', 'committed_at': current_ts, 'principal': 200, 'ts': current_ts, 'previous_transfer_number': 0, 'retention_interval': timedelta(days=5), } p.process_account_transfer_signal(**params) params['transfer_number'] = 2 params['principal'] = 400 params['previous_transfer_number'] = 1 p.process_account_transfer_signal(**params) assert len( p.get_account_ledger_entries(C_ID, D_ID, prev=10000, count=10000)) == 0 runner = app.test_cli_runner() result = runner.invoke(args=[ 'swpt_creditors', 'process_ledger_updates', '--burst=1', '--quit-early', '--wait=0' ]) assert not result.output assert len( p.get_account_ledger_entries(C_ID, D_ID, prev=10000, count=10000)) == 2
def on_account_update_signal( debtor_id: int, creditor_id: int, last_change_ts: str, last_change_seqnum: int, principal: int, interest: float, interest_rate: float, demurrage_rate: float, commit_period: int, transfer_note_max_bytes: int, last_interest_rate_change_ts: str, last_transfer_number: int, last_transfer_committed_at: str, last_config_ts: str, last_config_seqnum: int, creation_date: str, negligible_amount: float, config_data: str, config_flags: int, ts: str, ttl: int, account_id: str, debtor_info_iri: str, debtor_info_content_type: str, debtor_info_sha256: str, *args, **kwargs) -> None: assert 0 <= transfer_note_max_bytes <= TRANSFER_NOTE_MAX_BYTES assert len(config_data) <= CONFIG_DATA_MAX_BYTES and len(config_data.encode('utf8')) <= CONFIG_DATA_MAX_BYTES assert account_id == '' or len(account_id) <= 100 and account_id.encode('ascii') assert len(debtor_info_iri) <= 200 assert debtor_info_content_type == '' or ( len(debtor_info_content_type) <= 100 and debtor_info_content_type.encode('ascii')) assert debtor_info_sha256 == '' or len(debtor_info_sha256) == 64 procedures.process_account_update_signal( debtor_id=debtor_id, creditor_id=creditor_id, creation_date=date.fromisoformat(creation_date), last_change_ts=datetime.fromisoformat(last_change_ts), last_change_seqnum=last_change_seqnum, principal=principal, interest=interest, interest_rate=interest_rate, last_interest_rate_change_ts=datetime.fromisoformat(last_interest_rate_change_ts), transfer_note_max_bytes=transfer_note_max_bytes, last_config_ts=datetime.fromisoformat(last_config_ts), last_config_seqnum=last_config_seqnum, negligible_amount=negligible_amount, config_flags=config_flags, config_data=config_data, account_id=account_id, debtor_info_iri=debtor_info_iri or None, debtor_info_content_type=debtor_info_content_type or None, debtor_info_sha256=b16decode(debtor_info_sha256, casefold=True) or None, last_transfer_number=last_transfer_number, last_transfer_committed_at=datetime.fromisoformat(last_transfer_committed_at), ts=datetime.fromisoformat(ts), ttl=ttl, )