Add asserts for error status codes in grpc responses
Signed-off-by: Vladimir Domnich <v.domnich@yadro.com>
This commit is contained in:
parent
b6b1644fd6
commit
a76614b40d
7 changed files with 62 additions and 31 deletions
21
pytest_tests/helpers/grpc_responses.py
Normal file
21
pytest_tests/helpers/grpc_responses.py
Normal 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
|
|
@ -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:
|
||||
|
|
|
@ -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}')
|
||||
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')
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue