From 43163a5c5c6a9aa23f55a368ce23193b96e8b129 Mon Sep 17 00:00:00 2001 From: Yosuke Otosu Date: Wed, 29 Nov 2023 09:22:46 +0900 Subject: [PATCH] Add result_set for pagination to token holders API --- app/model/schema/__init__.py | 2 +- app/model/schema/holder.py | 9 + app/routers/bond.py | 36 +- app/routers/share.py | 37 +- ...bond_tokens_{token_address}_holders_GET.py | 966 ++++++++++++++---- ...hare_tokens_{token_address}_holders_GET.py | 966 ++++++++++++++---- 6 files changed, 1665 insertions(+), 351 deletions(-) diff --git a/app/model/schema/__init__.py b/app/model/schema/__init__.py index 91db577e..8d241ea2 100644 --- a/app/model/schema/__init__.py +++ b/app/model/schema/__init__.py @@ -61,7 +61,7 @@ ListAllFilesResponse, UploadFileRequest, ) -from .holder import HolderCountResponse, HolderResponse +from .holder import HolderCountResponse, HolderResponse, HoldersResponse from .index import BlockNumberResponse, E2EEResponse from .issue_redeem import IssueRedeemEvent, IssueRedeemHistoryResponse from .ledger import ( diff --git a/app/model/schema/holder.py b/app/model/schema/holder.py index 310ca4a9..60c55669 100644 --- a/app/model/schema/holder.py +++ b/app/model/schema/holder.py @@ -21,6 +21,8 @@ from pydantic import BaseModel +from app.model.schema.base import ResultSet + from .personal_info import PersonalInfo @@ -40,6 +42,13 @@ class HolderResponse(BaseModel): modified: Optional[datetime] +class HoldersResponse(BaseModel): + """Holders schema (Response)""" + + result_set: ResultSet + holders: list[HolderResponse] + + class HolderCountResponse(BaseModel): """Holder count schema (Response)""" diff --git a/app/routers/bond.py b/app/routers/bond.py index 376af077..307e6c72 100644 --- a/app/routers/bond.py +++ b/app/routers/bond.py @@ -110,6 +110,7 @@ GetBatchRegisterPersonalInfoResponse, HolderCountResponse, HolderResponse, + HoldersResponse, IbetStraightBondAdditionalIssue, IbetStraightBondCreate, IbetStraightBondRedeem, @@ -1654,13 +1655,16 @@ def delete_scheduled_event( # GET: /bond/tokens/{token_address}/holders @router.get( "/tokens/{token_address}/holders", - response_model=List[HolderResponse], + response_model=HoldersResponse, responses=get_routers_responses(422, InvalidParameterError, 404), ) def list_all_holders( db: DBSession, token_address: str, include_former_holder: bool = False, + sort_order: int = Query(0, ge=0, le=1, description="0:asc, 1:desc (created)"), + offset: Optional[int] = Query(None), + limit: Optional[int] = Query(None), issuer_address: str = Header(...), ): """List all bond token holders""" @@ -1715,6 +1719,8 @@ def list_all_holders( ) ) + total = db.scalar(select(func.count()).select_from(stmt.subquery())) + if not include_former_holder: stmt = stmt.where( or_( @@ -1726,8 +1732,22 @@ def list_all_holders( ) ) + count = db.scalar(select(func.count()).select_from(stmt.subquery())) + + # Sort + if sort_order == 0: # ASC + stmt = stmt.order_by(IDXPosition.id) + else: # DESC + stmt = stmt.order_by(desc(IDXPosition.id)) + + # Pagination + if limit is not None: + stmt = stmt.limit(limit) + if offset is not None: + stmt = stmt.offset(offset) + _holders: Sequence[tuple[IDXPosition, int, datetime | None]] = ( - db.execute(stmt.order_by(IDXPosition.id)).tuples().all() + db.execute(stmt).tuples().all() ) # Get personal information @@ -1780,7 +1800,17 @@ def list_all_holders( } ) - return json_response(holders) + return json_response( + { + "result_set": { + "count": count, + "total": total, + "limit": limit, + "offset": offset, + }, + "holders": holders, + } + ) # GET: /bond/tokens/{token_address}/holders/count diff --git a/app/routers/share.py b/app/routers/share.py index edfb9222..e68f3e80 100644 --- a/app/routers/share.py +++ b/app/routers/share.py @@ -110,6 +110,7 @@ GetBatchRegisterPersonalInfoResponse, HolderCountResponse, HolderResponse, + HoldersResponse, IbetShareAdditionalIssue, IbetShareCreate, IbetShareRedeem, @@ -1640,13 +1641,16 @@ def delete_scheduled_event( # GET: /share/tokens/{token_address}/holders @router.get( "/tokens/{token_address}/holders", - response_model=List[HolderResponse], + response_model=HoldersResponse, responses=get_routers_responses(422, InvalidParameterError, 404), ) def list_all_holders( db: DBSession, token_address: str, include_former_holder: bool = False, + sort_order: int = Query(0, ge=0, le=1, description="0:asc, 1:desc (created)"), + offset: Optional[int] = Query(None), + limit: Optional[int] = Query(None), issuer_address: str = Header(...), ): """List all share token holders""" @@ -1701,6 +1705,8 @@ def list_all_holders( ) ) + total = db.scalar(select(func.count()).select_from(stmt.subquery())) + if not include_former_holder: stmt = stmt.where( or_( @@ -1712,8 +1718,22 @@ def list_all_holders( ) ) + count = db.scalar(select(func.count()).select_from(stmt.subquery())) + + # Sort + if sort_order == 0: # ASC + stmt = stmt.order_by(IDXPosition.id) + else: # DESC + stmt = stmt.order_by(desc(IDXPosition.id)) + + # Pagination + if limit is not None: + stmt = stmt.limit(limit) + if offset is not None: + stmt = stmt.offset(offset) + _holders: Sequence[tuple[IDXPosition, int, datetime | None]] = ( - db.execute(stmt.order_by(IDXPosition.id)).tuples().all() + db.execute(stmt).tuples().all() ) # Get personal information @@ -1752,6 +1772,7 @@ def list_all_holders( if (_position.modified > _lock_event_latest_created) else _lock_event_latest_created ) + holders.append( { "account_address": _position.account_address, @@ -1765,7 +1786,17 @@ def list_all_holders( } ) - return json_response(holders) + return json_response( + { + "result_set": { + "count": count, + "total": total, + "limit": limit, + "offset": offset, + }, + "holders": holders, + } + ) # GET: /share/tokens/{token_address}/holders/count diff --git a/tests/test_app_routers_bond_tokens_{token_address}_holders_GET.py b/tests/test_app_routers_bond_tokens_{token_address}_holders_GET.py index 7e7b0875..12d5949f 100644 --- a/tests/test_app_routers_bond_tokens_{token_address}_holders_GET.py +++ b/tests/test_app_routers_bond_tokens_{token_address}_holders_GET.py @@ -67,7 +67,10 @@ def test_normal_1(self, client, db): # assertion assert resp.status_code == 200 - assert resp.json() == [] + assert resp.json() == { + "result_set": {"count": 0, "total": 0, "offset": None, "limit": None}, + "holders": [], + } # # 1 record @@ -159,27 +162,30 @@ def test_normal_2(self, client, db): # assertion assert resp.status_code == 200 - assert resp.json() == [ - { - "account_address": _account_address_1, - "personal_information": { - "key_manager": "key_manager_test1", - "name": "name_test1", - "postal_code": "postal_code_test1", - "address": "address_test1", - "email": "email_test1", - "birth": "birth_test1", - "is_corporate": False, - "tax_category": 10, - }, - "balance": 10, - "exchange_balance": 11, - "exchange_commitment": 12, - "pending_transfer": 5, - "locked": 10, - "modified": "2023-10-24T00:02:00", - } - ] + assert resp.json() == { + "result_set": {"count": 1, "total": 1, "offset": None, "limit": None}, + "holders": [ + { + "account_address": _account_address_1, + "personal_information": { + "key_manager": "key_manager_test1", + "name": "name_test1", + "postal_code": "postal_code_test1", + "address": "address_test1", + "email": "email_test1", + "birth": "birth_test1", + "is_corporate": False, + "tax_category": 10, + }, + "balance": 10, + "exchange_balance": 11, + "exchange_commitment": 12, + "pending_transfer": 5, + "locked": 10, + "modified": "2023-10-24T00:02:00", + } + ], + } # # Multi record @@ -345,65 +351,68 @@ def test_normal_3_1(self, client, db): # assertion assert resp.status_code == 200 - assert resp.json() == [ - { - "account_address": _account_address_1, - "personal_information": { - "key_manager": "key_manager_test1", - "name": "name_test1", - "postal_code": "postal_code_test1", - "address": "address_test1", - "email": "email_test1", - "birth": "birth_test1", - "is_corporate": False, - "tax_category": 10, + assert resp.json() == { + "result_set": {"count": 3, "total": 3, "offset": None, "limit": None}, + "holders": [ + { + "account_address": _account_address_1, + "personal_information": { + "key_manager": "key_manager_test1", + "name": "name_test1", + "postal_code": "postal_code_test1", + "address": "address_test1", + "email": "email_test1", + "birth": "birth_test1", + "is_corporate": False, + "tax_category": 10, + }, + "balance": 10, + "exchange_balance": 11, + "exchange_commitment": 12, + "pending_transfer": 5, + "locked": 10, + "modified": "2023-10-24T01:10:00", }, - "balance": 10, - "exchange_balance": 11, - "exchange_commitment": 12, - "pending_transfer": 5, - "locked": 10, - "modified": "2023-10-24T01:10:00", - }, - { - "account_address": _account_address_2, - "personal_information": { - "key_manager": None, - "name": None, - "postal_code": None, - "address": None, - "email": None, - "birth": None, - "is_corporate": None, - "tax_category": None, + { + "account_address": _account_address_2, + "personal_information": { + "key_manager": None, + "name": None, + "postal_code": None, + "address": None, + "email": None, + "birth": None, + "is_corporate": None, + "tax_category": None, + }, + "balance": 20, + "exchange_balance": 21, + "exchange_commitment": 22, + "pending_transfer": 10, + "locked": 20, + "modified": "2023-10-24T02:20:00", }, - "balance": 20, - "exchange_balance": 21, - "exchange_commitment": 22, - "pending_transfer": 10, - "locked": 20, - "modified": "2023-10-24T02:20:00", - }, - { - "account_address": _account_address_3, - "personal_information": { - "key_manager": "key_manager_test1", - "name": "name_test3", - "postal_code": "postal_code_test3", - "address": "address_test3", - "email": "email_test3", - "birth": "birth_test3", - "is_corporate": None, - "tax_category": None, + { + "account_address": _account_address_3, + "personal_information": { + "key_manager": "key_manager_test1", + "name": "name_test3", + "postal_code": "postal_code_test3", + "address": "address_test3", + "email": "email_test3", + "birth": "birth_test3", + "is_corporate": None, + "tax_category": None, + }, + "balance": 99, + "exchange_balance": 99, + "exchange_commitment": 99, + "pending_transfer": 99, + "locked": 30, + "modified": "2023-10-24T05:00:00", }, - "balance": 99, - "exchange_balance": 99, - "exchange_commitment": 99, - "pending_transfer": 99, - "locked": 30, - "modified": "2023-10-24T05:00:00", - }, - ] + ], + } # # Multi record (Including former holder) @@ -494,46 +503,49 @@ def test_normal_3_2(self, client, db): # assertion # former holder who has currently no balance is not listed assert resp.status_code == 200 - assert resp.json() == [ - { - "account_address": _account_address_1, - "personal_information": { - "key_manager": None, - "name": None, - "postal_code": None, - "address": None, - "email": None, - "birth": None, - "is_corporate": None, - "tax_category": None, + assert resp.json() == { + "result_set": {"count": 2, "total": 3, "offset": None, "limit": None}, + "holders": [ + { + "account_address": _account_address_1, + "personal_information": { + "key_manager": None, + "name": None, + "postal_code": None, + "address": None, + "email": None, + "birth": None, + "is_corporate": None, + "tax_category": None, + }, + "balance": 0, + "exchange_balance": 0, + "exchange_commitment": 12, + "pending_transfer": 5, + "locked": 0, + "modified": "2023-10-24T01:00:00", }, - "balance": 0, - "exchange_balance": 0, - "exchange_commitment": 12, - "pending_transfer": 5, - "locked": 0, - "modified": "2023-10-24T01:00:00", - }, - { - "account_address": _account_address_2, - "personal_information": { - "key_manager": None, - "name": None, - "postal_code": None, - "address": None, - "email": None, - "birth": None, - "is_corporate": None, - "tax_category": None, + { + "account_address": _account_address_2, + "personal_information": { + "key_manager": None, + "name": None, + "postal_code": None, + "address": None, + "email": None, + "birth": None, + "is_corporate": None, + "tax_category": None, + }, + "balance": 20, + "exchange_balance": 21, + "exchange_commitment": 0, + "pending_transfer": 0, + "locked": 0, + "modified": "2023-10-24T03:00:00", }, - "balance": 20, - "exchange_balance": 21, - "exchange_commitment": 0, - "pending_transfer": 0, - "locked": 0, - "modified": "2023-10-24T03:00:00", - }, - ] + ], + } # # Multi record (Including former holder) @@ -629,65 +641,675 @@ def test_normal_3_3(self, client, db): # assertion # former holder who has currently no balance is not listed assert resp.status_code == 200 - assert resp.json() == [ - { - "account_address": _account_address_1, - "personal_information": { - "key_manager": "key_manager_test1", - "name": "name_test1", - "postal_code": "postal_code_test1", - "address": "address_test1", - "email": "email_test1", - "birth": "birth_test1", - "is_corporate": False, - "tax_category": 10, + assert resp.json() == { + "result_set": {"count": 3, "total": 3, "offset": None, "limit": None}, + "holders": [ + { + "account_address": _account_address_1, + "personal_information": { + "key_manager": "key_manager_test1", + "name": "name_test1", + "postal_code": "postal_code_test1", + "address": "address_test1", + "email": "email_test1", + "birth": "birth_test1", + "is_corporate": False, + "tax_category": 10, + }, + "balance": 0, + "exchange_balance": 0, + "exchange_commitment": 12, + "pending_transfer": 5, + "locked": 0, + "modified": "2023-10-24T01:00:00", + }, + { + "account_address": _account_address_2, + "personal_information": { + "key_manager": None, + "name": None, + "postal_code": None, + "address": None, + "email": None, + "birth": None, + "is_corporate": None, + "tax_category": None, + }, + "balance": 20, + "exchange_balance": 21, + "exchange_commitment": 0, + "pending_transfer": 0, + "locked": 0, + "modified": "2023-10-24T02:00:00", + }, + { + "account_address": _account_address_3, + "personal_information": { + "key_manager": "key_manager_test1", + "name": "name_test3", + "postal_code": "postal_code_test3", + "address": "address_test3", + "email": "email_test3", + "birth": "birth_test3", + "is_corporate": None, + "tax_category": None, + }, + "balance": 0, + "exchange_balance": 0, + "exchange_commitment": 0, + "pending_transfer": 0, + "locked": 0, + "modified": "2023-10-24T03:00:00", + }, + ], + } + + # + # Sort Order + def test_normal_4(self, client, db): + user = config_eth_account("user1") + _issuer_address = user["address"] + _token_address = "0x82b1c9374aB625380bd498a3d9dF4033B8A0E3Bb" + _account_address_1 = "0xb75c7545b9230FEe99b7af370D38eBd3DAD929f7" + _account_address_2 = "0x3F198534Bbe3B2a197d3B317d41392F348EAC707" + _account_address_3 = "0x8277D905F37F8a9717F5718d0daC21495dFE74bf" + + account = Account() + account.issuer_address = _issuer_address + db.add(account) + + token = Token() + token.type = TokenType.IBET_STRAIGHT_BOND.value + token.tx_hash = "" + token.issuer_address = _issuer_address + token.token_address = _token_address + token.abi = "" + token.version = TokenVersion.V_23_12 + db.add(token) + + # prepare data: account_address_1 + idx_position_1 = IDXPosition() + idx_position_1.token_address = _token_address + idx_position_1.account_address = _account_address_1 + idx_position_1.balance = 10 + idx_position_1.exchange_balance = 11 + idx_position_1.exchange_commitment = 12 + idx_position_1.pending_transfer = 5 + idx_position_1.modified = datetime(2023, 10, 24, 0, 0, 0) + db.add(idx_position_1) + + idx_locked_position = IDXLockedPosition() + idx_locked_position.token_address = _token_address + idx_locked_position.lock_address = ( + "0x1234567890123456789012345678900000000001" # lock address 1 + ) + idx_locked_position.account_address = _account_address_1 + idx_locked_position.value = 5 + idx_locked_position.modified = datetime(2023, 10, 24, 1, 0, 0) + db.add(idx_locked_position) + + idx_locked_position = IDXLockedPosition() + idx_locked_position.token_address = _token_address + idx_locked_position.lock_address = ( + "0x1234567890123456789012345678900000000002" # lock address 2 + ) + idx_locked_position.account_address = _account_address_1 + idx_locked_position.value = 5 + idx_locked_position.modified = datetime(2023, 10, 24, 1, 10, 0) + db.add(idx_locked_position) + + idx_personal_info_1 = IDXPersonalInfo() + idx_personal_info_1.account_address = _account_address_1 + idx_personal_info_1.issuer_address = _issuer_address + idx_personal_info_1.personal_info = { + "key_manager": "key_manager_test1", + "name": "name_test1", + "postal_code": "postal_code_test1", + "address": "address_test1", + "email": "email_test1", + "birth": "birth_test1", + "is_corporate": False, + "tax_category": 10, + } + db.add(idx_personal_info_1) + + # prepare data: account_address_2 + idx_position_2 = IDXPosition() + idx_position_2.token_address = _token_address + idx_position_2.account_address = _account_address_2 + idx_position_2.balance = 20 + idx_position_2.exchange_balance = 21 + idx_position_2.exchange_commitment = 22 + idx_position_2.pending_transfer = 10 + idx_position_2.modified = datetime(2023, 10, 24, 2, 0, 0) + db.add(idx_position_2) + + idx_locked_position = IDXLockedPosition() + idx_locked_position.token_address = _token_address + idx_locked_position.lock_address = ( + "0x1234567890123456789012345678900000000001" # lock address 1 + ) + idx_locked_position.account_address = _account_address_2 + idx_locked_position.value = 10 + idx_locked_position.modified = datetime(2023, 10, 24, 2, 10, 0) + db.add(idx_locked_position) + + idx_locked_position = IDXLockedPosition() + idx_locked_position.token_address = _token_address + idx_locked_position.lock_address = ( + "0x1234567890123456789012345678900000000002" # lock address 2 + ) + idx_locked_position.account_address = _account_address_2 + idx_locked_position.value = 10 + idx_locked_position.modified = datetime(2023, 10, 24, 2, 20, 0) + db.add(idx_locked_position) + + # prepare data: account_address_3 + idx_position_3 = IDXPosition() + idx_position_3.token_address = _token_address + idx_position_3.account_address = _account_address_3 + idx_position_3.balance = 99 + idx_position_3.exchange_balance = 99 + idx_position_3.exchange_commitment = 99 + idx_position_3.pending_transfer = 99 + idx_position_3.modified = datetime(2023, 10, 24, 3, 0, 0) + db.add(idx_position_3) + + idx_locked_position = IDXLockedPosition() + idx_locked_position.token_address = _token_address + idx_locked_position.lock_address = ( + "0x1234567890123456789012345678900000000001" # lock address 1 + ) + idx_locked_position.account_address = _account_address_3 + idx_locked_position.value = 15 + idx_locked_position.modified = datetime(2023, 10, 24, 4, 0, 0) + db.add(idx_locked_position) + + idx_locked_position = IDXLockedPosition() + idx_locked_position.token_address = _token_address + idx_locked_position.lock_address = ( + "0x1234567890123456789012345678900000000002" # lock address 2 + ) + idx_locked_position.account_address = _account_address_3 + idx_locked_position.value = 15 + idx_locked_position.modified = datetime(2023, 10, 24, 5, 0, 0) + db.add(idx_locked_position) + + # Other locked position + _locked_position = IDXLockedPosition() + _locked_position.token_address = "other_token_address" + _locked_position.lock_address = ( + "0x1234567890123456789012345678900000000002" # lock address 2 + ) + _locked_position.account_address = _account_address_1 + _locked_position.value = 5 + _locked_position.modified = datetime(2023, 10, 25, 0, 2, 0) + db.add(_locked_position) + + idx_personal_info_3 = IDXPersonalInfo() + idx_personal_info_3.account_address = _account_address_3 + idx_personal_info_3.issuer_address = _issuer_address + idx_personal_info_3.personal_info = { + "key_manager": "key_manager_test1", + "name": "name_test3", + "postal_code": "postal_code_test3", + "address": "address_test3", + "email": "email_test3", + "birth": "birth_test3" + # PersonalInfo is partially registered. + } + db.add(idx_personal_info_3) + + # request target API + resp = client.get( + self.base_url.format(_token_address), + headers={"issuer-address": _issuer_address}, + params={"sort_order": 1}, + ) + + # assertion + assert resp.status_code == 200 + assert resp.json() == { + "result_set": {"count": 3, "total": 3, "offset": None, "limit": None}, + "holders": [ + { + "account_address": _account_address_3, + "personal_information": { + "key_manager": "key_manager_test1", + "name": "name_test3", + "postal_code": "postal_code_test3", + "address": "address_test3", + "email": "email_test3", + "birth": "birth_test3", + "is_corporate": None, + "tax_category": None, + }, + "balance": 99, + "exchange_balance": 99, + "exchange_commitment": 99, + "pending_transfer": 99, + "locked": 30, + "modified": "2023-10-24T05:00:00", }, - "balance": 0, - "exchange_balance": 0, - "exchange_commitment": 12, - "pending_transfer": 5, - "locked": 0, - "modified": "2023-10-24T01:00:00", - }, - { - "account_address": _account_address_2, - "personal_information": { - "key_manager": None, - "name": None, - "postal_code": None, - "address": None, - "email": None, - "birth": None, - "is_corporate": None, - "tax_category": None, + { + "account_address": _account_address_2, + "personal_information": { + "key_manager": None, + "name": None, + "postal_code": None, + "address": None, + "email": None, + "birth": None, + "is_corporate": None, + "tax_category": None, + }, + "balance": 20, + "exchange_balance": 21, + "exchange_commitment": 22, + "pending_transfer": 10, + "locked": 20, + "modified": "2023-10-24T02:20:00", }, - "balance": 20, - "exchange_balance": 21, - "exchange_commitment": 0, - "pending_transfer": 0, - "locked": 0, - "modified": "2023-10-24T02:00:00", - }, - { - "account_address": _account_address_3, - "personal_information": { - "key_manager": "key_manager_test1", - "name": "name_test3", - "postal_code": "postal_code_test3", - "address": "address_test3", - "email": "email_test3", - "birth": "birth_test3", - "is_corporate": None, - "tax_category": None, + { + "account_address": _account_address_1, + "personal_information": { + "key_manager": "key_manager_test1", + "name": "name_test1", + "postal_code": "postal_code_test1", + "address": "address_test1", + "email": "email_test1", + "birth": "birth_test1", + "is_corporate": False, + "tax_category": 10, + }, + "balance": 10, + "exchange_balance": 11, + "exchange_commitment": 12, + "pending_transfer": 5, + "locked": 10, + "modified": "2023-10-24T01:10:00", }, - "balance": 0, - "exchange_balance": 0, - "exchange_commitment": 0, - "pending_transfer": 0, - "locked": 0, - "modified": "2023-10-24T03:00:00", - }, - ] + ], + } + + # + # Pagination + def test_normal_5_1(self, client, db): + user = config_eth_account("user1") + _issuer_address = user["address"] + _token_address = "0x82b1c9374aB625380bd498a3d9dF4033B8A0E3Bb" + _account_address_1 = "0xb75c7545b9230FEe99b7af370D38eBd3DAD929f7" + _account_address_2 = "0x3F198534Bbe3B2a197d3B317d41392F348EAC707" + _account_address_3 = "0x8277D905F37F8a9717F5718d0daC21495dFE74bf" + + account = Account() + account.issuer_address = _issuer_address + db.add(account) + + token = Token() + token.type = TokenType.IBET_STRAIGHT_BOND.value + token.tx_hash = "" + token.issuer_address = _issuer_address + token.token_address = _token_address + token.abi = "" + token.version = TokenVersion.V_23_12 + db.add(token) + + # prepare data: account_address_1 + idx_position_1 = IDXPosition() + idx_position_1.token_address = _token_address + idx_position_1.account_address = _account_address_1 + idx_position_1.balance = 10 + idx_position_1.exchange_balance = 11 + idx_position_1.exchange_commitment = 12 + idx_position_1.pending_transfer = 5 + idx_position_1.modified = datetime(2023, 10, 24, 0, 0, 0) + db.add(idx_position_1) + + idx_locked_position = IDXLockedPosition() + idx_locked_position.token_address = _token_address + idx_locked_position.lock_address = ( + "0x1234567890123456789012345678900000000001" # lock address 1 + ) + idx_locked_position.account_address = _account_address_1 + idx_locked_position.value = 5 + idx_locked_position.modified = datetime(2023, 10, 24, 1, 0, 0) + db.add(idx_locked_position) + + idx_locked_position = IDXLockedPosition() + idx_locked_position.token_address = _token_address + idx_locked_position.lock_address = ( + "0x1234567890123456789012345678900000000002" # lock address 2 + ) + idx_locked_position.account_address = _account_address_1 + idx_locked_position.value = 5 + idx_locked_position.modified = datetime(2023, 10, 24, 1, 10, 0) + db.add(idx_locked_position) + + idx_personal_info_1 = IDXPersonalInfo() + idx_personal_info_1.account_address = _account_address_1 + idx_personal_info_1.issuer_address = _issuer_address + idx_personal_info_1.personal_info = { + "key_manager": "key_manager_test1", + "name": "name_test1", + "postal_code": "postal_code_test1", + "address": "address_test1", + "email": "email_test1", + "birth": "birth_test1", + "is_corporate": False, + "tax_category": 10, + } + db.add(idx_personal_info_1) + + # prepare data: account_address_2 + idx_position_2 = IDXPosition() + idx_position_2.token_address = _token_address + idx_position_2.account_address = _account_address_2 + idx_position_2.balance = 20 + idx_position_2.exchange_balance = 21 + idx_position_2.exchange_commitment = 22 + idx_position_2.pending_transfer = 10 + idx_position_2.modified = datetime(2023, 10, 24, 2, 0, 0) + db.add(idx_position_2) + + idx_locked_position = IDXLockedPosition() + idx_locked_position.token_address = _token_address + idx_locked_position.lock_address = ( + "0x1234567890123456789012345678900000000001" # lock address 1 + ) + idx_locked_position.account_address = _account_address_2 + idx_locked_position.value = 10 + idx_locked_position.modified = datetime(2023, 10, 24, 2, 10, 0) + db.add(idx_locked_position) + + idx_locked_position = IDXLockedPosition() + idx_locked_position.token_address = _token_address + idx_locked_position.lock_address = ( + "0x1234567890123456789012345678900000000002" # lock address 2 + ) + idx_locked_position.account_address = _account_address_2 + idx_locked_position.value = 10 + idx_locked_position.modified = datetime(2023, 10, 24, 2, 20, 0) + db.add(idx_locked_position) + + # prepare data: account_address_3 + idx_position_3 = IDXPosition() + idx_position_3.token_address = _token_address + idx_position_3.account_address = _account_address_3 + idx_position_3.balance = 99 + idx_position_3.exchange_balance = 99 + idx_position_3.exchange_commitment = 99 + idx_position_3.pending_transfer = 99 + idx_position_3.modified = datetime(2023, 10, 24, 3, 0, 0) + db.add(idx_position_3) + + idx_locked_position = IDXLockedPosition() + idx_locked_position.token_address = _token_address + idx_locked_position.lock_address = ( + "0x1234567890123456789012345678900000000001" # lock address 1 + ) + idx_locked_position.account_address = _account_address_3 + idx_locked_position.value = 15 + idx_locked_position.modified = datetime(2023, 10, 24, 4, 0, 0) + db.add(idx_locked_position) + + idx_locked_position = IDXLockedPosition() + idx_locked_position.token_address = _token_address + idx_locked_position.lock_address = ( + "0x1234567890123456789012345678900000000002" # lock address 2 + ) + idx_locked_position.account_address = _account_address_3 + idx_locked_position.value = 15 + idx_locked_position.modified = datetime(2023, 10, 24, 5, 0, 0) + db.add(idx_locked_position) + + # Other locked position + _locked_position = IDXLockedPosition() + _locked_position.token_address = "other_token_address" + _locked_position.lock_address = ( + "0x1234567890123456789012345678900000000002" # lock address 2 + ) + _locked_position.account_address = _account_address_1 + _locked_position.value = 5 + _locked_position.modified = datetime(2023, 10, 25, 0, 2, 0) + db.add(_locked_position) + + idx_personal_info_3 = IDXPersonalInfo() + idx_personal_info_3.account_address = _account_address_3 + idx_personal_info_3.issuer_address = _issuer_address + idx_personal_info_3.personal_info = { + "key_manager": "key_manager_test1", + "name": "name_test3", + "postal_code": "postal_code_test3", + "address": "address_test3", + "email": "email_test3", + "birth": "birth_test3" + # PersonalInfo is partially registered. + } + db.add(idx_personal_info_3) + + # request target API + resp = client.get( + self.base_url.format(_token_address), + headers={"issuer-address": _issuer_address}, + params={"offset": 1, "limit": 2}, + ) + + # assertion + assert resp.status_code == 200 + assert resp.json() == { + "result_set": {"count": 3, "total": 3, "offset": 1, "limit": 2}, + "holders": [ + { + "account_address": _account_address_2, + "personal_information": { + "key_manager": None, + "name": None, + "postal_code": None, + "address": None, + "email": None, + "birth": None, + "is_corporate": None, + "tax_category": None, + }, + "balance": 20, + "exchange_balance": 21, + "exchange_commitment": 22, + "pending_transfer": 10, + "locked": 20, + "modified": "2023-10-24T02:20:00", + }, + { + "account_address": _account_address_3, + "personal_information": { + "key_manager": "key_manager_test1", + "name": "name_test3", + "postal_code": "postal_code_test3", + "address": "address_test3", + "email": "email_test3", + "birth": "birth_test3", + "is_corporate": None, + "tax_category": None, + }, + "balance": 99, + "exchange_balance": 99, + "exchange_commitment": 99, + "pending_transfer": 99, + "locked": 30, + "modified": "2023-10-24T05:00:00", + }, + ], + } + + # + # Pagination (over offset) + def test_normal_5_2(self, client, db): + user = config_eth_account("user1") + _issuer_address = user["address"] + _token_address = "0x82b1c9374aB625380bd498a3d9dF4033B8A0E3Bb" + _account_address_1 = "0xb75c7545b9230FEe99b7af370D38eBd3DAD929f7" + _account_address_2 = "0x3F198534Bbe3B2a197d3B317d41392F348EAC707" + _account_address_3 = "0x8277D905F37F8a9717F5718d0daC21495dFE74bf" + + account = Account() + account.issuer_address = _issuer_address + db.add(account) + + token = Token() + token.type = TokenType.IBET_STRAIGHT_BOND.value + token.tx_hash = "" + token.issuer_address = _issuer_address + token.token_address = _token_address + token.abi = "" + token.version = TokenVersion.V_23_12 + db.add(token) + + # prepare data: account_address_1 + idx_position_1 = IDXPosition() + idx_position_1.token_address = _token_address + idx_position_1.account_address = _account_address_1 + idx_position_1.balance = 10 + idx_position_1.exchange_balance = 11 + idx_position_1.exchange_commitment = 12 + idx_position_1.pending_transfer = 5 + idx_position_1.modified = datetime(2023, 10, 24, 0, 0, 0) + db.add(idx_position_1) + + idx_locked_position = IDXLockedPosition() + idx_locked_position.token_address = _token_address + idx_locked_position.lock_address = ( + "0x1234567890123456789012345678900000000001" # lock address 1 + ) + idx_locked_position.account_address = _account_address_1 + idx_locked_position.value = 5 + idx_locked_position.modified = datetime(2023, 10, 24, 1, 0, 0) + db.add(idx_locked_position) + + idx_locked_position = IDXLockedPosition() + idx_locked_position.token_address = _token_address + idx_locked_position.lock_address = ( + "0x1234567890123456789012345678900000000002" # lock address 2 + ) + idx_locked_position.account_address = _account_address_1 + idx_locked_position.value = 5 + idx_locked_position.modified = datetime(2023, 10, 24, 1, 10, 0) + db.add(idx_locked_position) + + idx_personal_info_1 = IDXPersonalInfo() + idx_personal_info_1.account_address = _account_address_1 + idx_personal_info_1.issuer_address = _issuer_address + idx_personal_info_1.personal_info = { + "key_manager": "key_manager_test1", + "name": "name_test1", + "postal_code": "postal_code_test1", + "address": "address_test1", + "email": "email_test1", + "birth": "birth_test1", + "is_corporate": False, + "tax_category": 10, + } + db.add(idx_personal_info_1) + + # prepare data: account_address_2 + idx_position_2 = IDXPosition() + idx_position_2.token_address = _token_address + idx_position_2.account_address = _account_address_2 + idx_position_2.balance = 20 + idx_position_2.exchange_balance = 21 + idx_position_2.exchange_commitment = 22 + idx_position_2.pending_transfer = 10 + idx_position_2.modified = datetime(2023, 10, 24, 2, 0, 0) + db.add(idx_position_2) + + idx_locked_position = IDXLockedPosition() + idx_locked_position.token_address = _token_address + idx_locked_position.lock_address = ( + "0x1234567890123456789012345678900000000001" # lock address 1 + ) + idx_locked_position.account_address = _account_address_2 + idx_locked_position.value = 10 + idx_locked_position.modified = datetime(2023, 10, 24, 2, 10, 0) + db.add(idx_locked_position) + + idx_locked_position = IDXLockedPosition() + idx_locked_position.token_address = _token_address + idx_locked_position.lock_address = ( + "0x1234567890123456789012345678900000000002" # lock address 2 + ) + idx_locked_position.account_address = _account_address_2 + idx_locked_position.value = 10 + idx_locked_position.modified = datetime(2023, 10, 24, 2, 20, 0) + db.add(idx_locked_position) + + # prepare data: account_address_3 + idx_position_3 = IDXPosition() + idx_position_3.token_address = _token_address + idx_position_3.account_address = _account_address_3 + idx_position_3.balance = 99 + idx_position_3.exchange_balance = 99 + idx_position_3.exchange_commitment = 99 + idx_position_3.pending_transfer = 99 + idx_position_3.modified = datetime(2023, 10, 24, 3, 0, 0) + db.add(idx_position_3) + + idx_locked_position = IDXLockedPosition() + idx_locked_position.token_address = _token_address + idx_locked_position.lock_address = ( + "0x1234567890123456789012345678900000000001" # lock address 1 + ) + idx_locked_position.account_address = _account_address_3 + idx_locked_position.value = 15 + idx_locked_position.modified = datetime(2023, 10, 24, 4, 0, 0) + db.add(idx_locked_position) + + idx_locked_position = IDXLockedPosition() + idx_locked_position.token_address = _token_address + idx_locked_position.lock_address = ( + "0x1234567890123456789012345678900000000002" # lock address 2 + ) + idx_locked_position.account_address = _account_address_3 + idx_locked_position.value = 15 + idx_locked_position.modified = datetime(2023, 10, 24, 5, 0, 0) + db.add(idx_locked_position) + + # Other locked position + _locked_position = IDXLockedPosition() + _locked_position.token_address = "other_token_address" + _locked_position.lock_address = ( + "0x1234567890123456789012345678900000000002" # lock address 2 + ) + _locked_position.account_address = _account_address_1 + _locked_position.value = 5 + _locked_position.modified = datetime(2023, 10, 25, 0, 2, 0) + db.add(_locked_position) + + idx_personal_info_3 = IDXPersonalInfo() + idx_personal_info_3.account_address = _account_address_3 + idx_personal_info_3.issuer_address = _issuer_address + idx_personal_info_3.personal_info = { + "key_manager": "key_manager_test1", + "name": "name_test3", + "postal_code": "postal_code_test3", + "address": "address_test3", + "email": "email_test3", + "birth": "birth_test3" + # PersonalInfo is partially registered. + } + db.add(idx_personal_info_3) + + # request target API + resp = client.get( + self.base_url.format(_token_address), + headers={"issuer-address": _issuer_address}, + params={"offset": 4, "limit": 1}, + ) + + # assertion + assert resp.status_code == 200 + assert resp.json() == { + "result_set": {"count": 3, "total": 3, "offset": 4, "limit": 1}, + "holders": [], + } ########################################################################### # Error Case diff --git a/tests/test_app_routers_share_tokens_{token_address}_holders_GET.py b/tests/test_app_routers_share_tokens_{token_address}_holders_GET.py index 2637c3b9..4d55558f 100644 --- a/tests/test_app_routers_share_tokens_{token_address}_holders_GET.py +++ b/tests/test_app_routers_share_tokens_{token_address}_holders_GET.py @@ -67,7 +67,10 @@ def test_normal_1(self, client, db): # assertion assert resp.status_code == 200 - assert resp.json() == [] + assert resp.json() == { + "result_set": {"count": 0, "total": 0, "offset": None, "limit": None}, + "holders": [], + } # # 1 record @@ -148,27 +151,30 @@ def test_normal_2(self, client, db): # assertion assert resp.status_code == 200 - assert resp.json() == [ - { - "account_address": _account_address_1, - "personal_information": { - "key_manager": "key_manager_test1", - "name": "name_test1", - "postal_code": "postal_code_test1", - "address": "address_test1", - "email": "email_test1", - "birth": "birth_test1", - "is_corporate": False, - "tax_category": 10, - }, - "balance": 10, - "exchange_balance": 11, - "exchange_commitment": 12, - "pending_transfer": 5, - "locked": 10, - "modified": "2023-10-24T00:02:00", - } - ] + assert resp.json() == { + "result_set": {"count": 1, "total": 1, "offset": None, "limit": None}, + "holders": [ + { + "account_address": _account_address_1, + "personal_information": { + "key_manager": "key_manager_test1", + "name": "name_test1", + "postal_code": "postal_code_test1", + "address": "address_test1", + "email": "email_test1", + "birth": "birth_test1", + "is_corporate": False, + "tax_category": 10, + }, + "balance": 10, + "exchange_balance": 11, + "exchange_commitment": 12, + "pending_transfer": 5, + "locked": 10, + "modified": "2023-10-24T00:02:00", + } + ], + } # # Multi record @@ -334,65 +340,68 @@ def test_normal_3_1(self, client, db): # assertion assert resp.status_code == 200 - assert resp.json() == [ - { - "account_address": _account_address_1, - "personal_information": { - "key_manager": "key_manager_test1", - "name": "name_test1", - "postal_code": "postal_code_test1", - "address": "address_test1", - "email": "email_test1", - "birth": "birth_test1", - "is_corporate": False, - "tax_category": 10, + assert resp.json() == { + "result_set": {"count": 3, "total": 3, "offset": None, "limit": None}, + "holders": [ + { + "account_address": _account_address_1, + "personal_information": { + "key_manager": "key_manager_test1", + "name": "name_test1", + "postal_code": "postal_code_test1", + "address": "address_test1", + "email": "email_test1", + "birth": "birth_test1", + "is_corporate": False, + "tax_category": 10, + }, + "balance": 10, + "exchange_balance": 11, + "exchange_commitment": 12, + "pending_transfer": 5, + "locked": 10, + "modified": "2023-10-24T01:10:00", }, - "balance": 10, - "exchange_balance": 11, - "exchange_commitment": 12, - "pending_transfer": 5, - "locked": 10, - "modified": "2023-10-24T01:10:00", - }, - { - "account_address": _account_address_2, - "personal_information": { - "key_manager": None, - "name": None, - "postal_code": None, - "address": None, - "email": None, - "birth": None, - "is_corporate": None, - "tax_category": None, + { + "account_address": _account_address_2, + "personal_information": { + "key_manager": None, + "name": None, + "postal_code": None, + "address": None, + "email": None, + "birth": None, + "is_corporate": None, + "tax_category": None, + }, + "balance": 20, + "exchange_balance": 21, + "exchange_commitment": 22, + "pending_transfer": 10, + "locked": 20, + "modified": "2023-10-24T02:20:00", }, - "balance": 20, - "exchange_balance": 21, - "exchange_commitment": 22, - "pending_transfer": 10, - "locked": 20, - "modified": "2023-10-24T02:20:00", - }, - { - "account_address": _account_address_3, - "personal_information": { - "key_manager": "key_manager_test1", - "name": "name_test3", - "postal_code": "postal_code_test3", - "address": "address_test3", - "email": "email_test3", - "birth": "birth_test3", - "is_corporate": None, - "tax_category": None, + { + "account_address": _account_address_3, + "personal_information": { + "key_manager": "key_manager_test1", + "name": "name_test3", + "postal_code": "postal_code_test3", + "address": "address_test3", + "email": "email_test3", + "birth": "birth_test3", + "is_corporate": None, + "tax_category": None, + }, + "balance": 99, + "exchange_balance": 99, + "exchange_commitment": 99, + "pending_transfer": 99, + "locked": 30, + "modified": "2023-10-24T05:00:00", }, - "balance": 99, - "exchange_balance": 99, - "exchange_commitment": 99, - "pending_transfer": 99, - "locked": 30, - "modified": "2023-10-24T05:00:00", - }, - ] + ], + } # # Multi record (Including former holder) @@ -497,46 +506,49 @@ def test_normal_3_2(self, client, db): # assertion # former holder who has currently no balance is not listed assert resp.status_code == 200 - assert resp.json() == [ - { - "account_address": _account_address_1, - "personal_information": { - "key_manager": None, - "name": None, - "postal_code": None, - "address": None, - "email": None, - "birth": None, - "is_corporate": None, - "tax_category": None, + assert resp.json() == { + "result_set": {"count": 2, "total": 3, "offset": None, "limit": None}, + "holders": [ + { + "account_address": _account_address_1, + "personal_information": { + "key_manager": None, + "name": None, + "postal_code": None, + "address": None, + "email": None, + "birth": None, + "is_corporate": None, + "tax_category": None, + }, + "balance": 0, + "exchange_balance": 0, + "exchange_commitment": 12, + "pending_transfer": 5, + "locked": 0, + "modified": "2023-10-24T01:00:00", }, - "balance": 0, - "exchange_balance": 0, - "exchange_commitment": 12, - "pending_transfer": 5, - "locked": 0, - "modified": "2023-10-24T01:00:00", - }, - { - "account_address": _account_address_2, - "personal_information": { - "key_manager": None, - "name": None, - "postal_code": None, - "address": None, - "email": None, - "birth": None, - "is_corporate": None, - "tax_category": None, + { + "account_address": _account_address_2, + "personal_information": { + "key_manager": None, + "name": None, + "postal_code": None, + "address": None, + "email": None, + "birth": None, + "is_corporate": None, + "tax_category": None, + }, + "balance": 20, + "exchange_balance": 21, + "exchange_commitment": 0, + "pending_transfer": 0, + "locked": 0, + "modified": "2023-10-24T03:00:00", }, - "balance": 20, - "exchange_balance": 21, - "exchange_commitment": 0, - "pending_transfer": 0, - "locked": 0, - "modified": "2023-10-24T03:00:00", - }, - ] + ], + } # # Multi record (Including former holder) @@ -632,65 +644,675 @@ def test_normal_3_3(self, client, db): # assertion # former holder who has currently no balance is not listed assert resp.status_code == 200 - assert resp.json() == [ - { - "account_address": _account_address_1, - "personal_information": { - "key_manager": "key_manager_test1", - "name": "name_test1", - "postal_code": "postal_code_test1", - "address": "address_test1", - "email": "email_test1", - "birth": "birth_test1", - "is_corporate": False, - "tax_category": 10, + assert resp.json() == { + "result_set": {"count": 3, "total": 3, "offset": None, "limit": None}, + "holders": [ + { + "account_address": _account_address_1, + "personal_information": { + "key_manager": "key_manager_test1", + "name": "name_test1", + "postal_code": "postal_code_test1", + "address": "address_test1", + "email": "email_test1", + "birth": "birth_test1", + "is_corporate": False, + "tax_category": 10, + }, + "balance": 0, + "exchange_balance": 0, + "exchange_commitment": 12, + "pending_transfer": 5, + "locked": 0, + "modified": "2023-10-24T01:00:00", + }, + { + "account_address": _account_address_2, + "personal_information": { + "key_manager": None, + "name": None, + "postal_code": None, + "address": None, + "email": None, + "birth": None, + "is_corporate": None, + "tax_category": None, + }, + "balance": 20, + "exchange_balance": 21, + "exchange_commitment": 0, + "pending_transfer": 0, + "locked": 0, + "modified": "2023-10-24T02:00:00", + }, + { + "account_address": _account_address_3, + "personal_information": { + "key_manager": "key_manager_test1", + "name": "name_test3", + "postal_code": "postal_code_test3", + "address": "address_test3", + "email": "email_test3", + "birth": "birth_test3", + "is_corporate": None, + "tax_category": None, + }, + "balance": 0, + "exchange_balance": 0, + "exchange_commitment": 0, + "pending_transfer": 0, + "locked": 0, + "modified": "2023-10-24T03:00:00", + }, + ], + } + + # + # Sort Order + def test_normal_4(self, client, db): + user = config_eth_account("user1") + _issuer_address = user["address"] + _token_address = "0x82b1c9374aB625380bd498a3d9dF4033B8A0E3Bb" + _account_address_1 = "0xb75c7545b9230FEe99b7af370D38eBd3DAD929f7" + _account_address_2 = "0x3F198534Bbe3B2a197d3B317d41392F348EAC707" + _account_address_3 = "0x8277D905F37F8a9717F5718d0daC21495dFE74bf" + + account = Account() + account.issuer_address = _issuer_address + db.add(account) + + token = Token() + token.type = TokenType.IBET_SHARE.value + token.tx_hash = "" + token.issuer_address = _issuer_address + token.token_address = _token_address + token.abi = "" + token.version = TokenVersion.V_22_12 + db.add(token) + + # prepare data: account_address_1 + idx_position_1 = IDXPosition() + idx_position_1.token_address = _token_address + idx_position_1.account_address = _account_address_1 + idx_position_1.balance = 10 + idx_position_1.exchange_balance = 11 + idx_position_1.exchange_commitment = 12 + idx_position_1.pending_transfer = 5 + idx_position_1.modified = datetime(2023, 10, 24, 0, 0, 0) + db.add(idx_position_1) + + idx_locked_position = IDXLockedPosition() + idx_locked_position.token_address = _token_address + idx_locked_position.lock_address = ( + "0x1234567890123456789012345678900000000001" # lock address 1 + ) + idx_locked_position.account_address = _account_address_1 + idx_locked_position.value = 5 + idx_locked_position.modified = datetime(2023, 10, 24, 1, 0, 0) + db.add(idx_locked_position) + + idx_locked_position = IDXLockedPosition() + idx_locked_position.token_address = _token_address + idx_locked_position.lock_address = ( + "0x1234567890123456789012345678900000000002" # lock address 2 + ) + idx_locked_position.account_address = _account_address_1 + idx_locked_position.value = 5 + idx_locked_position.modified = datetime(2023, 10, 24, 1, 10, 0) + db.add(idx_locked_position) + + idx_personal_info_1 = IDXPersonalInfo() + idx_personal_info_1.account_address = _account_address_1 + idx_personal_info_1.issuer_address = _issuer_address + idx_personal_info_1.personal_info = { + "key_manager": "key_manager_test1", + "name": "name_test1", + "postal_code": "postal_code_test1", + "address": "address_test1", + "email": "email_test1", + "birth": "birth_test1", + "is_corporate": False, + "tax_category": 10, + } + db.add(idx_personal_info_1) + + # prepare data: account_address_2 + idx_position_2 = IDXPosition() + idx_position_2.token_address = _token_address + idx_position_2.account_address = _account_address_2 + idx_position_2.balance = 20 + idx_position_2.exchange_balance = 21 + idx_position_2.exchange_commitment = 22 + idx_position_2.pending_transfer = 10 + idx_position_2.modified = datetime(2023, 10, 24, 2, 0, 0) + db.add(idx_position_2) + + idx_locked_position = IDXLockedPosition() + idx_locked_position.token_address = _token_address + idx_locked_position.lock_address = ( + "0x1234567890123456789012345678900000000001" # lock address 1 + ) + idx_locked_position.account_address = _account_address_2 + idx_locked_position.value = 10 + idx_locked_position.modified = datetime(2023, 10, 24, 2, 10, 0) + db.add(idx_locked_position) + + idx_locked_position = IDXLockedPosition() + idx_locked_position.token_address = _token_address + idx_locked_position.lock_address = ( + "0x1234567890123456789012345678900000000002" # lock address 2 + ) + idx_locked_position.account_address = _account_address_2 + idx_locked_position.value = 10 + idx_locked_position.modified = datetime(2023, 10, 24, 2, 20, 0) + db.add(idx_locked_position) + + # prepare data: account_address_3 + idx_position_3 = IDXPosition() + idx_position_3.token_address = _token_address + idx_position_3.account_address = _account_address_3 + idx_position_3.balance = 99 + idx_position_3.exchange_balance = 99 + idx_position_3.exchange_commitment = 99 + idx_position_3.pending_transfer = 99 + idx_position_3.modified = datetime(2023, 10, 24, 3, 0, 0) + db.add(idx_position_3) + + idx_locked_position = IDXLockedPosition() + idx_locked_position.token_address = _token_address + idx_locked_position.lock_address = ( + "0x1234567890123456789012345678900000000001" # lock address 1 + ) + idx_locked_position.account_address = _account_address_3 + idx_locked_position.value = 15 + idx_locked_position.modified = datetime(2023, 10, 24, 4, 0, 0) + db.add(idx_locked_position) + + idx_locked_position = IDXLockedPosition() + idx_locked_position.token_address = _token_address + idx_locked_position.lock_address = ( + "0x1234567890123456789012345678900000000002" # lock address 2 + ) + idx_locked_position.account_address = _account_address_3 + idx_locked_position.value = 15 + idx_locked_position.modified = datetime(2023, 10, 24, 5, 0, 0) + db.add(idx_locked_position) + + # Other locked position + _locked_position = IDXLockedPosition() + _locked_position.token_address = "other_token_address" + _locked_position.lock_address = ( + "0x1234567890123456789012345678900000000002" # lock address 2 + ) + _locked_position.account_address = _account_address_1 + _locked_position.value = 5 + _locked_position.modified = datetime(2023, 10, 25, 0, 2, 0) + db.add(_locked_position) + + idx_personal_info_3 = IDXPersonalInfo() + idx_personal_info_3.account_address = _account_address_3 + idx_personal_info_3.issuer_address = _issuer_address + idx_personal_info_3.personal_info = { + "key_manager": "key_manager_test1", + "name": "name_test3", + "postal_code": "postal_code_test3", + "address": "address_test3", + "email": "email_test3", + "birth": "birth_test3" + # PersonalInfo is partially registered. + } + db.add(idx_personal_info_3) + + # request target API + resp = client.get( + self.base_url.format(_token_address), + headers={"issuer-address": _issuer_address}, + params={"sort_order": 1}, + ) + + # assertion + assert resp.status_code == 200 + assert resp.json() == { + "result_set": {"count": 3, "total": 3, "offset": None, "limit": None}, + "holders": [ + { + "account_address": _account_address_3, + "personal_information": { + "key_manager": "key_manager_test1", + "name": "name_test3", + "postal_code": "postal_code_test3", + "address": "address_test3", + "email": "email_test3", + "birth": "birth_test3", + "is_corporate": None, + "tax_category": None, + }, + "balance": 99, + "exchange_balance": 99, + "exchange_commitment": 99, + "pending_transfer": 99, + "locked": 30, + "modified": "2023-10-24T05:00:00", }, - "balance": 0, - "exchange_balance": 0, - "exchange_commitment": 12, - "pending_transfer": 5, - "locked": 0, - "modified": "2023-10-24T01:00:00", - }, - { - "account_address": _account_address_2, - "personal_information": { - "key_manager": None, - "name": None, - "postal_code": None, - "address": None, - "email": None, - "birth": None, - "is_corporate": None, - "tax_category": None, + { + "account_address": _account_address_2, + "personal_information": { + "key_manager": None, + "name": None, + "postal_code": None, + "address": None, + "email": None, + "birth": None, + "is_corporate": None, + "tax_category": None, + }, + "balance": 20, + "exchange_balance": 21, + "exchange_commitment": 22, + "pending_transfer": 10, + "locked": 20, + "modified": "2023-10-24T02:20:00", }, - "balance": 20, - "exchange_balance": 21, - "exchange_commitment": 0, - "pending_transfer": 0, - "locked": 0, - "modified": "2023-10-24T02:00:00", - }, - { - "account_address": _account_address_3, - "personal_information": { - "key_manager": "key_manager_test1", - "name": "name_test3", - "postal_code": "postal_code_test3", - "address": "address_test3", - "email": "email_test3", - "birth": "birth_test3", - "is_corporate": None, - "tax_category": None, + { + "account_address": _account_address_1, + "personal_information": { + "key_manager": "key_manager_test1", + "name": "name_test1", + "postal_code": "postal_code_test1", + "address": "address_test1", + "email": "email_test1", + "birth": "birth_test1", + "is_corporate": False, + "tax_category": 10, + }, + "balance": 10, + "exchange_balance": 11, + "exchange_commitment": 12, + "pending_transfer": 5, + "locked": 10, + "modified": "2023-10-24T01:10:00", }, - "balance": 0, - "exchange_balance": 0, - "exchange_commitment": 0, - "pending_transfer": 0, - "locked": 0, - "modified": "2023-10-24T03:00:00", - }, - ] + ], + } + + # + # Pagination + def test_normal_5_1(self, client, db): + user = config_eth_account("user1") + _issuer_address = user["address"] + _token_address = "0x82b1c9374aB625380bd498a3d9dF4033B8A0E3Bb" + _account_address_1 = "0xb75c7545b9230FEe99b7af370D38eBd3DAD929f7" + _account_address_2 = "0x3F198534Bbe3B2a197d3B317d41392F348EAC707" + _account_address_3 = "0x8277D905F37F8a9717F5718d0daC21495dFE74bf" + + account = Account() + account.issuer_address = _issuer_address + db.add(account) + + token = Token() + token.type = TokenType.IBET_SHARE.value + token.tx_hash = "" + token.issuer_address = _issuer_address + token.token_address = _token_address + token.abi = "" + token.version = TokenVersion.V_22_12 + db.add(token) + + # prepare data: account_address_1 + idx_position_1 = IDXPosition() + idx_position_1.token_address = _token_address + idx_position_1.account_address = _account_address_1 + idx_position_1.balance = 10 + idx_position_1.exchange_balance = 11 + idx_position_1.exchange_commitment = 12 + idx_position_1.pending_transfer = 5 + idx_position_1.modified = datetime(2023, 10, 24, 0, 0, 0) + db.add(idx_position_1) + + idx_locked_position = IDXLockedPosition() + idx_locked_position.token_address = _token_address + idx_locked_position.lock_address = ( + "0x1234567890123456789012345678900000000001" # lock address 1 + ) + idx_locked_position.account_address = _account_address_1 + idx_locked_position.value = 5 + idx_locked_position.modified = datetime(2023, 10, 24, 1, 0, 0) + db.add(idx_locked_position) + + idx_locked_position = IDXLockedPosition() + idx_locked_position.token_address = _token_address + idx_locked_position.lock_address = ( + "0x1234567890123456789012345678900000000002" # lock address 2 + ) + idx_locked_position.account_address = _account_address_1 + idx_locked_position.value = 5 + idx_locked_position.modified = datetime(2023, 10, 24, 1, 10, 0) + db.add(idx_locked_position) + + idx_personal_info_1 = IDXPersonalInfo() + idx_personal_info_1.account_address = _account_address_1 + idx_personal_info_1.issuer_address = _issuer_address + idx_personal_info_1.personal_info = { + "key_manager": "key_manager_test1", + "name": "name_test1", + "postal_code": "postal_code_test1", + "address": "address_test1", + "email": "email_test1", + "birth": "birth_test1", + "is_corporate": False, + "tax_category": 10, + } + db.add(idx_personal_info_1) + + # prepare data: account_address_2 + idx_position_2 = IDXPosition() + idx_position_2.token_address = _token_address + idx_position_2.account_address = _account_address_2 + idx_position_2.balance = 20 + idx_position_2.exchange_balance = 21 + idx_position_2.exchange_commitment = 22 + idx_position_2.pending_transfer = 10 + idx_position_2.modified = datetime(2023, 10, 24, 2, 0, 0) + db.add(idx_position_2) + + idx_locked_position = IDXLockedPosition() + idx_locked_position.token_address = _token_address + idx_locked_position.lock_address = ( + "0x1234567890123456789012345678900000000001" # lock address 1 + ) + idx_locked_position.account_address = _account_address_2 + idx_locked_position.value = 10 + idx_locked_position.modified = datetime(2023, 10, 24, 2, 10, 0) + db.add(idx_locked_position) + + idx_locked_position = IDXLockedPosition() + idx_locked_position.token_address = _token_address + idx_locked_position.lock_address = ( + "0x1234567890123456789012345678900000000002" # lock address 2 + ) + idx_locked_position.account_address = _account_address_2 + idx_locked_position.value = 10 + idx_locked_position.modified = datetime(2023, 10, 24, 2, 20, 0) + db.add(idx_locked_position) + + # prepare data: account_address_3 + idx_position_3 = IDXPosition() + idx_position_3.token_address = _token_address + idx_position_3.account_address = _account_address_3 + idx_position_3.balance = 99 + idx_position_3.exchange_balance = 99 + idx_position_3.exchange_commitment = 99 + idx_position_3.pending_transfer = 99 + idx_position_3.modified = datetime(2023, 10, 24, 3, 0, 0) + db.add(idx_position_3) + + idx_locked_position = IDXLockedPosition() + idx_locked_position.token_address = _token_address + idx_locked_position.lock_address = ( + "0x1234567890123456789012345678900000000001" # lock address 1 + ) + idx_locked_position.account_address = _account_address_3 + idx_locked_position.value = 15 + idx_locked_position.modified = datetime(2023, 10, 24, 4, 0, 0) + db.add(idx_locked_position) + + idx_locked_position = IDXLockedPosition() + idx_locked_position.token_address = _token_address + idx_locked_position.lock_address = ( + "0x1234567890123456789012345678900000000002" # lock address 2 + ) + idx_locked_position.account_address = _account_address_3 + idx_locked_position.value = 15 + idx_locked_position.modified = datetime(2023, 10, 24, 5, 0, 0) + db.add(idx_locked_position) + + # Other locked position + _locked_position = IDXLockedPosition() + _locked_position.token_address = "other_token_address" + _locked_position.lock_address = ( + "0x1234567890123456789012345678900000000002" # lock address 2 + ) + _locked_position.account_address = _account_address_1 + _locked_position.value = 5 + _locked_position.modified = datetime(2023, 10, 25, 0, 2, 0) + db.add(_locked_position) + + idx_personal_info_3 = IDXPersonalInfo() + idx_personal_info_3.account_address = _account_address_3 + idx_personal_info_3.issuer_address = _issuer_address + idx_personal_info_3.personal_info = { + "key_manager": "key_manager_test1", + "name": "name_test3", + "postal_code": "postal_code_test3", + "address": "address_test3", + "email": "email_test3", + "birth": "birth_test3" + # PersonalInfo is partially registered. + } + db.add(idx_personal_info_3) + + # request target API + resp = client.get( + self.base_url.format(_token_address), + headers={"issuer-address": _issuer_address}, + params={"offset": 1, "limit": 2}, + ) + + # assertion + assert resp.status_code == 200 + assert resp.json() == { + "result_set": {"count": 3, "total": 3, "offset": 1, "limit": 2}, + "holders": [ + { + "account_address": _account_address_2, + "personal_information": { + "key_manager": None, + "name": None, + "postal_code": None, + "address": None, + "email": None, + "birth": None, + "is_corporate": None, + "tax_category": None, + }, + "balance": 20, + "exchange_balance": 21, + "exchange_commitment": 22, + "pending_transfer": 10, + "locked": 20, + "modified": "2023-10-24T02:20:00", + }, + { + "account_address": _account_address_3, + "personal_information": { + "key_manager": "key_manager_test1", + "name": "name_test3", + "postal_code": "postal_code_test3", + "address": "address_test3", + "email": "email_test3", + "birth": "birth_test3", + "is_corporate": None, + "tax_category": None, + }, + "balance": 99, + "exchange_balance": 99, + "exchange_commitment": 99, + "pending_transfer": 99, + "locked": 30, + "modified": "2023-10-24T05:00:00", + }, + ], + } + + # + # Pagination (over offset) + def test_normal_5_2(self, client, db): + user = config_eth_account("user1") + _issuer_address = user["address"] + _token_address = "0x82b1c9374aB625380bd498a3d9dF4033B8A0E3Bb" + _account_address_1 = "0xb75c7545b9230FEe99b7af370D38eBd3DAD929f7" + _account_address_2 = "0x3F198534Bbe3B2a197d3B317d41392F348EAC707" + _account_address_3 = "0x8277D905F37F8a9717F5718d0daC21495dFE74bf" + + account = Account() + account.issuer_address = _issuer_address + db.add(account) + + token = Token() + token.type = TokenType.IBET_SHARE.value + token.tx_hash = "" + token.issuer_address = _issuer_address + token.token_address = _token_address + token.abi = "" + token.version = TokenVersion.V_22_12 + db.add(token) + + # prepare data: account_address_1 + idx_position_1 = IDXPosition() + idx_position_1.token_address = _token_address + idx_position_1.account_address = _account_address_1 + idx_position_1.balance = 10 + idx_position_1.exchange_balance = 11 + idx_position_1.exchange_commitment = 12 + idx_position_1.pending_transfer = 5 + idx_position_1.modified = datetime(2023, 10, 24, 0, 0, 0) + db.add(idx_position_1) + + idx_locked_position = IDXLockedPosition() + idx_locked_position.token_address = _token_address + idx_locked_position.lock_address = ( + "0x1234567890123456789012345678900000000001" # lock address 1 + ) + idx_locked_position.account_address = _account_address_1 + idx_locked_position.value = 5 + idx_locked_position.modified = datetime(2023, 10, 24, 1, 0, 0) + db.add(idx_locked_position) + + idx_locked_position = IDXLockedPosition() + idx_locked_position.token_address = _token_address + idx_locked_position.lock_address = ( + "0x1234567890123456789012345678900000000002" # lock address 2 + ) + idx_locked_position.account_address = _account_address_1 + idx_locked_position.value = 5 + idx_locked_position.modified = datetime(2023, 10, 24, 1, 10, 0) + db.add(idx_locked_position) + + idx_personal_info_1 = IDXPersonalInfo() + idx_personal_info_1.account_address = _account_address_1 + idx_personal_info_1.issuer_address = _issuer_address + idx_personal_info_1.personal_info = { + "key_manager": "key_manager_test1", + "name": "name_test1", + "postal_code": "postal_code_test1", + "address": "address_test1", + "email": "email_test1", + "birth": "birth_test1", + "is_corporate": False, + "tax_category": 10, + } + db.add(idx_personal_info_1) + + # prepare data: account_address_2 + idx_position_2 = IDXPosition() + idx_position_2.token_address = _token_address + idx_position_2.account_address = _account_address_2 + idx_position_2.balance = 20 + idx_position_2.exchange_balance = 21 + idx_position_2.exchange_commitment = 22 + idx_position_2.pending_transfer = 10 + idx_position_2.modified = datetime(2023, 10, 24, 2, 0, 0) + db.add(idx_position_2) + + idx_locked_position = IDXLockedPosition() + idx_locked_position.token_address = _token_address + idx_locked_position.lock_address = ( + "0x1234567890123456789012345678900000000001" # lock address 1 + ) + idx_locked_position.account_address = _account_address_2 + idx_locked_position.value = 10 + idx_locked_position.modified = datetime(2023, 10, 24, 2, 10, 0) + db.add(idx_locked_position) + + idx_locked_position = IDXLockedPosition() + idx_locked_position.token_address = _token_address + idx_locked_position.lock_address = ( + "0x1234567890123456789012345678900000000002" # lock address 2 + ) + idx_locked_position.account_address = _account_address_2 + idx_locked_position.value = 10 + idx_locked_position.modified = datetime(2023, 10, 24, 2, 20, 0) + db.add(idx_locked_position) + + # prepare data: account_address_3 + idx_position_3 = IDXPosition() + idx_position_3.token_address = _token_address + idx_position_3.account_address = _account_address_3 + idx_position_3.balance = 99 + idx_position_3.exchange_balance = 99 + idx_position_3.exchange_commitment = 99 + idx_position_3.pending_transfer = 99 + idx_position_3.modified = datetime(2023, 10, 24, 3, 0, 0) + db.add(idx_position_3) + + idx_locked_position = IDXLockedPosition() + idx_locked_position.token_address = _token_address + idx_locked_position.lock_address = ( + "0x1234567890123456789012345678900000000001" # lock address 1 + ) + idx_locked_position.account_address = _account_address_3 + idx_locked_position.value = 15 + idx_locked_position.modified = datetime(2023, 10, 24, 4, 0, 0) + db.add(idx_locked_position) + + idx_locked_position = IDXLockedPosition() + idx_locked_position.token_address = _token_address + idx_locked_position.lock_address = ( + "0x1234567890123456789012345678900000000002" # lock address 2 + ) + idx_locked_position.account_address = _account_address_3 + idx_locked_position.value = 15 + idx_locked_position.modified = datetime(2023, 10, 24, 5, 0, 0) + db.add(idx_locked_position) + + # Other locked position + _locked_position = IDXLockedPosition() + _locked_position.token_address = "other_token_address" + _locked_position.lock_address = ( + "0x1234567890123456789012345678900000000002" # lock address 2 + ) + _locked_position.account_address = _account_address_1 + _locked_position.value = 5 + _locked_position.modified = datetime(2023, 10, 25, 0, 2, 0) + db.add(_locked_position) + + idx_personal_info_3 = IDXPersonalInfo() + idx_personal_info_3.account_address = _account_address_3 + idx_personal_info_3.issuer_address = _issuer_address + idx_personal_info_3.personal_info = { + "key_manager": "key_manager_test1", + "name": "name_test3", + "postal_code": "postal_code_test3", + "address": "address_test3", + "email": "email_test3", + "birth": "birth_test3" + # PersonalInfo is partially registered. + } + db.add(idx_personal_info_3) + + # request target API + resp = client.get( + self.base_url.format(_token_address), + headers={"issuer-address": _issuer_address}, + params={"offset": 4, "limit": 1}, + ) + + # assertion + assert resp.status_code == 200 + assert resp.json() == { + "result_set": {"count": 3, "total": 3, "offset": 4, "limit": 1}, + "holders": [], + } ########################################################################### # Error Case