Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • hive/wax
1 result
Show changes
Showing
with 1302 additions and 0 deletions
from __future__ import annotations
from typing import TYPE_CHECKING
import pytest
if TYPE_CHECKING:
from wax.interfaces import IWaxBaseInterface
@pytest.mark.descrbe("Should be able to estimate hive collateral")
def test_estimate_hive_collateral(wax: IWaxBaseInterface) -> None:
estimated_hive_collateral = wax.estimate_hive_collateral(
current_median_history_base=wax.hbd.satoshis(201),
current_median_history_quote=wax.hive.satoshis(1000),
current_min_history_base=wax.hbd.satoshis(197),
current_min_history_quote=wax.hive.satoshis(1000),
hbd_amount_to_get=wax.hbd.satoshis(100000),
)
assert estimated_hive_collateral == wax.hive.satoshis(1065988)
from __future__ import annotations
from typing import TYPE_CHECKING, Final
import pytest
from wax._private.models.asset import Asset
from wax.models.asset import AssetName
from wax.proto.asset import asset
if TYPE_CHECKING:
from wax.interfaces import IWaxBaseInterface
MAX_SAFE_INTEGER: Final[int] = 2**53
@pytest.mark.describe("Should be able to get hive asset with double-precision floating-point format")
def test_generate_asset_0(wax: IWaxBaseInterface) -> None:
assert wax.hive.coins(100.3).amount == "100300"
@pytest.mark.describe(
"Should be able to get hive asset with Double-precision floating-point"
"format with more decimal places than the precision"
)
def test_generate_asset_1(wax: IWaxBaseInterface) -> None:
assert wax.hive.coins(100.34567).amount == "100345"
@pytest.mark.describe("Should be able to get hbd asset with double-precision floating-point format")
def test_generate_asset_2(wax: IWaxBaseInterface) -> None:
assert wax.hbd.coins(100.34567).amount == "100345"
@pytest.mark.describe(
"Should be able to get hbd asset with Double-precision floating-point"
"format with less decimal places than the precision"
)
def test_generate_asset_3(wax: IWaxBaseInterface) -> None:
assert wax.hbd.coins(100.3).amount == "100300"
@pytest.mark.describe("Should be able to get vests asset with double-precision floating-point format")
def test_generate_asset_4(wax: IWaxBaseInterface) -> None:
assert wax.vests.coins(100).amount == "100000000"
@pytest.mark.describe(
"Should be able to get Hive asset with double-precision"
"floating-point format with more decimal places than the precision"
)
def test_generate_asset_5(wax: IWaxBaseInterface) -> None:
assert wax.hive.coins(100.345678910).amount == "100345"
@pytest.mark.describe(
"Should be able to get vests asset with double-precision floating-point"
"format with less decimal places than the precision"
)
def test_generate_asset_6(wax: IWaxBaseInterface) -> None:
assert wax.vests.coins(100.3).amount == "100300000"
@pytest.mark.describe(
"Should be able to get vests asset with double-precision floating-point"
"format near max safe integer (with fractional part)"
)
def test_generate_asset_7(wax: IWaxBaseInterface) -> None:
assert wax.vests.coins(9007199254740.543).amount == "9007199254740543000"
@pytest.mark.describe("Should be able to get hive asset with large number value")
def test_generate_asset_8(wax: IWaxBaseInterface) -> None:
assert wax.hive.satoshis(MAX_SAFE_INTEGER).amount == "9007199254740992"
@pytest.mark.describe("Should be able to get hbd asset with large number value")
def test_generate_asset_9(wax: IWaxBaseInterface) -> None:
assert wax.hbd.satoshis(MAX_SAFE_INTEGER).amount == "9007199254740992"
@pytest.mark.describe("Should be able to get vests asset with large number value")
def test_generate_asset_10(wax: IWaxBaseInterface) -> None:
assert wax.vests.satoshis(MAX_SAFE_INTEGER).amount == "9007199254740992"
@pytest.mark.describe("Should be able to generate HBD, HIVE and VESTS assets - coins and satoshis")
@pytest.mark.parametrize("asset_type", ["hbd", "hive", "vests"])
@pytest.mark.parametrize("monetary_units", ["coins", "satoshis"])
@pytest.mark.parametrize("amount", [-1_234_567_890, -1_000_000, -100_000, -1, 0, 1, 100_000, 1_000_000, 1_234_567_890])
def test_generate_asset_11(wax: IWaxBaseInterface, asset_type: str, monetary_units: str, amount: int) -> None:
result = getattr(getattr(wax, asset_type), monetary_units)(amount)
asset_info = Asset().get_asset_info(getattr(AssetName, asset_type.capitalize()))
if amount == 0:
assert result.amount == str(0)
else:
assert result.amount == f"{amount}{'0' * asset_info.precision}" if monetary_units == "coins" else f"{amount}"
assert result.precision == asset_info.precision
assert result.nai == asset_info.nai
@pytest.mark.skip(reason="getAsset() not implemented")
@pytest.mark.describe("Should be able to convert API asset to the proper negative HIVE asset data")
def test_generate_asset_12(wax: IWaxBaseInterface) -> None: ...
@pytest.mark.describe("Should be able to convert API asset to the proper custom asset data")
def test_generate_asset_13() -> None:
custom_asset = asset(amount="300", precision=1, nai="@@002137000")
assert custom_asset.amount == "300"
assert custom_asset.precision == 1
assert custom_asset.nai == "@@002137000"
from __future__ import annotations
from typing import TYPE_CHECKING
import pytest
if TYPE_CHECKING:
from wax.interfaces import IWaxBaseInterface
@pytest.mark.describe("Should be able to generate random private key using password")
def test_get_private_key_from_password(wax: IWaxBaseInterface) -> None:
private_key = wax.get_private_key_from_password(
account="gtg",
role="active",
password="verysecurepassword", # noqa: S106
)
assert private_key.associated_public_key == "STM6JswFatSixhR9AMUP38rtpMVAagTvxGYu7d8i2JUK1QZDkPbH3"
assert private_key.wif_private_key == "5J89tdX8b1wQJHcqDMDVn1UwvtiYFK53PQEgG5gL5oCEk83Us12"
from __future__ import annotations
from typing import TYPE_CHECKING
import pytest
if TYPE_CHECKING:
from wax.interfaces import IWaxBaseInterface
@pytest.mark.parametrize(
"valid_account_name",
[
"aaa",
"a0a",
"a-a",
"aa0",
"a00",
"a-0",
"aaa-bbb-ccc",
"aaa-bbb.ccc",
"aaa.bbb-ccc",
"aaa.bbb.ccc",
"aaa--bbb--ccc",
"xn--san-p8a.dex",
"xn-san-p8a.dex",
"this-label-has",
],
)
@pytest.mark.describe("Should be able to validate valid account names")
def test_is_valid_account_name_0(wax: IWaxBaseInterface, valid_account_name: str) -> None:
assert wax.is_valid_account_name(account_name=valid_account_name)
@pytest.mark.parametrize(
"invalid_account_name",
[
"a",
"A",
"0",
".",
"-",
"aa",
"aA",
"a0",
"a.",
"a-",
"aAa",
"a.a",
"aA0",
"a.0",
"aaa,bbb-ccc",
"aaa_bbb-ccc",
"aaa-BBB-ccc",
"1aaa-bbb",
"-aaa-bbb-ccc",
".aaa-bbb-ccc",
"/aaa-bbb-ccc",
"aaa-bbb-ccc-",
"aaa-bbb-ccc.",
"aaa-bbb-ccc..",
"aaa-bbb-ccc/",
"aaa..bbb-ccc",
"xn--san-p8a.de",
"xn-san-p8a.de",
"this-label-has-more-than-63-char.act.ers-64-to-be-really-precise",
"none.of.these.labels.has.more.than-63.chars--but.still.not.valid",
],
)
@pytest.mark.describe("Should be able to validate invalid account names")
def test_is_valid_account_name_1(wax: IWaxBaseInterface, invalid_account_name: str) -> None:
assert not wax.is_valid_account_name(account_name=invalid_account_name)
from __future__ import annotations
import json
from typing import TYPE_CHECKING, Any, Final
import pytest
from wax.proto.authority import authority
from wax.proto.operations import operation, recover_account, vote
if TYPE_CHECKING:
from wax.interfaces import IWaxBaseInterface
from wax.models.operations import Operation
AUTHORITY_1: Final[authority] = authority(
weight_threshold=1,
account_auths={"account": 1, "account1": 2},
key_auths={"STM76EQNV2RTA6yF9TnBvGSV71mW7eW36MM7XQp24JxdoArTfKA76": 1},
)
AUTHORITY_2: Final[authority] = authority(
weight_threshold=1,
account_auths={"account1": 1, "account2": 2},
key_auths={"STM76EQNV2RTA6yF9TnBvGSV71mW7eW36MM7XQp24JxdoArTfKA76": 1},
)
RECOVER_ACCOUNT: Final[recover_account] = recover_account(
account_to_recover="account", new_owner_authority=AUTHORITY_1, recent_owner_authority=AUTHORITY_2, extensions=[]
)
PROTO_OPERATION: Final[operation] = operation(recover_account=RECOVER_ACCOUNT)
API_OPERATION_DICT: Final[dict[str, Any]] = {
"type": "claim_reward_balance_operation",
"value": {
"account": "account",
"reward_hive": {"amount": "0", "precision": 3, "nai": "@@000000021"},
"reward_hbd": {"amount": "0", "precision": 3, "nai": "@@000000013"},
"reward_vests": {"amount": "1", "precision": 6, "nai": "@@000000037"},
},
}
API_OPERATION_JSON: Final[str] = json.dumps(API_OPERATION_DICT)
EXPECTED_IMPACTED_ACCOUNT: Final[str] = "account"
EXPECTED_AMOUNT_OF_IMPACTED_ACCOUNTS: Final[int] = 1
@pytest.mark.parametrize("operation", [PROTO_OPERATION, API_OPERATION_DICT, API_OPERATION_JSON])
def test_operation_get_impacted_accounts(wax: IWaxBaseInterface, operation: Operation) -> None:
# ACT
result = wax.get_operation_impacted_accounts(operation)
# ASSERT
assert len(result) == EXPECTED_AMOUNT_OF_IMPACTED_ACCOUNTS
assert result[0] == EXPECTED_IMPACTED_ACCOUNT
@pytest.mark.description("Should be able to get impacted accounts from example api operation")
def test_get_operation_impacted_accounts_0(wax: IWaxBaseInterface) -> None:
result = wax.get_operation_impacted_accounts(
operation={
"type": "vote_operation",
"value": {"voter": "otom", "author": "c0ff33a", "permlink": "ewxhnjbj", "weight": 2200},
}
)
assert result == ["c0ff33a", "otom"]
@pytest.mark.description("Should be able to get impacted accounts from example proto operation")
def test_get_operation_impacted_accounts_1(wax: IWaxBaseInterface) -> None:
result = wax.get_operation_impacted_accounts(
operation=operation(
vote=vote(
author="c0ff33a",
permlink="ewxhnjbj",
voter="otom",
weight=2200,
)
)
)
assert result == ["c0ff33a", "otom"]
from __future__ import annotations
import json
from typing import TYPE_CHECKING, Any, Final
import pytest
from wax._private.proto.recurrent_transfer_pb2 import recurrent_transfer_extension, recurrent_transfer_pair_id
from wax.proto.operations import recurrent_transfer
if TYPE_CHECKING:
from wax import ITransaction, IWaxBaseInterface
@pytest.mark.parametrize("asset_type", ["hbd", "hive"])
@pytest.mark.describe("Should initialize push_operation with recurrent_transfer_operation via base interface")
def test_basic_recurrent_transfer_operation(wax: IWaxBaseInterface, transaction: ITransaction, asset_type: str) -> None:
transfer_amount = getattr(wax, asset_type).satoshis(100)
transaction.push_operation(
operation=recurrent_transfer(
from_account="alice",
to_account="bob",
amount=transfer_amount,
recurrence=24,
executions=2,
memo="thanks for the service",
)
)
expected: Final[dict[str, Any]] = {
"type": "recurrent_transfer_operation",
"value": {
"amount": {
"amount": transfer_amount.amount,
"nai": transfer_amount.nai,
"precision": transfer_amount.precision,
},
"executions": 2,
"from": "alice",
"memo": "thanks for the service",
"recurrence": 24,
"to": "bob",
},
}
assert json.loads(transaction.to_api())["operations"][0] == expected
@pytest.mark.parametrize("asset_type", ["hbd", "hive"])
@pytest.mark.describe(
"Should initialize push_operation with recurrent_transfer_operation and pair_id extension via base interface"
)
def test_recurrent_transfer_with_pair_id_extension(
wax: IWaxBaseInterface, transaction: ITransaction, asset_type: str
) -> None:
id_: Final[int] = 12345
transfer_amount = getattr(wax, asset_type).satoshis(100)
transaction.push_operation(
operation=recurrent_transfer(
amount=transfer_amount,
executions=2,
from_account="alice",
memo="monthly subscription",
recurrence=24,
to_account="bob",
extensions=[
recurrent_transfer_extension(recurrent_transfer_pair_id=recurrent_transfer_pair_id(pair_id=id_))
],
)
)
expected: Final[dict[str, Any]] = {
"type": "recurrent_transfer_operation",
"value": {
"amount": {
"amount": transfer_amount.amount,
"nai": transfer_amount.nai,
"precision": transfer_amount.precision,
},
"executions": 2,
"extensions": [{"type": "recurrent_transfer_pair_id", "value": {"pair_id": id_}}],
"from": "alice",
"memo": "monthly subscription",
"recurrence": 24,
"to": "bob",
},
}
assert json.loads(transaction.to_api())["operations"][0] == expected
from __future__ import annotations
from typing import Any
import pytest
from tests.base_api.templates import INPUT_WITNESS_PROPERTIES, WITNESS_PROPERTIES
from wax import cpp_python_bridge
def test_serialize_witness_set_properties_0() -> None:
serialized = cpp_python_bridge.serialize_witness_set_properties(INPUT_WITNESS_PROPERTIES)
assert serialized[b"key"].decode() == "029072da2e84ebd6eb520f944db3d1af718500b0f1ddf60e11e986f990acddd524"
assert serialized[b"hbd_exchange_rate"].decode() == "11010000000000000320bcbee8030000000000002320bcbe"
def test_serialize_witness_set_properties_1() -> Any: # noqa: ANN401
serialized = cpp_python_bridge.serialize_witness_set_properties(WITNESS_PROPERTIES)
assert serialized[b"account_creation_fee"].decode() == "88130000000000002320bcbe"
assert serialized[b"account_subsidy_budget"].decode() == "1d030000"
assert serialized[b"account_subsidy_decay"].decode() == "b94c0500"
assert serialized[b"hbd_exchange_rate"].decode() == "64000000000000000320bcbe64000000000000002320bcbe"
assert serialized[b"hbd_interest_rate"].decode() == "e803"
assert serialized[b"key"].decode() == "02472d6eb6d691b6de8b103b51ebdf4e128a523946d8cd03d6ded91b1497ee2e83"
assert serialized[b"maximum_block_size"].decode() == "00000200"
assert serialized[b"url"].decode() == "0f68747470733a2f2f686976652e696f"
assert (
serialized[b"new_signing_key"].decode() == "02cf69b1f999d133ebbe178a8b4bbf4da356b264dfdc843b1c740378bff8f65b33"
)
return serialized
@pytest.mark.skip(reason="method not implemented.")
@pytest.mark.describe("Should be able to serialize witness properties and then deserialize")
def test_serialize_witness_set_properties_2() -> None:
serialized = test_serialize_witness_set_properties_1()
deserialized = cpp_python_bridge.deserialize_witness_set_properties(serialized)
assert deserialized == WITNESS_PROPERTIES
from __future__ import annotations
from typing import TYPE_CHECKING
import pytest
if TYPE_CHECKING:
from wax.interfaces import IWaxBaseInterface
@pytest.mark.describe("Should be able to suggest brain key")
def test_suggest_brain_key(wax: IWaxBaseInterface) -> None:
brain_key_data = wax.suggest_brain_key()
assert len(brain_key_data.associated_public_key) == 53
assert isinstance(brain_key_data.associated_public_key, str)
assert len(brain_key_data.brain_key.split(" ")) == 16
assert isinstance(brain_key_data.brain_key, str)
assert len(brain_key_data.wif_private_key) == 51
assert isinstance(brain_key_data.wif_private_key, str)
from __future__ import annotations
import json
from datetime import datetime, timezone
from typing import TYPE_CHECKING, Final
import pytest
if TYPE_CHECKING:
from beekeepy._interface.abc.asynchronous.wallet import UnlockedWallet
from wax.interfaces import IWaxBaseInterface
from wax._private.models.hive_date_time import HiveDateTime
from wax.proto.operations import operation, recurrent_transfer, vote
from wax.proto.transaction import transaction
from .templates import (
RECOVER_ACCOUNT_TRANSACTION,
REQUIRED_ACTIVE_AUTHORITY_TRANSACTION,
REQUIRED_OWNER_AUTHORITY_TRANSACTION,
SIGNATURE_TRANSACTION,
)
@pytest.mark.describe("Should be able to create TAPOS transaction using implicit expiration time")
async def test_transaction_processing_0(wallet: UnlockedWallet, wax: IWaxBaseInterface) -> None:
await wallet.import_keys(private_keys=["5JkFnXrLM2ap9t3AmAxBJvQHF7xSKtnTrCTginQCkhzU5S7ecPT"])
tx = wax.create_transaction_with_tapos("04c1c7a566fc0da66aee465714acee7346b48ac2")
now = HiveDateTime.now()
tx.push_operation(
vote(
author="c0ff33a",
permlink="ewxhnjbj",
voter="otom",
weight=2200,
)
).validate()
await tx.sign(wallet, "STM5RqVBAVNp5ufMCetQtvLGLJo7unX9nyCBMMrTXRWQ9i1Zzzizh")
expiration = HiveDateTime.fromisoformat(tx.transaction.expiration)
assert len(tx.signature_keys) == 1
assert tx.signature_keys[0] == "STM5RqVBAVNp5ufMCetQtvLGLJo7unX9nyCBMMrTXRWQ9i1Zzzizh"
assert now < expiration
@pytest.mark.describe("Should be able to bidirectional convert api to proto using object interface")
def test_transaction_processing_1(wax: IWaxBaseInterface) -> None:
tx = transaction(
ref_block_num=34559,
ref_block_prefix=1271006404,
expiration="2021-12-13T11:31:33",
operations=[
operation(
vote=vote(
author="c0ff33a",
permlink="ewxhnjbj",
voter="otom",
weight=2200,
)
)
],
)
tx_as_json = wax.create_transaction_from_proto(tx).to_api_json()
assert (
wax.create_transaction_from_json(tx_as_json).to_binary_form()
== wax.create_transaction_from_proto(tx).to_binary_form()
)
@pytest.mark.describe("Should be able to create and sign transaction using object interface")
async def test_transaction_processing_2(wallet: UnlockedWallet, wax: IWaxBaseInterface) -> None:
await wallet.import_key(private_key="5JkFnXrLM2ap9t3AmAxBJvQHF7xSKtnTrCTginQCkhzU5S7ecPT")
tx = wax.create_transaction_with_tapos(
"04c1c7a566fc0da66aee465714acee7346b48ac2",
expiration=HiveDateTime.fromisoformat("2023-08-01T15:38:48").replace(tzinfo=timezone.utc),
)
tx.push_operation(
vote(
author="c0ff33a",
permlink="ewxhnjbj",
voter="otom",
weight=2200,
)
).validate()
signature = await tx.sign(wallet, "STM5RqVBAVNp5ufMCetQtvLGLJo7unX9nyCBMMrTXRWQ9i1Zzzizh")
assert (
signature == "1f7f0c3e89e6ccef1ae156a96fb4255e619ca3a73ef3be46746b4b40a6"
"6cc4252070eb313cc6308bbee39a0a9fc38ef99137ead3c9b003584c0a1b8f5ca2ff8707"
)
assert tx.sig_digest == "205c79e3d17211882b1a2ba8640ff208413d68cabdca892cf47e9a6ad46e63a1"
assert len(tx.signature_keys) == 1
assert tx.signature_keys[0] == "STM5RqVBAVNp5ufMCetQtvLGLJo7unX9nyCBMMrTXRWQ9i1Zzzizh"
@pytest.mark.describe("Should be able to binary serialize signed transaction using object interface")
async def test_transaction_processing_3(wallet: UnlockedWallet, wax: IWaxBaseInterface) -> None:
await wallet.import_key(private_key="5JkFnXrLM2ap9t3AmAxBJvQHF7xSKtnTrCTginQCkhzU5S7ecPT")
tx = wax.create_transaction_with_tapos(
"04c1c7a566fc0da66aee465714acee7346b48ac2",
expiration=HiveDateTime.fromisoformat("2023-08-01T15:38:48").replace(tzinfo=timezone.utc),
)
tx.push_operation(
vote(
author="c0ff33a",
permlink="ewxhnjbj",
voter="otom",
weight=2200,
)
)
tx.push_operation(
recurrent_transfer(
from_account="initminer",
to_account="gtg",
amount=wax.hive.satoshis(100),
recurrence=24,
executions=2,
memo="",
)
)
signature = await tx.sign(wallet, "STM5RqVBAVNp5ufMCetQtvLGLJo7unX9nyCBMMrTXRWQ9i1Zzzizh")
assert signature == (
"1f7c6eb7a30681d77606a1491be2869e8112fee5241ec13cea5c7b4f54edc8d14"
"5269172f88359bb190fb26b362c81ccdf02bb56eb1d09daea3a381e5580e52f58"
)
assert tx.sig_digest == "d07a8509795ff7c6f33ab7d6f4da24044e8f5833f0dffcd357bf21ba5e4db1d9"
assert len(tx.signature_keys) == 1
assert tx.signature_keys[0] == "STM5RqVBAVNp5ufMCetQtvLGLJo7unX9nyCBMMrTXRWQ9i1Zzzizh"
assert tx.to_binary_form() == (
"a5c766fc0da60827c9640200046f746f6d076330666633336108657778686e6a626a98083109696e69746d696e"
"65720367746764000000000000002320bcbe00180002000000011f7c6eb7a30681d77606a1491be2869e8112fe"
"e5241ec13cea5c7b4f54edc8d145269172f88359bb190fb26b362c81ccdf02bb56eb1d09daea3a381e5580e52f58"
)
@pytest.mark.describe("Should be able to convert transaction json to binary form")
def test_transaction_processing_4(wax: IWaxBaseInterface) -> None:
tx = wax.create_transaction_from_proto(
transaction=transaction(
ref_block_num=34559,
ref_block_prefix=1271006404,
expiration="2021-12-13T11:31:33",
operations=[
operation(
vote=vote(
author="c0ff33a",
permlink="ewxhnjbj",
voter="otom",
weight=2200,
)
)
],
)
)
assert tx.to_binary_form() == "ff86c404c24b152fb7610100046f746f6d076330666633336108657778686e6a626a98080000"
@pytest.mark.describe("Should be able to sign the transaction twice")
async def test_transaction_processing_5(wallet: UnlockedWallet, wax: IWaxBaseInterface) -> None:
key = (await wallet.import_keys(private_keys=["5JkFnXrLM2ap9t3AmAxBJvQHF7xSKtnTrCTginQCkhzU5S7ecPT"]))[0]
other_key = (await wallet.import_keys(private_keys=["5KXNQP5feaaXpp28yRrGaFeNYZT7Vrb1PqLEyo7E3pJiG1veLKG"]))[0]
tx = wax.create_transaction_with_tapos(
"04c1c7a566fc0da66aee465714acee7346b48ac2",
expiration=datetime.fromisoformat("2023-08-01T15:38:48").replace(tzinfo=timezone.utc),
)
tx.push_operation(
vote(
voter="otom",
author="c0ff33a",
permlink="ewxhnjbj",
weight=2200,
)
)
assert (
await tx.sign(wallet, key) == "1f7f0c3e89e6ccef1ae156a96fb4255e619ca3a73ef3be46746b4"
"b40a66cc4252070eb313cc6308bbee39a0a9fc38ef99137ead3c9b003584c0a1b8f5ca2ff8707"
)
assert (
await tx.sign(wallet, other_key) == "209e2e371495ae731486c46cad62786ebb4260a54e558c4139"
"3e4ee681047ee07b5f476133d1100e08a6b88220c62c372789efbeb17d465d1c65efb0e23f8f1e0b"
)
@pytest.mark.describe("Should be able to sign the transaction twice on different transaction instances")
async def test_transaction_processing_6(wallet: UnlockedWallet, wax: IWaxBaseInterface) -> None:
key = (await wallet.import_keys(private_keys=["5JkFnXrLM2ap9t3AmAxBJvQHF7xSKtnTrCTginQCkhzU5S7ecPT"]))[0]
other_key = (await wallet.import_keys(private_keys=["5KXNQP5feaaXpp28yRrGaFeNYZT7Vrb1PqLEyo7E3pJiG1veLKG"]))[0]
tx = wax.create_transaction_with_tapos(
"04c1c7a566fc0da66aee465714acee7346b48ac2",
expiration=datetime.fromisoformat("2023-08-01T15:38:48").replace(tzinfo=timezone.utc),
)
tx.push_operation(
vote(
voter="otom",
author="c0ff33a",
permlink="ewxhnjbj",
weight=2200,
)
)
assert (
await tx.sign(wallet, key) == "1f7f0c3e89e6ccef1ae156a96fb4255e619ca3a73ef3be46746b4"
"b40a66cc4252070eb313cc6308bbee39a0a9fc38ef99137ead3c9b003584c0a1b8f5ca2ff8707"
)
json_tx = wax.create_transaction_from_json(tx.to_api_json())
assert (
await json_tx.sign(wallet, other_key) == "209e2e371495ae731486c46cad62786ebb4260a54e558c4"
"1393e4ee681047ee07b5f476133d1100e08a6b88220c62c372789efbeb17d465d1c65efb0e23f8f1e0b"
)
@pytest.mark.describe("Should be able to get transaction required posting authority")
def test_transaction_processing_7(wax: IWaxBaseInterface) -> None:
tx = wax.create_transaction_from_json(json.dumps(SIGNATURE_TRANSACTION))
assert next(iter(tx.required_authorities.posting_accounts)) == "thatcryptodave"
assert len(tx.required_authorities.posting_accounts) == 1
assert len(tx.required_authorities.owner_accounts) == 0
assert len(tx.required_authorities.active_accounts) == 0
assert len(tx.required_authorities.other_authorities) == 0
@pytest.mark.describe("Should be able to get transaction required active authority")
def test_transaction_processing_8(wax: IWaxBaseInterface) -> None:
tx = wax.create_transaction_from_json(json.dumps(REQUIRED_ACTIVE_AUTHORITY_TRANSACTION))
assert next(iter(tx.required_authorities.active_accounts)) == "droida"
assert len(tx.required_authorities.active_accounts) == 1
assert len(tx.required_authorities.owner_accounts) == 0
assert len(tx.required_authorities.posting_accounts) == 0
assert len(tx.required_authorities.other_authorities) == 0
@pytest.mark.describe("Should be able to get transaction required owner authority")
def test_transaction_processing_9(wax: IWaxBaseInterface) -> None:
tx = wax.create_transaction_from_json(json.dumps(REQUIRED_OWNER_AUTHORITY_TRANSACTION))
assert next(iter(tx.required_authorities.owner_accounts)) == "vsc.gateway"
assert len(tx.required_authorities.owner_accounts) == 1
assert len(tx.required_authorities.active_accounts) == 0
assert len(tx.required_authorities.posting_accounts) == 0
assert len(tx.required_authorities.other_authorities) == 0
@pytest.mark.describe(
"Should be able to get transaction required authorities for transaction with recover_account_operation"
)
def test_transaction_processing_10(wax: IWaxBaseInterface) -> None:
tx = wax.create_transaction_from_json(json.dumps(RECOVER_ACCOUNT_TRANSACTION))
expected_otger_authorities: Final[int] = 2
assert len(tx.required_authorities.other_authorities) == expected_otger_authorities
assert tx.required_authorities.other_authorities[0].key_auths == {
"STM5P8syqoj7itoDjbtDvCMCb5W3BNJtUjws9v7TDNZKqBLmp3pQW": 1
}
assert tx.required_authorities.other_authorities[1].key_auths == {
"STM4wJYLcRnALfbpb4ziqiH3oLEgw9PTJZTBBj8goFyjta3mm6D1s": 1
}
assert len(tx.required_authorities.owner_accounts) == 0
assert len(tx.required_authorities.posting_accounts) == 0
assert len(tx.required_authorities.active_accounts) == 0
from __future__ import annotations
import json
from typing import TYPE_CHECKING, Any, Final
import pytest
from google.protobuf.json_format import MessageToDict
from wax._private.exceptions import WaxError
from wax._private.proto.update_proposal_pb2 import update_proposal_end_date, update_proposal_extension
from wax.proto.operations import update_proposal
if TYPE_CHECKING:
from wax.interfaces import ITransaction, IWaxBaseInterface
@pytest.mark.describe("Should initialize update proposal with mandatory fields only")
def test_initialize_update_proposal_with_mandatory_fields(transaction: ITransaction) -> None:
transaction.push_operation(
operation=update_proposal(
proposal_id=123,
creator="alice",
daily_pay={"amount": "1000", "precision": 3, "nai": "@@000000013"},
subject="Improve UI Design",
permlink="improve-ui",
)
)
expected: Final[dict[str, Any]] = {
"type": "update_proposal_operation",
"value": {
"creator": "alice",
"daily_pay": {"amount": "1000", "nai": "@@000000013", "precision": 3},
"permlink": "improve-ui",
"proposal_id": "123",
"subject": "Improve UI Design",
},
}
assert json.loads(transaction.to_api())["operations"][0] == expected
@pytest.mark.skip(reason="python version `update_proposal` not implemented")
@pytest.mark.describe("Should add end_date in update proposal when provided")
def test_adds_end_date_to_update_proposal_when_provided(transaction: ITransaction) -> None:
transaction.push_operation(
operation=update_proposal(
proposal_id=123,
creator="alice",
daily_pay={"amount": "1000", "precision": 3, "nai": "@@000000013"},
subject="Improve UI Design",
permlink="improve-ui",
extensions=[
update_proposal_extension(update_proposal_end_date=update_proposal_end_date(end_date="2023-03-14"))
],
)
)
expected: Final[dict[str, Any]] = {
"type": "update_proposal_operation",
"value": {
"creator": "alice",
"daily_pay": {"amount": "1000", "nai": "@@000000013", "precision": 3},
"extensions": [{"type": "update_proposal_end_date", "value": {"end_date": "2023-03-14T00:00:00"}}],
"permlink": "improve-ui",
"proposal_id": "123",
"subject": "Improve UI Design",
},
}
# TODO: fix in wax problem with converting time to specyfic type
# E - 'value': {'end_date': '2023-03-14T00:00:00'}}],
# E ? ---------
# E + 'value': {'end_date': '2023-03-14'}}],
assert json.loads(transaction.to_api())["operations"][0] == expected
@pytest.mark.skip(reason="python version `update_proposal` not implemented")
@pytest.mark.describe("Should handle edge case in update proposal where end_date is given as timestamp")
def test_handles_update_proposal_with_timestamp_end_date(transaction: ITransaction) -> None:
transaction.push_operation(
operation=update_proposal(
proposal_id=123,
creator="alice",
daily_pay={"amount": "1000", "precision": 3, "nai": "@@000000013"},
subject="Improve UI Design",
permlink="improve-ui",
extensions=[
# TODO: TypeError: bad argument type for built-in operation
# in TS test end_date is provided as int ↓ ↓ ↓ ↓ ↓ ↓ ↓
update_proposal_extension(update_proposal_end_date=update_proposal_end_date(end_date="1678917600000"))
],
)
)
expected: Final[dict[str, Any]] = {
"type": "update_proposal_operation",
"value": {
"creator": "alice",
"daily_pay": {"amount": "1000", "nai": "@@000000013", "precision": 3},
"extensions": [{"type": "update_proposal_end_date", "value": {"end_date": "2023-03-15T22:00:00"}}],
"permlink": "improve-ui",
"proposal_id": "123",
"subject": "Improve UI Design",
},
}
# TODO: fix in wax problem with converting time to specyfic type
# E - 'pattern_value': {'end_date': '2023-03-15T22:00:00'}}],
# E ? -------- ^ ^ ^ ^ ^ ^
# E + 'wax_value': {'end_date': '1678917600000'}}],
# problems:
# - update_proposal_end_date accept date only in string format
# - wax not converted "1678917600000" into date model
# - TypeError: bad argument type for built-in operation
assert json.loads(transaction.to_api())["operations"][0] == expected
@pytest.mark.skip(reason="can't convert wax operation to legacy format, missing method `tx.toLegacyApi()")
@pytest.mark.describe("Should be able to convert transaction to legacy api with end_date property")
def test_convert_update_proposal_to_legacy_api_with_end_date(transaction: ITransaction) -> None:
transaction.push_operation(
operation=update_proposal(
proposal_id=123,
creator="alice",
daily_pay={"amount": "1000", "precision": 3, "nai": "@@000000013"},
subject="Improve UI Design",
permlink="improve-ui",
extensions=[
update_proposal_extension(update_proposal_end_date=update_proposal_end_date(end_date="2023-03-14"))
],
)
)
expected_in_legacy_format: Final[list[str | dict[str, Any]]] = [
"update_proposal",
{
"creator": "alice",
"daily_pay": "1.000 HBD",
"extensions": [[1, {"end_date": "2023-03-14T00:00:00"}]],
"permlink": "improve-ui",
"proposal_id": 123,
"subject": "Improve UI Design",
},
]
assert [
MessageToDict(op, including_default_value_fields=True) for op in transaction.transaction.operations
] == expected_in_legacy_format
@pytest.mark.skip(reason="python version `update_proposal` not implemented")
@pytest.mark.describe("Should fail when invalid asset is provided")
def test_reject_invalid_asset_in_update_proposal_operation(wax: IWaxBaseInterface, transaction: ITransaction) -> None:
# TODO: shouldn't by possible to create update_proposal object with wrong type of asset in daily_pay field
with pytest.raises(WaxError) as error:
transaction.push_operation(
update_proposal(
proposal_id=100,
creator="initminer",
daily_pay=wax.hive.satoshis(0), # proposals daily pay should be give in HBD
subject="subject",
permlink="permlink",
extensions=[],
)
)
expected_message = (
"Invalid asset provided: "
+ json.dumps({"amount": "0", "precision": 3, "nai": "@@000000021"})
+ '. Expected asset symbol(s): "@@000000013" (HBD) with precision: 3".'
)
assert expected_message in str(error.value.args)
@pytest.mark.describe(
"Should be able to create an update proposal with underlying extensions using transaction interface"
)
def test_transaction_interface_handles_update_proposal_with_extensions(
wax: IWaxBaseInterface, transaction: ITransaction
) -> None:
transaction.push_operation(
update_proposal(
proposal_id=100,
creator="initminer",
daily_pay=wax.hbd.satoshis(0),
subject="subject",
permlink="permlink",
extensions=[
update_proposal_extension(
update_proposal_end_date=update_proposal_end_date(end_date="2023-08-01T15:38:48")
)
],
)
)
transaction.push_operation(
update_proposal(
proposal_id=100,
creator="initminer",
daily_pay=wax.hbd.satoshis(0),
subject="subject",
permlink="permlink",
extensions=[],
)
)
expected: Final[list[dict[str, Any]]] = [
{
"update_proposal": {
"creator": "initminer",
"daily_pay": {"amount": "0", "nai": "@@000000013", "precision": 3},
"permlink": "permlink",
"subject": "subject",
"proposal_id": "100",
"extensions": [{"update_proposal_end_date": {"end_date": "2023-08-01T15:38:48"}}],
}
},
{
"update_proposal": {
"creator": "initminer",
"daily_pay": {"amount": "0", "nai": "@@000000013", "precision": 3},
"permlink": "permlink",
"subject": "subject",
"proposal_id": "100",
"extensions": [],
}
},
]
# TODO: repair this assert when transaction.from_proto_to_dict() will be available
assert [
MessageToDict(op, including_default_value_fields=True) for op in transaction.transaction.operations
] == expected
from __future__ import annotations
import re
import shutil
from pathlib import Path
from typing import AsyncIterator, Iterator
import pytest
from beekeepy._remote_handle.abc.api import AbstractApi, RegisteredApisT
from beekeepy.handle.remote import RemoteSettings
from beekeepy.interfaces import HttpUrl
from wax.helpy import AsyncHived, Hived
def _convert_test_name_to_directory_name(test_name: str) -> str:
directory_name = []
parametrized_test_match = re.match(r"([\w_]+)\[(.*)\]", test_name)
if parametrized_test_match:
test_name = f"{parametrized_test_match[1]}_with_parameters_{parametrized_test_match[2]}"
for character in test_name:
character_to_append = character
if not (character_to_append.isalnum() or character_to_append in "-_"):
character_to_append = f"-0x{ord(character):X}-"
directory_name.append(character_to_append)
return "generated_" + "".join(directory_name)
@pytest.fixture(autouse=True)
def working_directory(request: pytest.FixtureRequest) -> Path:
name_of_directory = _convert_test_name_to_directory_name(request.node.name)
path_to_generated = request.node.path.parent / name_of_directory
if path_to_generated.exists():
shutil.rmtree(path_to_generated)
path_to_generated.mkdir()
assert isinstance(path_to_generated, Path), "given object is not Path"
return path_to_generated
def pytest_addoption(parser: pytest.Parser) -> None:
parser.addoption(
"--hived-http-endpoint", action="store", type=str, help="specifies http_endpoint of reference node"
)
@pytest.fixture
def registered_apis() -> RegisteredApisT:
"""Return registered methods."""
return AbstractApi._get_registered_methods()
@pytest.fixture
def hived_http_endpoint(request: pytest.FixtureRequest) -> HttpUrl:
raw_url = request.config.getoption("--hived-http-endpoint")
assert raw_url is not None
assert isinstance(raw_url, str)
return HttpUrl(raw_url)
@pytest.fixture
def sync_node(hived_http_endpoint: HttpUrl) -> Iterator[Hived]:
with Hived(settings=RemoteSettings(http_endpoint=hived_http_endpoint)) as hived:
yield hived
@pytest.fixture
async def async_node(hived_http_endpoint: HttpUrl) -> AsyncIterator[AsyncHived]:
async with AsyncHived(settings=RemoteSettings(http_endpoint=hived_http_endpoint)) as hived:
yield hived
from __future__ import annotations
from typing import TYPE_CHECKING, Final, Literal
import pytest
from beekeepy._interface._suppress_api_not_found import SuppressApiNotFound
from beekeepy.exceptions import CommunicationError, NothingToSendError, ResponseNotReadyError
if TYPE_CHECKING:
from wax.helpy._handles.hived.async_handle import AsyncHived
from wax.helpy._handles.hived.sync_handle import Hived
def test_batch_node(sync_node: Hived) -> None:
with sync_node.batch() as node:
dynamic_properties = node.api.database.get_dynamic_global_properties()
config = node.api.database.get_config()
assert len(dynamic_properties.dict(by_alias=True)) != 0
assert len(config.dict(by_alias=True)) != 0
async def test_async_batch_node(async_node: AsyncHived) -> None:
async with await async_node.batch() as node:
dynamic_properties = await node.api.database.get_dynamic_global_properties()
config = await node.api.database.get_config()
assert len(dynamic_properties.dict(by_alias=True)) != 0
assert len(config.dict(by_alias=True)) != 0
def test_batch_node_response_not_ready(sync_node: Hived) -> None:
with sync_node.batch() as node:
dynamic_properties = node.api.database.get_dynamic_global_properties()
with pytest.raises(ResponseNotReadyError):
_ = dynamic_properties.head_block_id
def test_batch_node_error_response(sync_node: Hived) -> None:
with pytest.raises(CommunicationError, match="Invalid cast"): # noqa: SIM117
with sync_node.batch() as node:
node.api.database.find_accounts(accounts=123)
def test_batch_node_error_response_delayed(sync_node: Hived) -> None:
with sync_node.batch(delay_error_on_data_access=True) as node:
response = node.api.database.find_accounts(accounts=123)
with pytest.raises(CommunicationError, match="Invalid cast"):
_ = response.accounts[0].name
async def test_async_batch_node_error_response_delayed(async_node: AsyncHived) -> None:
async with await async_node.batch(delay_error_on_data_access=True) as node:
response = await node.api.database.find_accounts(accounts=123)
with pytest.raises(CommunicationError, match="Invalid cast"):
_ = response.accounts[0].name
@pytest.mark.parametrize("order", ["first_good", "first_bad"])
def test_batch_node_mixed_request_delayed(sync_node: Hived, order: Literal["first_good", "first_bad"]) -> None:
with sync_node.batch(delay_error_on_data_access=True) as node:
if order == "first_good":
good_response = node.api.database.get_dynamic_global_properties()
bad_response = node.api.database.find_accounts(accounts=123)
else:
bad_response = node.api.database.find_accounts(accounts=123)
good_response = node.api.database.get_dynamic_global_properties()
assert good_response.head_block_number > 0
with pytest.raises(CommunicationError, match="Invalid cast"):
_ = bad_response.accounts[0].name
async def test_batch_node_nothing_to_send(sync_node: Hived) -> None:
with pytest.raises(NothingToSendError): # noqa: SIM117
with sync_node.batch():
pass
def test_batch_node_with_suppress_api_not_found(sync_node: Hived) -> None:
# ARRANGE
missing_api: Final[str] = "debug_node_api"
amount_of_requests: Final[int] = 3
with SuppressApiNotFound(missing_api) as suppress, sync_node.batch() as bnode:
for _ in range(amount_of_requests):
bnode.api.debug_node.debug_get_head_block()
# ASSERT
assert len(suppress.errors) == amount_of_requests, "there should be exactly 2 suppressed errors"
assert all(item.api == missing_api for item in suppress.errors), f"suppressed for: {suppress.errors}"
from __future__ import annotations
from typing import TYPE_CHECKING, Final
import pytest
from beekeepy.communication import (
AioHttpCommunicator,
CommonOverseer,
CommunicationSettings,
HttpxCommunicator,
RequestCommunicator,
StrictOverseer,
)
from beekeepy.exceptions import (
ApiNotFoundError,
JussiResponseError,
NullResultError,
OverseerError,
UnparsableResponseError,
)
from tests.helpy_test.helpy.testing_server import run_simple_server
if TYPE_CHECKING:
from beekeepy.communication import AbstractCommunicator, AbstractOverseer
ERRORS_TO_DETECT: Final[list[tuple[type[OverseerError], str]]] = [
(
NullResultError,
"""{"jsonrpc": "2.0", "result": null, "id": 1}""",
),
(
ApiNotFoundError,
"""{"jsonrpc": "2.0", "error": {"code": -32003, "message":
"Assert Exception:api_itr != data._registered_apis.end(): Could not find API debug_node_api"
}, "id": 1}""",
),
(
JussiResponseError,
"""{"jsonrpc":"2.0","id":null,"error":{"code":-32603,"message":
"Internal Error","data":{"error_id":"b6384d8c-95ad-4af0-92dc-dd7828d3c707",
"jussi_request_id":"000312363819934224"}}}""",
),
(UnparsableResponseError, """404: Not Found"""),
]
SYNC_COMMUNICATORS: Final[list[type[AbstractCommunicator]]] = [
HttpxCommunicator,
RequestCommunicator,
]
ASYNC_COMMUNICATORS: Final[list[type[AbstractCommunicator]]] = [
HttpxCommunicator,
AioHttpCommunicator,
]
OVERSEERS: Final[list[type[AbstractOverseer]]] = [CommonOverseer, StrictOverseer]
REQUEST: Final[str] = """{"method": "aaa", "id": 1, "jsonrpc": "2.0"}"""
@pytest.mark.parametrize("error_and_message", ERRORS_TO_DETECT)
@pytest.mark.parametrize("overseer_cls", OVERSEERS)
@pytest.mark.parametrize("communicator", SYNC_COMMUNICATORS)
def test_sync_overseer(
error_and_message: tuple[type[OverseerError], str],
overseer_cls: type[AbstractOverseer],
communicator: type[AbstractCommunicator],
) -> None:
error, message = error_and_message
overseer = overseer_cls(communicator=communicator(settings=CommunicationSettings()))
try:
with run_simple_server(message) as url, pytest.raises(error):
overseer.send(url, REQUEST)
finally:
overseer.teardown()
@pytest.mark.parametrize("error_and_message", ERRORS_TO_DETECT)
@pytest.mark.parametrize("overseer_cls", OVERSEERS)
@pytest.mark.parametrize("communicator", ASYNC_COMMUNICATORS)
async def test_async_overseer(
error_and_message: tuple[type[OverseerError], str],
overseer_cls: type[AbstractOverseer],
communicator: type[AbstractCommunicator],
) -> None:
error, message = error_and_message
overseer = overseer_cls(communicator=communicator(settings=CommunicationSettings()))
try:
with run_simple_server(message) as url, pytest.raises(error):
await overseer.async_send(url, REQUEST)
finally:
overseer.teardown()
from __future__ import annotations
import asyncio
import time
from contextlib import contextmanager
from threading import Thread
from typing import Any, Iterator
from aiohttp import web
from beekeepy._communication.abc.http_server_observer import HttpServerObserver
from beekeepy.handle.runnable import AsyncHttpServer
from beekeepy.interfaces import HttpUrl
class DummyObserver(HttpServerObserver):
async def data_received(self, data: dict[str, Any]) -> None: # noqa: ARG002
return None
class TestAsyncHttpServer(AsyncHttpServer):
def __init__(self, response: str) -> None:
self.__response = response
super().__init__(DummyObserver(), None)
def _setup_routes(self) -> None:
async def handle_post_method(request: web.Request) -> web.Response: # noqa: ARG001
return web.Response(text=self.__response)
self._app.router.add_route("POST", "/", handle_post_method)
def create_simple_server(response: str) -> TestAsyncHttpServer:
return TestAsyncHttpServer(response=response)
@contextmanager
def run_simple_server(response: str) -> Iterator[HttpUrl]:
server = create_simple_server(response)
worker = Thread(target=asyncio.run, args=(server.run(),))
worker.start()
time.sleep(0.5)
try:
yield HttpUrl(f"http://127.0.0.1:{server.port}")
finally:
server.close()
worker.join()
from __future__ import annotations
from typing import Final
from beekeepy.interfaces import HttpUrl, WsUrl
URL_TYPES = type[HttpUrl] | type[WsUrl]
DEFAULT_ADDRESS: Final[str] = "127.0.0.1"
DEFAULT_PORT: Final[int] = 8080