diff --git a/app/exceptions.py b/app/exceptions.py index 0ae833df..faad132b 100644 --- a/app/exceptions.py +++ b/app/exceptions.py @@ -17,7 +17,10 @@ SPDX-License-Identifier: Apache-2.0 """ +from typing import Literal + from fastapi import status +from pydantic import BaseModel from app.utils.contract_error_code import REVERT_CODE_MAP, error_code_msg @@ -28,6 +31,15 @@ class AppError(Exception): code_list: list[int] | None = None +class RecordErrorDetail(BaseModel): + row_num: int + error_reason: Literal["PersonalInfoExceedsSizeLimit"] + + +class InvalidUploadErrorDetail(BaseModel): + record_error_details: list[RecordErrorDetail] + + ################################################ # 400_BAD_REQUEST ################################################ @@ -88,11 +100,34 @@ class MultipleTokenTransferNotAllowedError(BadRequestError): class OperationNotPermittedForOlderIssuers(BadRequestError): - """An operation not permitted for older issuers""" + """ + An operation not permitted for older issuers + """ code = 10 +class PersonalInfoExceedsSizeLimit(BadRequestError): + """ + Personal info exceeds size limit + """ + + code = 11 + + +class BatchPersonalInfoRegistrationValidationError(BadRequestError): + """ + The uploaded list of batch personal info registration has invalid records + """ + + code = 12 + detail: InvalidUploadErrorDetail + + def __init__(self, detail: InvalidUploadErrorDetail): + self.record_error_details = detail + super().__init__(detail) + + class OperationNotAllowedStateError(BadRequestError): """ Error returned when server-side data is not ready to process the request diff --git a/app/routers/issuer/bond.py b/app/routers/issuer/bond.py index c37e6ac5..e6b3d34d 100644 --- a/app/routers/issuer/bond.py +++ b/app/routers/issuer/bond.py @@ -17,6 +17,7 @@ SPDX-License-Identifier: Apache-2.0 """ +import json import uuid from datetime import UTC, datetime from typing import Annotated, List, Optional, Sequence @@ -49,12 +50,16 @@ from app.database import DBAsyncSession from app.exceptions import ( AuthorizationError, + BatchPersonalInfoRegistrationValidationError, ContractRevertError, InvalidParameterError, + InvalidUploadErrorDetail, MultipleTokenTransferNotAllowedError, NonTransferableTokenError, OperationNotAllowedStateError, OperationNotSupportedVersionError, + PersonalInfoExceedsSizeLimit, + RecordErrorDetail, SendTransactionError, TokenNotExistError, ) @@ -2519,6 +2524,7 @@ async def register_bond_token_holder_extra_info( InvalidParameterError, SendTransactionError, ContractRevertError, + PersonalInfoExceedsSizeLimit, ), ) async def register_bond_token_holder_personal_info( @@ -2589,6 +2595,13 @@ async def register_bond_token_holder_personal_info( await db.merge(_off_personal_info) await db.commit() else: + # Check the length of personal info content + if ( + len(json.dumps(input_personal_info).encode("utf-8")) + > config.PERSONAL_INFO_MESSAGE_SIZE_LIMIT + ): + raise PersonalInfoExceedsSizeLimit + token_contract = await IbetStraightBondContract(token_address).get() try: personal_info_contract = PersonalInfoContract( @@ -2699,7 +2712,12 @@ async def list_all_bond_token_batch_personal_info_registration( operation_id="InitiateBondTokenBatchPersonalInfoRegistration", response_model=BatchRegisterPersonalInfoUploadResponse, responses=get_routers_responses( - 422, 401, 404, AuthorizationError, InvalidParameterError + 422, + 401, + 404, + AuthorizationError, + InvalidParameterError, + BatchPersonalInfoRegistrationValidationError, ), ) async def initiate_bond_token_batch_personal_info_registration( @@ -2757,14 +2775,39 @@ async def initiate_bond_token_batch_personal_info_registration( batch.status = BatchRegisterPersonalInfoUploadStatus.PENDING.value db.add(batch) - for personal_info in personal_info_list: + errs = [] + bulk_register_record_list = [] + + for i, personal_info in enumerate(personal_info_list): bulk_register_record = BatchRegisterPersonalInfo() bulk_register_record.upload_id = batch_id bulk_register_record.token_address = token_address bulk_register_record.account_address = personal_info.account_address - bulk_register_record.personal_info = personal_info.model_dump() bulk_register_record.status = 0 - db.add(bulk_register_record) + bulk_register_record.personal_info = personal_info.model_dump() + + # Check the length of personal info content + if ( + personal_info.data_source == PersonalInfoDataSource.ON_CHAIN + and len(json.dumps(bulk_register_record.personal_info).encode("utf-8")) + > config.PERSONAL_INFO_MESSAGE_SIZE_LIMIT + ): + errs.append( + RecordErrorDetail( + row_num=i, + error_reason="PersonalInfoExceedsSizeLimit", + ) + ) + + if not errs: + bulk_register_record_list.append(bulk_register_record) + + if len(errs) > 0: + raise BatchPersonalInfoRegistrationValidationError( + detail=InvalidUploadErrorDetail(record_error_details=errs) + ) + + db.add_all(bulk_register_record_list) await db.commit() diff --git a/app/routers/issuer/share.py b/app/routers/issuer/share.py index 2b3e00a8..b928f4d7 100644 --- a/app/routers/issuer/share.py +++ b/app/routers/issuer/share.py @@ -17,6 +17,7 @@ SPDX-License-Identifier: Apache-2.0 """ +import json import uuid from datetime import UTC, datetime from decimal import Decimal @@ -50,12 +51,16 @@ from app.database import DBAsyncSession from app.exceptions import ( AuthorizationError, + BatchPersonalInfoRegistrationValidationError, ContractRevertError, InvalidParameterError, + InvalidUploadErrorDetail, MultipleTokenTransferNotAllowedError, NonTransferableTokenError, OperationNotAllowedStateError, OperationNotSupportedVersionError, + PersonalInfoExceedsSizeLimit, + RecordErrorDetail, SendTransactionError, TokenNotExistError, ) @@ -2447,6 +2452,7 @@ async def register_share_token_holder_extra_info( InvalidParameterError, SendTransactionError, ContractRevertError, + PersonalInfoExceedsSizeLimit, ), ) async def register_share_token_holder_personal_info( @@ -2517,6 +2523,13 @@ async def register_share_token_holder_personal_info( await db.merge(_off_personal_info) await db.commit() else: + # Check the length of personal info content + if ( + len(json.dumps(input_personal_info).encode("utf-8")) + > config.PERSONAL_INFO_MESSAGE_SIZE_LIMIT + ): + raise PersonalInfoExceedsSizeLimit + token_contract = await IbetShareContract(token_address).get() try: personal_info_contract = PersonalInfoContract( @@ -2627,7 +2640,12 @@ async def list_all_share_token_batch_personal_info_registration( operation_id="InitiateShareTokenBatchPersonalInfoRegistration", response_model=BatchRegisterPersonalInfoUploadResponse, responses=get_routers_responses( - 422, 401, 404, AuthorizationError, InvalidParameterError + 422, + 401, + 404, + AuthorizationError, + InvalidParameterError, + BatchPersonalInfoRegistrationValidationError, ), ) async def initiate_share_token_batch_personal_info_registration( @@ -2685,14 +2703,38 @@ async def initiate_share_token_batch_personal_info_registration( batch.status = BatchRegisterPersonalInfoUploadStatus.PENDING.value db.add(batch) - for personal_info in personal_info_list: + errs = [] + bulk_register_record_list = [] + + for i, personal_info in enumerate(personal_info_list): bulk_register_record = BatchRegisterPersonalInfo() bulk_register_record.upload_id = batch_id bulk_register_record.token_address = token_address bulk_register_record.account_address = personal_info.account_address - bulk_register_record.personal_info = personal_info.model_dump() bulk_register_record.status = 0 - db.add(bulk_register_record) + bulk_register_record.personal_info = personal_info.model_dump() + + # Check the length of personal info content + if ( + len(json.dumps(bulk_register_record.personal_info).encode("utf-8")) + > config.PERSONAL_INFO_MESSAGE_SIZE_LIMIT + ): + errs.append( + RecordErrorDetail( + row_num=i, + error_reason="PersonalInfoExceedsSizeLimit", + ) + ) + + if not errs: + bulk_register_record_list.append(bulk_register_record) + + if len(errs) > 0: + raise BatchPersonalInfoRegistrationValidationError( + detail=InvalidUploadErrorDetail(record_error_details=errs) + ) + + db.add_all(bulk_register_record_list) await db.commit() diff --git a/batch/processor_batch_register_personal_info.py b/batch/processor_batch_register_personal_info.py index 5a050c01..e8ed8b34 100644 --- a/batch/processor_batch_register_personal_info.py +++ b/batch/processor_batch_register_personal_info.py @@ -181,6 +181,12 @@ async def process(self): await self.__sink_on_finish_register_process( db_session=db_session, record_id=batch_data.id, status=2 ) + except ValueError: + # for ValueError: Plaintext is too long + LOG.warning(f"Failed to send transaction: id=<{batch_data.id}>") + await self.__sink_on_finish_register_process( + db_session=db_session, record_id=batch_data.id, status=2 + ) await db_session.commit() error_registration_list = await self.__get_registration_data( diff --git a/config.py b/config.py index fe2616af..de2e54dd 100644 --- a/config.py +++ b/config.py @@ -119,6 +119,12 @@ # Average block generation interval EXPECTED_BLOCKS_PER_SEC = float(os.environ.get("EXPECTED_BLOCKS_PER_SEC", 0.1)) +# Maximum message size for name registration for ibet PersonalInfo contract: +# ( key bit length / 8 ) - ( 2 * hash function output length + 2 ) = 1238 +# key bit length: 10240 +# hash function output length: 20 +PERSONAL_INFO_MESSAGE_SIZE_LIMIT = int((10240 / 8) - (2 * 20 + 2)) + #################################################### # Web3 settings diff --git a/docs/ibet_prime.yaml b/docs/ibet_prime.yaml index eee03e61..e177f896 100644 --- a/docs/ibet_prime.yaml +++ b/docs/ibet_prime.yaml @@ -2534,6 +2534,7 @@ paths: anyOf: - $ref: '#/components/schemas/ContractRevertErrorResponse' - $ref: '#/components/schemas/InvalidParameterErrorResponse' + - $ref: '#/components/schemas/PersonalInfoExceedsSizeLimitResponse' - $ref: '#/components/schemas/SendTransactionErrorResponse' title: Response 400 Registerbondtokenholderpersonalinfo /bond/tokens/{token_address}/personal_info/batch: @@ -2701,7 +2702,10 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/InvalidParameterErrorResponse' + anyOf: + - $ref: '#/components/schemas/BatchPersonalInfoRegistrationValidationErrorResponse' + - $ref: '#/components/schemas/InvalidParameterErrorResponse' + title: Response 400 Initiatebondtokenbatchpersonalinforegistration /bond/tokens/{token_address}/personal_info/batch/{batch_id}: get: tags: @@ -6984,6 +6988,7 @@ paths: anyOf: - $ref: '#/components/schemas/ContractRevertErrorResponse' - $ref: '#/components/schemas/InvalidParameterErrorResponse' + - $ref: '#/components/schemas/PersonalInfoExceedsSizeLimitResponse' - $ref: '#/components/schemas/SendTransactionErrorResponse' title: Response 400 Registersharetokenholderpersonalinfo /share/tokens/{token_address}/personal_info/batch: @@ -7151,7 +7156,10 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/InvalidParameterErrorResponse' + anyOf: + - $ref: '#/components/schemas/BatchPersonalInfoRegistrationValidationErrorResponse' + - $ref: '#/components/schemas/InvalidParameterErrorResponse' + title: Response 400 Initiatesharetokenbatchpersonalinforegistration /share/tokens/{token_address}/personal_info/batch/{batch_id}: get: tags: @@ -10193,6 +10201,41 @@ components: - batch_id title: BatchIssueRedeemUploadIdResponse description: Batch issue/redeem upload id (RESPONSE) + BatchPersonalInfoRegistrationValidationErrorCode: + type: integer + enum: + - 12 + title: BatchPersonalInfoRegistrationValidationErrorCode + BatchPersonalInfoRegistrationValidationErrorMetainfo: + properties: + code: + $ref: '#/components/schemas/BatchPersonalInfoRegistrationValidationErrorCode' + examples: + - 12 + title: + type: string + title: Title + examples: + - BatchPersonalInfoRegistrationValidationError + type: object + required: + - code + - title + title: BatchPersonalInfoRegistrationValidationErrorMetainfo + BatchPersonalInfoRegistrationValidationErrorResponse: + properties: + meta: + $ref: '#/components/schemas/BatchPersonalInfoRegistrationValidationErrorMetainfo' + detail: + type: string + title: Detail + type: object + required: + - meta + - detail + title: BatchPersonalInfoRegistrationValidationErrorResponse + description: The uploaded list of batch personal info registration has invalid + records BatchRegisterPersonalInfoErrorMetaInfo: properties: upload_id: @@ -14067,6 +14110,40 @@ components: - register - modify title: PersonalInfoEventType + PersonalInfoExceedsSizeLimitCode: + type: integer + enum: + - 11 + title: PersonalInfoExceedsSizeLimitCode + PersonalInfoExceedsSizeLimitMetainfo: + properties: + code: + $ref: '#/components/schemas/PersonalInfoExceedsSizeLimitCode' + examples: + - 11 + title: + type: string + title: Title + examples: + - PersonalInfoExceedsSizeLimit + type: object + required: + - code + - title + title: PersonalInfoExceedsSizeLimitMetainfo + PersonalInfoExceedsSizeLimitResponse: + properties: + meta: + $ref: '#/components/schemas/PersonalInfoExceedsSizeLimitMetainfo' + detail: + type: string + title: Detail + type: object + required: + - meta + - detail + title: PersonalInfoExceedsSizeLimitResponse + description: Personal info exceeds size limit PersonalInfoHistory: properties: id: diff --git a/tests/app/test_bond_personal_info_InitiateBondTokenBatchPersonalInfoRegistration.py b/tests/app/test_bond_personal_info_InitiateBondTokenBatchPersonalInfoRegistration.py index 3471f3dc..4edacc26 100644 --- a/tests/app/test_bond_personal_info_InitiateBondTokenBatchPersonalInfoRegistration.py +++ b/tests/app/test_bond_personal_info_InitiateBondTokenBatchPersonalInfoRegistration.py @@ -819,3 +819,85 @@ def test_error_4_2(self, client, db): "meta": {"code": 1, "title": "InvalidParameterError"}, "detail": "personal information list must not be empty", } + + # + # BatchPersonalInfoRegistrationValidationError + def test_error_5(self, client, db): + _issuer_account = config_eth_account("user1") + _issuer_address = _issuer_account["address"] + _issuer_keyfile = _issuer_account["keyfile_json"] + + _test_account_1 = config_eth_account("user2") + _test_account_address_1 = _test_account_1["address"] + + _test_account_2 = config_eth_account("user3") + _test_account_address_2 = _test_account_2["address"] + + _token_address = "0xd9F55747DE740297ff1eEe537aBE0f8d73B7D783" + + # prepare data + account = Account() + account.issuer_address = _issuer_address + account.keyfile = _issuer_keyfile + account.eoa_password = E2EEUtils.encrypt("password") + 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_24_09 + db.add(token) + + db.commit() + + personal_info_1 = { + "account_address": _test_account_address_1, + "key_manager": "test_key_manager", + "name": "test_name", + "postal_code": "test_postal_code", + "address": "test_address" * 100, + "email": "test_email", + "birth": "test_birth", + "is_corporate": False, + "tax_category": 10, + } + + personal_info_2 = { + "account_address": _test_account_address_2, + "key_manager": "test_key_manager", + "name": "test_name", + "postal_code": "test_postal_code", + "address": "test_address" * 100, # Too long value + "email": "test_email", + "birth": "test_birth", + "is_corporate": False, + "tax_category": 10, + } + # request target API + req_param = [personal_info_1, personal_info_2] + resp = client.post( + self.test_url.format(_token_address), + json=req_param, + headers={ + "issuer-address": _issuer_address, + "eoa-password": E2EEUtils.encrypt("password"), + }, + ) + + # assertion + assert resp.status_code == 400 + assert resp.json() == { + "meta": { + "code": 12, + "title": "BatchPersonalInfoRegistrationValidationError", + }, + "detail": { + "record_error_details": [ + {"error_reason": "PersonalInfoExceedsSizeLimit", "row_num": 0}, + {"error_reason": "PersonalInfoExceedsSizeLimit", "row_num": 1}, + ] + }, + } diff --git a/tests/app/test_bond_personal_info_RegisterBondTokenHolderPersonalInfo.py b/tests/app/test_bond_personal_info_RegisterBondTokenHolderPersonalInfo.py index e307971b..4f491315 100644 --- a/tests/app/test_bond_personal_info_RegisterBondTokenHolderPersonalInfo.py +++ b/tests/app/test_bond_personal_info_RegisterBondTokenHolderPersonalInfo.py @@ -1071,3 +1071,83 @@ def test_error_5(self, client, db): "meta": {"code": 2, "title": "SendTransactionError"}, "detail": "failed to register personal information", } + + # + # PersonalInfoExceedsSizeLimit + def test_error_6(self, client, db): + _issuer_account = config_eth_account("user1") + _issuer_address = _issuer_account["address"] + _issuer_keyfile = _issuer_account["keyfile_json"] + + _test_account = config_eth_account("user2") + _test_account_address = _test_account["address"] + + _token_address = "0xd9F55747DE740297ff1eEe537aBE0f8d73B7D783" + + # prepare data + account = Account() + account.issuer_address = _issuer_address + account.keyfile = _issuer_keyfile + account.eoa_password = E2EEUtils.encrypt("password") + db.add(account) + + token = Token() + token.type = TokenType.IBET_STRAIGHT_BOND + token.tx_hash = "" + token.issuer_address = _issuer_address + token.token_address = _token_address + token.abi = {} + token.version = TokenVersion.V_24_09 + db.add(token) + + db.commit() + + # mock + ibet_bond_contract = IbetStraightBondContract() + ibet_bond_contract.personal_info_contract_address = ( + "personal_info_contract_address" + ) + IbetStraightBondContract_get = patch( + target="app.model.blockchain.token.IbetStraightBondContract.get", + return_value=ibet_bond_contract, + ) + PersonalInfoContract_init = patch( + target="app.model.blockchain.personal_info.PersonalInfoContract.__init__", + return_value=None, + ) + PersonalInfoContract_register_info = patch( + target="app.model.blockchain.personal_info.PersonalInfoContract.register_info", + side_effect=SendTransactionError(), + ) + + with ( + IbetStraightBondContract_get, + PersonalInfoContract_init, + PersonalInfoContract_register_info, + ): + # request target API + req_param = { + "account_address": _test_account_address, + "key_manager": "test_key_manager", + "name": "test_name", + "postal_code": "test_postal_code", + "address": "test_address" * 100, # Too long value + "email": "test_email", + "birth": "test_birth", + "is_corporate": False, + "tax_category": 10, + } + resp = client.post( + self.test_url.format(_token_address, _test_account_address), + json=req_param, + headers={ + "issuer-address": _issuer_address, + "eoa-password": E2EEUtils.encrypt("password"), + }, + ) + + # assertion + assert resp.status_code == 400 + assert resp.json() == { + "meta": {"code": 11, "title": "PersonalInfoExceedsSizeLimit"} + } diff --git a/tests/app/test_share_personal_info_InitiateShareTokenBatchPersonalInfoRegistration.py b/tests/app/test_share_personal_info_InitiateShareTokenBatchPersonalInfoRegistration.py index 26f4f5bd..d18c18d2 100644 --- a/tests/app/test_share_personal_info_InitiateShareTokenBatchPersonalInfoRegistration.py +++ b/tests/app/test_share_personal_info_InitiateShareTokenBatchPersonalInfoRegistration.py @@ -752,3 +752,85 @@ def test_error_4_2(self, client, db): "meta": {"code": 1, "title": "InvalidParameterError"}, "detail": "personal information list must not be empty", } + + # + # BatchPersonalInfoRegistrationValidationError + def test_error_5(self, client, db): + _issuer_account = config_eth_account("user1") + _issuer_address = _issuer_account["address"] + _issuer_keyfile = _issuer_account["keyfile_json"] + + _test_account_1 = config_eth_account("user2") + _test_account_address_1 = _test_account_1["address"] + + _test_account_2 = config_eth_account("user3") + _test_account_address_2 = _test_account_2["address"] + + _token_address = "0xd9F55747DE740297ff1eEe537aBE0f8d73B7D783" + + # prepare data + account = Account() + account.issuer_address = _issuer_address + account.keyfile = _issuer_keyfile + account.eoa_password = E2EEUtils.encrypt("password") + 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_24_09 + db.add(token) + + db.commit() + + personal_info_1 = { + "account_address": _test_account_address_1, + "key_manager": "test_key_manager", + "name": "test_name", + "postal_code": "test_postal_code", + "address": "test_address" * 100, + "email": "test_email", + "birth": "test_birth", + "is_corporate": False, + "tax_category": 10, + } + + personal_info_2 = { + "account_address": _test_account_address_2, + "key_manager": "test_key_manager", + "name": "test_name", + "postal_code": "test_postal_code", + "address": "test_address" * 100, # Too long value + "email": "test_email", + "birth": "test_birth", + "is_corporate": False, + "tax_category": 10, + } + # request target API + req_param = [personal_info_1, personal_info_2] + resp = client.post( + self.test_url.format(_token_address), + json=req_param, + headers={ + "issuer-address": _issuer_address, + "eoa-password": E2EEUtils.encrypt("password"), + }, + ) + + # assertion + assert resp.status_code == 400 + assert resp.json() == { + "meta": { + "code": 12, + "title": "BatchPersonalInfoRegistrationValidationError", + }, + "detail": { + "record_error_details": [ + {"error_reason": "PersonalInfoExceedsSizeLimit", "row_num": 0}, + {"error_reason": "PersonalInfoExceedsSizeLimit", "row_num": 1}, + ] + }, + } diff --git a/tests/app/test_share_personal_info_RegisterShareTokenHolderPersonalInfo.py b/tests/app/test_share_personal_info_RegisterShareTokenHolderPersonalInfo.py index e1e57023..a6092b0d 100644 --- a/tests/app/test_share_personal_info_RegisterShareTokenHolderPersonalInfo.py +++ b/tests/app/test_share_personal_info_RegisterShareTokenHolderPersonalInfo.py @@ -1071,3 +1071,83 @@ def test_error_5(self, client, db): "meta": {"code": 2, "title": "SendTransactionError"}, "detail": "failed to register personal information", } + + # + # PersonalInfoExceedsSizeLimit + def test_error_6(self, client, db): + _issuer_account = config_eth_account("user1") + _issuer_address = _issuer_account["address"] + _issuer_keyfile = _issuer_account["keyfile_json"] + + _test_account = config_eth_account("user2") + _test_account_address = _test_account["address"] + + _token_address = "0xd9F55747DE740297ff1eEe537aBE0f8d73B7D783" + + # prepare data + account = Account() + account.issuer_address = _issuer_address + account.keyfile = _issuer_keyfile + account.eoa_password = E2EEUtils.encrypt("password") + db.add(account) + + token = Token() + token.type = TokenType.IBET_SHARE + token.tx_hash = "" + token.issuer_address = _issuer_address + token.token_address = _token_address + token.abi = {} + token.version = TokenVersion.V_24_09 + db.add(token) + + db.commit() + + # mock + ibet_share_contract = IbetShareContract() + ibet_share_contract.personal_info_contract_address = ( + "personal_info_contract_address" + ) + IbetShareContract_get = patch( + target="app.model.blockchain.token.IbetShareContract.get", + return_value=ibet_share_contract, + ) + PersonalInfoContract_init = patch( + target="app.model.blockchain.personal_info.PersonalInfoContract.__init__", + return_value=None, + ) + PersonalInfoContract_register_info = patch( + target="app.model.blockchain.personal_info.PersonalInfoContract.register_info", + side_effect=SendTransactionError(), + ) + + with ( + IbetShareContract_get, + PersonalInfoContract_init, + PersonalInfoContract_register_info, + ): + # request target API + req_param = { + "account_address": _test_account_address, + "key_manager": "test_key_manager", + "name": "test_name", + "postal_code": "test_postal_code", + "address": "test_address" * 100, # Too long value + "email": "test_email", + "birth": "test_birth", + "is_corporate": False, + "tax_category": 10, + } + resp = client.post( + self.test_url.format(_token_address, _test_account_address), + json=req_param, + headers={ + "issuer-address": _issuer_address, + "eoa-password": E2EEUtils.encrypt("password"), + }, + ) + + # assertion + assert resp.status_code == 400 + assert resp.json() == { + "meta": {"code": 11, "title": "PersonalInfoExceedsSizeLimit"} + } diff --git a/tests/batch/test_processor_register_personal_info.py b/tests/batch/test_processor_register_personal_info.py index a11d414f..3588a1c5 100644 --- a/tests/batch/test_processor_register_personal_info.py +++ b/tests/batch/test_processor_register_personal_info.py @@ -965,3 +965,141 @@ async def test_error_3( batch_register.id for batch_register in batch_register_list ], } + + # + # Personal info exceeds size limit + @pytest.mark.asyncio + async def test_error_4( + self, + processor: Processor, + db: Session, + personal_info_contract, + caplog: pytest.LogCaptureFixture, + ): + _account = self.account_list[0] + issuer_private_key = decode_keyfile_json( + raw_keyfile_json=_account["keyfile"], password="password".encode("utf-8") + ) + + # Prepare data : Account + account = Account() + account.issuer_address = _account["address"] + account.eoa_password = E2EEUtils.encrypt("password_ng") + account.keyfile = _account["keyfile"] + account.rsa_passphrase = E2EEUtils.encrypt("password") + account.rsa_public_key = """-----BEGIN PUBLIC KEY----- +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvOlb0wLo/vw/FwFys8IX +/DE4pMNLGA/mJoZ6jz0DdQG4JqGnIitzdaPreZK9H75Cfs5yfNJmP7kJixqSNjyz +WFyZ+0Jf+hwaZ6CxIyTp4zm7A0OLdqzsFIdXXHWFF10g3iwd2KkvKeuocD5c/TT8 +tuI2MzhLPwCUn/umBlVPswsRucAC67U5gig5KdeKkR6JdfwVO7OpeMX3gJT6A/Ns +YE/ce4vvF/aH7mNmirnCpkfeqEk5ANpw6bdpEGwYXAdxdD3DhIabMxUvrRZp5LEh +E7pl6K6sCvVKAPl5HPtZ5/AL/Kj7iLU88qY+TYE9bSTtWqhGabSlON7bo132EkZn +Y8BnCy4c8ni00hRkxQ8ZdH468DjzaXOtiNlBLGV7BXiMf7zIE3YJD7Xd22g/XKMs +FsL7F45sgez6SBxiVQrk5WuFtLLD08+3ZAYKqaxFmesw1Niqg6mBB1E+ipYOeBlD +xtUxBqY1kHdua48rjTwEBJz6M+X6YUzIaDS/j/GcFehSyBthj099whK629i1OY3A +PhrKAc+Fywn8tNTkjicPHdzDYzUL2STwAzhrbSfIIc2HlZJSks0Fqs+tQ4Iugep6 +fdDfFGjZcF3BwH6NA0frWvUW5AhhkJyJZ8IJ4C0UqakBrRLn2rvThvdCPNPWJ/tx +EK7Y4zFFnfKP3WIA3atUbbcCAwEAAQ== +-----END PUBLIC KEY-----""" + db.add(account) + + # Prepare data : Token + token_contract_1 = await self.deploy_share_token_contract( + _account["address"], issuer_private_key, personal_info_contract.address + ) + token_address_1 = token_contract_1.address + token_1 = Token() + token_1.type = TokenType.IBET_SHARE.value + token_1.token_address = token_address_1 + token_1.issuer_address = _account["address"] + token_1.abi = token_contract_1.abi + token_1.tx_hash = "tx_hash" + token_1.version = TokenVersion.V_24_09 + db.add(token_1) + + # Prepare data : BatchRegisterPersonalInfoUpload + batch_register_upload = BatchRegisterPersonalInfoUpload() + batch_register_upload.issuer_address = _account["address"] + batch_register_upload.upload_id = self.upload_id_list[0] + batch_register_upload.status = ( + BatchRegisterPersonalInfoUploadStatus.PENDING.value + ) + db.add(batch_register_upload) + + # Prepare data : BatchRegisterPersonalInfo + batch_register_list = [] + for i in range(0, 3): + batch_register = BatchRegisterPersonalInfo() + batch_register.upload_id = self.upload_id_list[0] + batch_register.token_address = token_address_1 + batch_register.account_address = self.account_list[i]["address"] + batch_register.status = 0 + batch_register.personal_info = { + "key_manager": "test_value", + "name": "test_value", + "postal_code": "1000001", + "address": "test_value" * 100, # Too long value + "email": "test_value@a.test", + "birth": "19900101", + "is_corporate": True, + "tax_category": 3, + } + db.add(batch_register) + batch_register_list.append(batch_register) + + db.commit() + + # Execute batch + await processor.process() + + # Assertion + _batch_register_upload: Optional[BatchRegisterPersonalInfoUpload] = db.scalars( + select(BatchRegisterPersonalInfoUpload) + .where( + BatchRegisterPersonalInfoUpload.issuer_address == _account["address"] + ) + .limit(1) + ).first() + assert ( + _batch_register_upload.status + == BatchRegisterPersonalInfoUploadStatus.FAILED.value + ) + + assert 1 == caplog.record_tuples.count( + ( + LOG.name, + logging.WARN, + f"Failed to send transaction: id=<{batch_register_list[0].id}>", + ) + ) + assert 1 == caplog.record_tuples.count( + ( + LOG.name, + logging.WARN, + f"Failed to send transaction: id=<{batch_register_list[1].id}>", + ) + ) + assert 1 == caplog.record_tuples.count( + ( + LOG.name, + logging.WARN, + f"Failed to send transaction: id=<{batch_register_list[2].id}>", + ) + ) + + _notification_list = db.scalars(select(Notification)).all() + for _notification in _notification_list: + assert _notification.notice_id is not None + assert _notification.issuer_address == _account["address"] + assert _notification.priority == 1 + assert ( + _notification.type + == NotificationType.BATCH_REGISTER_PERSONAL_INFO_ERROR + ) + assert _notification.code == 2 + assert _notification.metainfo == { + "upload_id": batch_register_upload.upload_id, + "error_registration_id": [ + batch_register.id for batch_register in batch_register_list + ], + }