Add asserts for error status codes in grpc responses

Signed-off-by: Vladimir Domnich <v.domnich@yadro.com>
This commit is contained in:
Vladimir Domnich 2022-08-12 19:44:10 +03:00 committed by Vladimir Domnich
parent b6b1644fd6
commit a76614b40d
7 changed files with 62 additions and 31 deletions

View file

@ -0,0 +1,21 @@
import re
# Regex patterns of status codes of Container service (https://github.com/nspcc-dev/neofs-spec/blob/98b154848116223e486ce8b43eaa35fec08b4a99/20-api-v2/container.md)
CONTAINER_NOT_FOUND = "code = 3072.*message = container not found"
# Regex patterns of status codes of Object service (https://github.com/nspcc-dev/neofs-spec/blob/98b154848116223e486ce8b43eaa35fec08b4a99/20-api-v2/object.md)
OBJECT_ACCESS_DENIED = "code = 2048.*message = access to object operation denied"
OBJECT_NOT_FOUND = "code = 2049.*message = object not found"
OBJECT_ALREADY_REMOVED = "code = 2052.*message = object already removed"
def error_matches_status(error: Exception, status_pattern: str) -> bool:
"""
Determines whether exception matches specified status pattern.
We use re.search to be consistent with pytest.raises.
"""
match = re.search(status_pattern, str(error))
return match is not None

View file

@ -6,6 +6,7 @@ import pytest
import wallet import wallet
from common import ASSETS_DIR from common import ASSETS_DIR
from grpc_responses import OBJECT_ACCESS_DENIED
from python_keywords.acl import set_eacl from python_keywords.acl import set_eacl
from python_keywords.container import create_container from python_keywords.container import create_container
from python_keywords.neofs_verbs import (delete_object, get_object, get_range, from python_keywords.neofs_verbs import (delete_object, get_object, get_range,
@ -77,26 +78,25 @@ class TestACL:
@staticmethod @staticmethod
def check_no_access_to_container(wallet: str, cid: str, oid: str, file_name: str): def check_no_access_to_container(wallet: str, cid: str, oid: str, file_name: str):
err_pattern = '.*access to object operation denied.*' with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
with pytest.raises(Exception, match=err_pattern):
get_object(wallet, cid, oid) get_object(wallet, cid, oid)
with pytest.raises(Exception, match=err_pattern): with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
put_object(wallet, file_name, cid) put_object(wallet, file_name, cid)
with pytest.raises(Exception, match=err_pattern): with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
delete_object(wallet, cid, oid) delete_object(wallet, cid, oid)
with pytest.raises(Exception, match=err_pattern): with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
head_object(wallet, cid, oid) head_object(wallet, cid, oid)
with pytest.raises(Exception, match=err_pattern): with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
get_range(wallet, cid, oid, bearer='', range_cut='0:10') get_range(wallet, cid, oid, bearer='', range_cut='0:10')
with pytest.raises(Exception, match=err_pattern): with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
get_range_hash(wallet, cid, oid, bearer_token='', range_cut='0:10') get_range_hash(wallet, cid, oid, bearer_token='', range_cut='0:10')
with pytest.raises(Exception, match=err_pattern): with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
search_object(wallet, cid) search_object(wallet, cid)
def check_full_access(self, cid: str, file_name: str, wallet_to_check: Tuple = None) -> str: def check_full_access(self, cid: str, file_name: str, wallet_to_check: Tuple = None) -> str:

View file

@ -5,6 +5,7 @@ import allure
import pytest import pytest
from epoch import tick_epoch from epoch import tick_epoch
from grpc_responses import CONTAINER_NOT_FOUND, error_matches_status
from python_keywords.container import (create_container, delete_container, get_container, from python_keywords.container import (create_container, delete_container, get_container,
list_containers) list_containers)
from utility import placement_policy_from_container from utility import placement_policy_from_container
@ -29,8 +30,8 @@ def test_container_creation(prepare_wallet_and_deposit, name):
containers = list_containers(wallet) containers = list_containers(wallet)
assert cid in containers, f'Expected container {cid} in containers: {containers}' assert cid in containers, f'Expected container {cid} in containers: {containers}'
container_info = get_container(wallet, cid, flag='') container_info: str = get_container(wallet, cid, flag='')
container_info = container_info.lower() # To ignore case when comparing with expected values container_info = container_info.casefold() # To ignore case when comparing with expected values
info_to_check = { info_to_check = {
f'basic ACL: {PRIVATE_ACL_F} (private)', f'basic ACL: {PRIVATE_ACL_F} (private)',
@ -41,13 +42,13 @@ def test_container_creation(prepare_wallet_and_deposit, name):
info_to_check.add(f'Name={name}') info_to_check.add(f'Name={name}')
with allure.step('Check container has correct information'): with allure.step('Check container has correct information'):
expected_policy = placement_rule.lower() expected_policy = placement_rule.casefold()
actual_policy = placement_policy_from_container(container_info) actual_policy = placement_policy_from_container(container_info)
assert actual_policy == expected_policy, \ assert actual_policy == expected_policy, \
f'Expected policy\n{expected_policy} but got policy\n{actual_policy}' f'Expected policy\n{expected_policy} but got policy\n{actual_policy}'
for info in info_to_check: for info in info_to_check:
expected_info = info.lower() expected_info = info.casefold()
assert expected_info in container_info, \ assert expected_info in container_info, \
f'Expected {expected_info} in container info:\n{container_info}' f'Expected {expected_info} in container info:\n{container_info}'
@ -66,7 +67,8 @@ def wait_for_container_deletion(wallet: str, cid: str) -> None:
sleep(sleep_interval) sleep(sleep_interval)
continue continue
except Exception as err: except Exception as err:
if 'container not found' not in str(err): if error_matches_status(err, CONTAINER_NOT_FOUND):
raise AssertionError(f'Expected "container not found" in error, got\n{err}')
return return
raise AssertionError(f'Expected "{CONTAINER_NOT_FOUND}" error, got\n{err}')
raise AssertionError(f'Container was not deleted within {attempts * sleep_interval} sec') raise AssertionError(f'Container was not deleted within {attempts * sleep_interval} sec')

View file

@ -8,6 +8,7 @@ from data_formatters import get_wallet_public_key
from common import (COMPLEX_OBJ_SIZE, MAINNET_BLOCK_TIME, NEOFS_CONTRACT_CACHE_TIMEOUT, from common import (COMPLEX_OBJ_SIZE, MAINNET_BLOCK_TIME, NEOFS_CONTRACT_CACHE_TIMEOUT,
NEOFS_NETMAP_DICT, STORAGE_RPC_ENDPOINT_1, STORAGE_WALLET_PASS) NEOFS_NETMAP_DICT, STORAGE_RPC_ENDPOINT_1, STORAGE_WALLET_PASS)
from epoch import tick_epoch from epoch import tick_epoch
from grpc_responses import OBJECT_NOT_FOUND, error_matches_status
from python_keywords.container import create_container, get_container from python_keywords.container import create_container, get_container
from python_keywords.failover_utils import wait_object_replication_on_nodes from python_keywords.failover_utils import wait_object_replication_on_nodes
from python_keywords.neofs_verbs import delete_object, get_object, head_object, put_object from python_keywords.neofs_verbs import delete_object, get_object, head_object, put_object
@ -400,7 +401,8 @@ def wait_for_obj_dropped(wallet: str, cid: str, oid: str, checker) -> None:
checker(wallet, cid, oid) checker(wallet, cid, oid)
wait_for_gc_pass_on_storage_nodes() wait_for_gc_pass_on_storage_nodes()
except Exception as err: except Exception as err:
if 'object not found' in str(err): if error_matches_status(err, OBJECT_NOT_FOUND):
break return
else: raise AssertionError(f'Expected "{OBJECT_NOT_FOUND}" error, got\n{err}')
raise AssertionError(f'Object {oid} is not dropped from node')
raise AssertionError(f'Object {oid} was not dropped from node')

View file

@ -3,9 +3,11 @@ from time import sleep
import allure import allure
import pytest import pytest
from common import SIMPLE_OBJ_SIZE, COMPLEX_OBJ_SIZE from common import SIMPLE_OBJ_SIZE, COMPLEX_OBJ_SIZE
from container import create_container from container import create_container
from epoch import get_epoch, tick_epoch from epoch import get_epoch, tick_epoch
from grpc_responses import OBJECT_ALREADY_REMOVED, OBJECT_NOT_FOUND, error_matches_status
from python_keywords.neofs_verbs import (delete_object, get_object, get_range, from python_keywords.neofs_verbs import (delete_object, get_object, get_range,
get_range_hash, head_object, get_range_hash, head_object,
put_object, search_object) put_object, search_object)
@ -93,8 +95,8 @@ def test_object_api(prepare_wallet_and_deposit, request, object_size):
sleep(CLEANUP_TIMEOUT) sleep(CLEANUP_TIMEOUT)
with allure.step('Get objects and check errors'): with allure.step('Get objects and check errors'):
get_object_and_check_error(**wallet_cid, oid=oids[0], err_msg='object already removed') get_object_and_check_error(**wallet_cid, oid=oids[0], error_pattern=OBJECT_ALREADY_REMOVED)
get_object_and_check_error(**wallet_cid, oid=oids[1], err_msg='object already removed') get_object_and_check_error(**wallet_cid, oid=oids[1], error_pattern=OBJECT_ALREADY_REMOVED)
@allure.title('Test object life time') @allure.title('Test object life time')
@ -126,17 +128,17 @@ def test_object_api_lifetime(prepare_wallet_and_deposit, request, object_size):
wait_for_gc_pass_on_storage_nodes() wait_for_gc_pass_on_storage_nodes()
with allure.step('Check object deleted because it expires-on epoch'): with allure.step('Check object deleted because it expires-on epoch'):
with pytest.raises(Exception, match='.*object not found.*'): with pytest.raises(Exception, match=OBJECT_NOT_FOUND):
get_object(wallet, cid, oid) get_object(wallet, cid, oid)
def get_object_and_check_error(wallet: str, cid: str, oid: str, err_msg: str): def get_object_and_check_error(wallet: str, cid: str, oid: str, error_pattern: str) -> None:
try: try:
get_object(wallet=wallet, cid=cid, oid=oid) get_object(wallet=wallet, cid=cid, oid=oid)
raise AssertionError(f'Expected object {oid} removed, but it is not') raise AssertionError(f'Expected object {oid} removed, but it is not')
except Exception as err: except Exception as err:
logger.info(f'Error is {err}') logger.info(f'Error is {err}')
assert err_msg in str(err), f'Expected message {err_msg} in error: {err}' assert error_matches_status(err, error_pattern), f'Expected {err} to match {error_pattern}'
def check_header_is_presented(head_info: dict, object_header: dict): def check_header_is_presented(head_info: dict, object_header: dict):

View file

@ -5,9 +5,11 @@ from time import sleep
import allure import allure
import pytest import pytest
from common import COMPLEX_OBJ_SIZE from common import COMPLEX_OBJ_SIZE
from container import create_container from container import create_container
from epoch import get_epoch, tick_epoch from epoch import get_epoch, tick_epoch
from grpc_responses import OBJECT_NOT_FOUND, error_matches_status
from python_keywords.http_gate import (get_via_http_curl, get_via_http_gate, from python_keywords.http_gate import (get_via_http_curl, get_via_http_gate,
get_via_http_gate_by_attribute, get_via_zip_http_gate, get_via_http_gate_by_attribute, get_via_zip_http_gate,
upload_via_http_gate, upload_via_http_gate_curl) upload_via_http_gate, upload_via_http_gate_curl)
@ -128,7 +130,6 @@ class TestHttpGate:
def test_expiration_epoch_in_http(self): def test_expiration_epoch_in_http(self):
cid = create_container(self.wallet, rule=self.PLACEMENT_RULE, basic_acl=PUBLIC_ACL) cid = create_container(self.wallet, rule=self.PLACEMENT_RULE, basic_acl=PUBLIC_ACL)
file_path = generate_file() file_path = generate_file()
object_not_found_err = 'object not found'
oids = [] oids = []
curr_epoch = get_epoch() curr_epoch = get_epoch()
@ -156,7 +157,7 @@ class TestHttpGate:
self.try_to_get_object_and_expect_error( self.try_to_get_object_and_expect_error(
cid=cid, cid=cid,
oid=oid, oid=oid,
expected_err=object_not_found_err error_pattern=OBJECT_NOT_FOUND
) )
with allure.step('Other objects can be get'): with allure.step('Other objects can be get'):
@ -220,12 +221,13 @@ class TestHttpGate:
@staticmethod @staticmethod
@allure.step('Try to get object and expect error') @allure.step('Try to get object and expect error')
def try_to_get_object_and_expect_error(cid: str, oid: str, expected_err: str): def try_to_get_object_and_expect_error(cid: str, oid: str, error_pattern: str) -> None:
try: try:
get_via_http_gate(cid=cid, oid=oid) get_via_http_gate(cid=cid, oid=oid)
raise AssertionError(f'Expected error on getting object with cid: {cid}') raise AssertionError(f'Expected error on getting object with cid: {cid}')
except Exception as err: except Exception as err:
assert expected_err in str(err), f'Expected error {expected_err} in {err}' assert error_matches_status(err, error_pattern), \
f'Expected {err} to match {error_pattern}'
@staticmethod @staticmethod
@allure.step('Verify object can be get using HTTP header attribute') @allure.step('Verify object can be get using HTTP header attribute')

View file

@ -2,7 +2,7 @@
""" """
This module contains keywords which are used for asserting This module contains keywords which are used for asserting
that storage policies are kept. that storage policies are respected.
""" """
from typing import Optional from typing import Optional
@ -13,6 +13,8 @@ from robot.api.deco import keyword
import complex_object_actions import complex_object_actions
import neofs_verbs import neofs_verbs
from common import NEOFS_NETMAP from common import NEOFS_NETMAP
from grpc_responses import OBJECT_NOT_FOUND, error_matches_status
ROBOT_AUTO_KEYWORDS = False ROBOT_AUTO_KEYWORDS = False
@ -142,7 +144,7 @@ def get_nodes_without_object(wallet: str, cid: str, oid: str):
if res is None: if res is None:
nodes_list.append(node) nodes_list.append(node)
except Exception as err: except Exception as err:
if 'object not found' in str(err): if error_matches_status(err, OBJECT_NOT_FOUND):
nodes_list.append(node) nodes_list.append(node)
else: else:
raise Exception(f'Got error {err} on head object command') from err raise Exception(f'Got error {err} on head object command') from err