Skip to content

Commit

Permalink
[refactor] #2398: Add integration tests for query filters
Browse files Browse the repository at this point in the history
Signed-off-by: Shanin Roman <[email protected]>
  • Loading branch information
Erigara authored and appetrosyan committed Jul 31, 2023
1 parent 6c3b833 commit 7e7c120
Show file tree
Hide file tree
Showing 26 changed files with 312 additions and 232 deletions.
8 changes: 4 additions & 4 deletions client_cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,15 +133,15 @@ To know how many units of a particular asset an account has, use `asset get` wit

This query returns the quantity of `XOR#Soramitsu` asset for the `White Rabbit@Soramitsu` account.

It's possible to filter based on account, asset, domain id by using filtering api provided by client cli.
It's possible to filter based on either account, asset or domain id by using the filtering API provided by the Iroha client CLI.

Generally it looks like this:

```bash
./iroha_clien_cli ENTITY list filter PREDICATE
./iroha_client_cli ENTITY list filter PREDICATE
```

Where ENTITY is asset, account or domain and PREDICATE is condition used for filtering serialized using JSON (check ValuePredicate and GenericPredicateBox in [schema](https://github.com/hyperledger/iroha/blob/iroha2-dev/docs/source/references/schema.json) for reference).
Where ENTITY is asset, account or domain and PREDICATE is condition used for filtering serialized using JSON (check `ValuePredicate` and `GenericPredicateBox` in [schema](https://github.com/hyperledger/iroha/blob/iroha2-dev/docs/source/references/schema.json) for reference).

Examples:

Expand All @@ -151,7 +151,7 @@ Examples:
# Filter accounts by domain
./iroha_client_cli account list filter '{"Identifiable": {"EndsWith": "@wonderland"}}'
# It is possible to combine filters using "Or" or "And"
# Filter asset by it's domain id
# Filter asset by domain
./iroha_client_cli asset list filter '{"Or": [{"Identifiable": {"Contains": "#wonderland#"}}, {"And": [{"Identifiable": {"Contains": "##"}}, {"Identifiable": {"EndsWith": "@wonderland"}}]}]}'
```

Expand Down
2 changes: 1 addition & 1 deletion client_cli/pytests/common/consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class Stderr(Enum):
CANNOT_BE_EMPTY = 'cannot be empty\n\nFor more information try --help\n'
REPETITION = 'Repetition'
TOO_LONG = 'Name length violation'
FAILED_TO_FIND_DOMAIN = 'Failed to find domain'
FAILED_TO_FIND_DOMAIN = 'Entity missing'
INVALID_CHARACTER = 'Invalid character'
INVALID_VALUE_TYPE = 'Matching variant not found'
RESERVED_CHARACTER = 'The `@` character is reserved for `account@domain` constructs, `#` — for `asset#domain` and `$` — for `trigger$domain`'
Expand Down
42 changes: 19 additions & 23 deletions client_cli/pytests/src/client_cli/client_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"""
import json
import subprocess
from time import sleep, time
from time import sleep, monotonic
from typing import Callable

import allure
Expand All @@ -18,6 +18,9 @@ class ClientCli:
A class to represent the Iroha client command line interface.
"""
BASE_PATH = CLIENT_CLI_PATH
# --skip-mst-check flag is used because
# MST isn't used in the tests
# and don't using this flag results in tests being broken by interactive prompt
BASE_FLAGS = ['--config=' + PATH_CONFIG_CLIENT_CLI, '--skip-mst-check']

def __init__(self, config: Config):
Expand Down Expand Up @@ -48,36 +51,21 @@ def __exit__(self, exc_type, exc_val, exc_tb):
"""
self.reset()

def wait_for(self, expected: str, actual_call: Callable[[], str], timeout=None):
def wait_for(self, condition: Callable[[], bool], timeout=None):
"""
Wait for a certain condition to be met, specified by the expected and actual values.
:param expected: The expected value.
:type expected: str
:param actual: The actual value.
:type actual: str
:param condition: Condition that should be met in given time.
:type condition: Callable[[], bool]
:param timeout: Maximum time to wait for the condition to be met, defaults to None.
:type timeout: int, optional
"""
timeout = timeout or self._timeout
start_time = time()
actual = actual_call()
while expected not in actual:
if time() - start_time > timeout:
allure.attach(
json.dumps(actual),
name='actual',
attachment_type=allure.attachment_type.JSON)
allure.attach(
json.dumps(expected),
name='expected',
attachment_type=allure.attachment_type.JSON)
raise TimeoutError(f"Expected '{expected}' "
f"to be in '{actual}' "
f"after waiting for '{timeout}' seconds.")
start_time = monotonic()
while not condition():
if monotonic() - start_time > timeout:
raise TimeoutError(f"Expected condition to be satisfied after waiting for '{timeout}' seconds.")
sleep(1)
actual = actual_call()
assert expected in actual

def reset(self):
"""
Expand Down Expand Up @@ -255,6 +243,14 @@ def execute(self):
text=True
) as process:
self.stdout, self.stderr = process.communicate()
allure.attach(
self.stdout,
name='stdout',
attachment_type=allure.attachment_type.TEXT)
allure.attach(
self.stderr,
name='stderr',
attachment_type=allure.attachment_type.TEXT)
except Exception as exception:
raise RuntimeError(
f"Error executing command: {command}. "
Expand Down
43 changes: 30 additions & 13 deletions client_cli/pytests/src/client_cli/have.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,22 @@
This module contains functions for checking expected results in tests.
"""

import json
import allure

from src.client_cli import client_cli, iroha, match

def expected_in_actual(expected, actual) -> bool:
allure.attach(
json.dumps(actual),
name='actual',
attachment_type=allure.attachment_type.JSON)
allure.attach(
json.dumps(expected),
name='expected',
attachment_type=allure.attachment_type.JSON)

return expected in actual

def domain(expected):
"""
Expand All @@ -12,9 +26,10 @@ def domain(expected):
:param expected: The expected domain object.
:return: True if the domain is present, False otherwise.
"""
return match.iroha_have_domain(
expected=expected,
actual=lambda: iroha.list_filter(f'{{"Identifiable": {{"Is": "{expected}"}}}}').domains())
def domain_in_domains() -> bool:
domains = iroha.list_filter(f'{{"Identifiable": {{"Is": "{expected}"}}}}').domains()
return expected_in_actual(expected, domains)
return client_cli.wait_for(domain_in_domains)


def account(expected):
Expand All @@ -24,9 +39,10 @@ def account(expected):
:param expected: The expected account object.
:return: True if the account is present, False otherwise.
"""
return match.iroha_have_account(
expected=expected,
actual=lambda: iroha.list_filter(f'{{"Identifiable": {{"Is": "{expected}"}}}}').accounts())
def account_in_accounts() -> bool:
accounts = iroha.list_filter(f'{{"Identifiable": {{"Is": "{expected}"}}}}').accounts()
return expected_in_actual(expected, accounts)
return client_cli.wait_for(account_in_accounts)


def asset_definition(expected):
Expand All @@ -37,10 +53,10 @@ def asset_definition(expected):
:return: True if the asset definition is present, False otherwise.
"""
expected_domain = expected.split('#')[1]
return match.iroha_have_asset_definition(
expected=expected,
actual=lambda: iroha.list_filter(f'{{"Identifiable": {{"Is": "{expected_domain}"}}}}').asset_definitions())

def asset_definition_in_asset_definitions() -> bool:
asset_definitions = iroha.list_filter(f'{{"Identifiable": {{"Is": "{expected_domain}"}}}}').asset_definitions()
return expected_in_actual(expected, asset_definitions)
return client_cli.wait_for(asset_definition_in_asset_definitions)

def asset(expected):
"""
Expand All @@ -49,9 +65,10 @@ def asset(expected):
:param expected: The expected asset object.
:return: True if the asset is present, False otherwise.
"""
return match.iroha_have_asset(
expected=expected,
actual=lambda: iroha.list_filter(f'{{"Identifiable": {{"Is": "{expected}"}}}}').assets())
def asset_in_assets() -> bool:
assets = iroha.list_filter(f'{{"Identifiable": {{"Is": "{expected}"}}}}').assets()
return expected_in_actual(expected, assets)
return client_cli.wait_for(asset_in_assets)


def error(expected):
Expand Down
16 changes: 8 additions & 8 deletions client_cli/pytests/src/client_cli/iroha.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ def domains(self) -> List[str]:
"""
Retrieve domains from the Iroha network and return then as list of ids.
:return: The current Iroha object.
:rtype: Iroha
:return: List of domains ids.
:rtype: List[str]
"""
self._execute_command('domain')
domains = json.loads(self.stdout)
Expand All @@ -59,8 +59,8 @@ def accounts(self) -> List[str]:
"""
Retrieve accounts from the Iroha network and return them as list of ids.
:return: The current Iroha object.
:rtype: Iroha
:return: List of accounts ids.
:rtype: List[str]
"""
self._execute_command('account')
accounts = json.loads(self.stdout)
Expand All @@ -71,8 +71,8 @@ def assets(self) -> List[str]:
"""
Retrieve assets from the Iroha network and return them as list of ids.
:return: The current Iroha object.
:rtype: Iroha
:return: List of assets ids.
:rtype: List[str]
"""
self._execute_command('asset')
assets = json.loads(self.stdout)
Expand All @@ -84,8 +84,8 @@ def asset_definitions(self) -> Dict[str, str]:
Retrieve asset definitions from the Iroha network
and return them as map where ids are keys and value types are values
:return: The current Iroha object.
:rtype: Iroha
:return: Dict of asset definitions ids with there value type.
:rtype: Dict[str, str]
"""
self._execute_command('domain')
domains = json.loads(self.stdout)
Expand Down
54 changes: 0 additions & 54 deletions client_cli/pytests/src/client_cli/match.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,62 +2,8 @@
This module provides helper functions for matching expected and actual values in Iroha objects.
"""

from typing import Callable
import allure

from src.client_cli import client_cli


def wait_for(expected, actual):
"""
Waits for the expected value to be present in the actual value.
:param expected: The expected value.
:param actual: The actual value.
"""
client_cli.wait_for(expected, actual)


def iroha_have_domain(expected: str, actual: str):
"""
Checks if Iroha has the expected domain.
:param expected: The expected domain.
:param actual: The actual domain list.
"""
wait_for(expected, actual)


def iroha_have_account(expected: str, actual: str):
"""
Checks if Iroha has the expected account.
:param expected: The expected account.
:param actual: The actual account list.
"""
wait_for(expected, actual)


def iroha_have_asset_definition(expected: str, actual: Callable[[], str]):
"""
Checks if Iroha has the expected asset definition.
:param expected: The expected asset definition.
:param actual: The actual asset definition list.
"""
wait_for(expected, actual)


def iroha_have_asset(expected: str, actual: str):
"""
Checks if Iroha has the expected asset.
:param expected: The expected asset.
:param actual: The actual asset list.
"""
wait_for(expected, actual)


def client_cli_have_error(expected: str, actual: str):
"""
Checks if the command-line client has the expected error.
Expand Down
10 changes: 5 additions & 5 deletions client_cli/pytests/test/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
GIVEN_127_lenght_name,
GIVEN_128_lenght_name,
GIVEN_129_lenght_name,
GIVEN_new_one_existence_account,
GIVEN_existence_asset_definition_with_quantity_value_type,
GIVEN_existence_asset_definition_with_store_value_type,
GIVEN_existence_domain_with_uppercase_letter,
GIVEN_new_one_existence_domain,
GIVEN_new_one_existing_account,
GIVEN_existing_asset_definition_with_quantity_value_type,
GIVEN_existing_asset_definition_with_store_value_type,
GIVEN_existing_domain_with_uppercase_letter,
GIVEN_new_one_existing_domain,
GIVEN_fake_asset_name,
GIVEN_fake_name,
GIVEN_key_with_invalid_character_in_key,
Expand Down
4 changes: 2 additions & 2 deletions client_cli/pytests/test/accounts/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
from test import (
GIVEN_127_lenght_name,
GIVEN_129_lenght_name,
GIVEN_new_one_existence_account,
GIVEN_new_one_existence_domain,
GIVEN_new_one_existing_account,
GIVEN_new_one_existing_domain,
GIVEN_fake_name,
GIVEN_key_with_invalid_character_in_key,
GIVEN_not_existing_name,
Expand Down
51 changes: 51 additions & 0 deletions client_cli/pytests/test/accounts/test_accounts_query_filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import json
import allure

from src.client_cli import iroha, client_cli

# using existing account to have at least one account in response
def test_filter_by_domain(GIVEN_new_one_existing_account):
def condition():
domain = GIVEN_new_one_existing_account.domain
with allure.step(
f'WHEN client_cli query accounts '
f'in the "{domain}" domain'):
accounts = iroha.list_filter(f'{{"Identifiable": {{"EndsWith": "@{domain}"}}}}').accounts()
with allure.step(
f'THEN Iroha should return only accounts with this domain'):
allure.attach(
json.dumps(accounts),
name='accounts',
attachment_type=allure.attachment_type.JSON)
return accounts and all(account.endswith(domain) for account in accounts)
client_cli.wait_for(condition)

def test_filter_by_account_name(GIVEN_new_one_existing_account):
def condition():
name = GIVEN_new_one_existing_account.name
with allure.step(
f'WHEN client_cli query accounts with name "{name}"'):
accounts = iroha.list_filter(f'{{"Identifiable": {{"StartsWith": "{name}@"}}}}').accounts()
with allure.step(
f'THEN Iroha should return only accounts with this name'):
allure.attach(
json.dumps(accounts),
name='accounts',
attachment_type=allure.attachment_type.JSON)
return accounts and all(account.startswith(name) for account in accounts)
client_cli.wait_for(condition)

def test_filter_by_account_id(GIVEN_new_one_existing_account):
def condition():
account_id = GIVEN_new_one_existing_account.name + "@" + GIVEN_new_one_existing_account.domain
with allure.step(
f'WHEN client_cli query accounts with account id "{account_id}"'):
accounts = iroha.list_filter(f'{{"Identifiable": {{"Is": "{account_id}"}}}}').accounts()
with allure.step(
f'THEN Iroha should return only accounts with this id'):
allure.attach(
json.dumps(accounts),
name='accounts',
attachment_type=allure.attachment_type.JSON)
return accounts and all(account == account_id for account in accounts)
client_cli.wait_for(condition)
Loading

0 comments on commit 7e7c120

Please sign in to comment.