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
from common import ASSETS_DIR
from grpc_responses import OBJECT_ACCESS_DENIED
from python_keywords.acl import set_eacl
from python_keywords.container import create_container
from python_keywords.neofs_verbs import (delete_object, get_object, get_range,
@ -77,26 +78,25 @@ class TestACL:
@staticmethod
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=err_pattern):
with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
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)
with pytest.raises(Exception, match=err_pattern):
with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
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)
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')
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')
with pytest.raises(Exception, match=err_pattern):
with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
search_object(wallet, cid)
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
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,
list_containers)
from utility import placement_policy_from_container
@ -23,14 +24,14 @@ def test_container_creation(prepare_wallet_and_deposit, name):
json_wallet = json.load(file)
placement_rule = 'REP 2 IN X CBF 1 SELECT 2 FROM * AS X'
options = f" --name {name}" if name else ""
options = f"--name {name}" if name else ""
cid = create_container(wallet, rule=placement_rule, options=options)
containers = list_containers(wallet)
assert cid in containers, f'Expected container {cid} in containers: {containers}'
container_info = get_container(wallet, cid, flag='')
container_info = container_info.lower() # To ignore case when comparing with expected values
container_info: str = get_container(wallet, cid, flag='')
container_info = container_info.casefold() # To ignore case when comparing with expected values
info_to_check = {
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}')
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)
assert actual_policy == expected_policy, \
f'Expected policy\n{expected_policy} but got policy\n{actual_policy}'
for info in info_to_check:
expected_info = info.lower()
expected_info = info.casefold()
assert expected_info in 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)
continue
except Exception as err:
if 'container not found' not in str(err):
raise AssertionError(f'Expected "container not found" in error, got\n{err}')
return
if error_matches_status(err, CONTAINER_NOT_FOUND):
return
raise AssertionError(f'Expected "{CONTAINER_NOT_FOUND}" error, got\n{err}')
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,
NEOFS_NETMAP_DICT, STORAGE_RPC_ENDPOINT_1, STORAGE_WALLET_PASS)
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.failover_utils import wait_object_replication_on_nodes
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)
wait_for_gc_pass_on_storage_nodes()
except Exception as err:
if 'object not found' in str(err):
break
else:
raise AssertionError(f'Object {oid} is not dropped from node')
if error_matches_status(err, OBJECT_NOT_FOUND):
return
raise AssertionError(f'Expected "{OBJECT_NOT_FOUND}" error, got\n{err}')
raise AssertionError(f'Object {oid} was not dropped from node')

View file

@ -3,9 +3,11 @@ from time import sleep
import allure
import pytest
from common import SIMPLE_OBJ_SIZE, COMPLEX_OBJ_SIZE
from container import create_container
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,
get_range_hash, head_object,
put_object, search_object)
@ -93,8 +95,8 @@ def test_object_api(prepare_wallet_and_deposit, request, object_size):
sleep(CLEANUP_TIMEOUT)
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[1], 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], error_pattern=OBJECT_ALREADY_REMOVED)
@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()
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)
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:
get_object(wallet=wallet, cid=cid, oid=oid)
raise AssertionError(f'Expected object {oid} removed, but it is not')
except Exception as 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):

View file

@ -5,9 +5,11 @@ from time import sleep
import allure
import pytest
from common import COMPLEX_OBJ_SIZE
from container import create_container
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,
get_via_http_gate_by_attribute, get_via_zip_http_gate,
upload_via_http_gate, upload_via_http_gate_curl)
@ -128,7 +130,6 @@ class TestHttpGate:
def test_expiration_epoch_in_http(self):
cid = create_container(self.wallet, rule=self.PLACEMENT_RULE, basic_acl=PUBLIC_ACL)
file_path = generate_file()
object_not_found_err = 'object not found'
oids = []
curr_epoch = get_epoch()
@ -156,7 +157,7 @@ class TestHttpGate:
self.try_to_get_object_and_expect_error(
cid=cid,
oid=oid,
expected_err=object_not_found_err
error_pattern=OBJECT_NOT_FOUND
)
with allure.step('Other objects can be get'):
@ -220,12 +221,13 @@ class TestHttpGate:
@staticmethod
@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:
get_via_http_gate(cid=cid, oid=oid)
raise AssertionError(f'Expected error on getting object with cid: {cid}')
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
@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
that storage policies are kept.
that storage policies are respected.
"""
from typing import Optional
@ -13,6 +13,8 @@ from robot.api.deco import keyword
import complex_object_actions
import neofs_verbs
from common import NEOFS_NETMAP
from grpc_responses import OBJECT_NOT_FOUND, error_matches_status
ROBOT_AUTO_KEYWORDS = False
@ -142,7 +144,7 @@ def get_nodes_without_object(wallet: str, cid: str, oid: str):
if res is None:
nodes_list.append(node)
except Exception as err:
if 'object not found' in str(err):
if error_matches_status(err, OBJECT_NOT_FOUND):
nodes_list.append(node)
else:
raise Exception(f'Got error {err} on head object command') from err