diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 5b2ba66bd..809bd0161 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -144,7 +144,7 @@ jobs: uses: actions/checkout@v4 with: repository: nspcc-dev/neofs-rest-gw - ref: f024e798a5bfc5d3183bac417c8a6713e9e756b0 + ref: v0.9.0 path: neofs-rest-gw - name: Build neofs-rest-gw diff --git a/neofs-testlib/neofs_testlib/env/templates/neofs_env_config.yaml b/neofs-testlib/neofs_testlib/env/templates/neofs_env_config.yaml index 5a6b2e9a9..3ab98d446 100644 --- a/neofs-testlib/neofs_testlib/env/templates/neofs_env_config.yaml +++ b/neofs-testlib/neofs_testlib/env/templates/neofs_env_config.yaml @@ -36,7 +36,7 @@ binaries: neofs_rest_gw: repo: 'nspcc-dev/neofs-rest-gw' - version: 'v0.8.3' + version: 'v0.9.0' file: 'neofs-rest-gw-linux-amd64' neo_go: diff --git a/pytest_tests/lib/helpers/http_gate.py b/pytest_tests/lib/helpers/rest_gate.py similarity index 66% rename from pytest_tests/lib/helpers/http_gate.py rename to pytest_tests/lib/helpers/rest_gate.py index a41a6a547..bf2515bd5 100644 --- a/pytest_tests/lib/helpers/http_gate.py +++ b/pytest_tests/lib/helpers/rest_gate.py @@ -23,8 +23,8 @@ ASSETS_DIR = os.getenv("ASSETS_DIR", "TemporaryDir/") -@allure.step("Get via HTTP Gate") -def get_via_http_gate( +@allure.step("Get via REST Gate") +def get_via_rest_gate( cid: str, oid: str, endpoint: str, @@ -33,11 +33,11 @@ def get_via_http_gate( download=False, ) -> Union[str, requests.Response]: """ - This function gets given object from HTTP gate + This function gets given object from REST gate cid: container id to get object from oid: object ID - endpoint: http gate endpoint - request_path: (optional) http request, if ommited - use default [{endpoint}/get/{cid}/{oid}] + endpoint: REST gate endpoint + request_path: (optional) REST request, if ommited - use default [{endpoint}/objects/{cid}/by_id/{oid}] return_response: (optional) either return internal requests.Response object or not """ @@ -46,7 +46,7 @@ def get_via_http_gate( if download: download_attribute = "?download=true" if request_path is None: - request = f"{endpoint}/get/{cid}/{oid}{download_attribute}" + request = f"{endpoint}/objects/{cid}/by_id/{oid}{download_attribute}" else: request = f"{endpoint}{request_path}{download_attribute}" @@ -54,7 +54,7 @@ def get_via_http_gate( if not resp.ok: raise Exception( - f"""Failed to get object via HTTP gate: + f"""Failed to get object via REST gate: request: {resp.request.path_url}, response: {resp.text}, status code: {resp.status_code} {resp.reason}""" @@ -103,20 +103,20 @@ def get_via_zip_http_gate(cid: str, prefix: str, endpoint: str): return os.path.join(os.getcwd(), ASSETS_DIR, prefix) -@allure.step("Get via HTTP Gate by attribute") -def get_via_http_gate_by_attribute(cid: str, attribute: dict, endpoint: str, request_path: Optional[str] = None): +@allure.step("Get via REST Gate by attribute") +def get_via_rest_gate_by_attribute(cid: str, attribute: dict, endpoint: str, request_path: Optional[str] = None): """ - This function gets given object from HTTP gate + This function gets given object from REST gate cid: CID to get object from attribute: attribute {name: attribute} value pair - endpoint: http gate endpoint - request_path: (optional) http request path, if ommited - use default [{endpoint}/get_by_attribute/{Key}/{Value}] + endpoint: REST gate endpoint + request_path: (optional) REST request path, if ommited - use default [{endpoint}/objects/{Key}/by_attribute/{Value}] """ attr_name = list(attribute.keys())[0] attr_value = quote(str(attribute.get(attr_name))) # if `request_path` parameter ommited, use default if request_path is None: - request = f"{endpoint}/get_by_attribute/{cid}/{quote(str(attr_name))}/{attr_value}" + request = f"{endpoint}/objects/{cid}/by_attribute/{quote(str(attr_name))}/{attr_value}" else: request = f"{endpoint}{request_path}" @@ -124,7 +124,7 @@ def get_via_http_gate_by_attribute(cid: str, attribute: dict, endpoint: str, req if not resp.ok: raise Exception( - f"""Failed to get object via HTTP gate: + f"""Failed to get object via REST gate: request: {resp.request.path_url}, response: {resp.text}, status code: {resp.status_code} {resp.reason}""" @@ -139,8 +139,8 @@ def get_via_http_gate_by_attribute(cid: str, attribute: dict, endpoint: str, req return file_path -@allure.step("Upload via HTTP Gate") -def upload_via_http_gate( +@allure.step("Upload via REST Gate") +def upload_via_rest_gate( cid: str, path: str, endpoint: str, @@ -150,10 +150,10 @@ def upload_via_http_gate( error_pattern: Optional[str] = None, ) -> str: """ - This function upload given object through HTTP gate + This function upload given object through REST gate cid: CID to get object from path: File path to upload - endpoint: http gate endpoint + endpoint: REST gate endpoint headers: Object header file_content_type: Special Multipart Content-Type header """ @@ -171,7 +171,7 @@ def upload_via_http_gate( assert match, f"Expected {resp.text} to match {error_pattern}" return "" raise Exception( - f"""Failed to get object via HTTP gate: + f"""Failed to get object via REST gate: request: {resp.request.path_url}, response: {resp.text}, status code: {resp.status_code} {resp.reason}""" @@ -199,8 +199,8 @@ def is_object_large(filepath: str) -> bool: return False -@allure.step("Upload via HTTP Gate using Curl") -def upload_via_http_gate_curl( +@allure.step("Upload via REST Gate using Curl") +def upload_via_rest_gate_curl( cid: str, filepath: str, endpoint: str, @@ -208,11 +208,11 @@ def upload_via_http_gate_curl( error_pattern: Optional[str] = None, ) -> str: """ - This function upload given object through HTTP gate using curl utility. + This function upload given object through REST gate using curl utility. cid: CID to get object from filepath: File path to upload headers: Object header - endpoint: http gate endpoint + endpoint: REST gate endpoint error_pattern: [optional] expected error message from the command """ request = f"{endpoint}/upload/{cid}" @@ -248,18 +248,28 @@ def _attach_allure_step(request: str, status_code: int, req_type="GET"): @allure.step("Try to get object and expect error") def try_to_get_object_and_expect_error(cid: str, oid: str, error_pattern: str, endpoint: str) -> None: try: - get_via_http_gate(cid=cid, oid=oid, endpoint=endpoint) + get_via_rest_gate(cid=cid, oid=oid, endpoint=endpoint) raise AssertionError(f"Expected error on getting object with cid: {cid}") except Exception as err: match = error_pattern.casefold() in str(err).casefold() assert match, f"Expected {err} to match {error_pattern}" -@allure.step("Verify object can be get using HTTP header attribute") -def get_object_by_attr_and_verify_hashes(oid: str, file_name: str, cid: str, attrs: dict, endpoint: str) -> None: - got_file_path_http = get_via_http_gate(cid=cid, oid=oid, endpoint=endpoint) - got_file_path_http_attr = get_via_http_gate_by_attribute(cid=cid, attribute=attrs, endpoint=endpoint) - assert_hashes_are_equal(file_name, got_file_path_http, got_file_path_http_attr) +@allure.step("Verify object can be get using REST header attribute") +def get_object_by_attr_and_verify_hashes( + oid: str, + file_name: str, + cid: str, + attrs: dict, + endpoint: str, + request_path: Optional[str] = None, + request_path_attr: Optional[str] = None, +) -> None: + got_file_path_rest = get_via_rest_gate(cid=cid, oid=oid, endpoint=endpoint, request_path=request_path) + got_file_path_rest_attr = get_via_rest_gate_by_attribute( + cid=cid, attribute=attrs, endpoint=endpoint, request_path=request_path_attr + ) + assert_hashes_are_equal(file_name, got_file_path_rest, got_file_path_rest_attr) def get_object_and_verify_hashes( @@ -285,7 +295,7 @@ def get_object_and_verify_hashes( else: random_node = random.choice(nodes) - object_getter = object_getter or get_via_http_gate + object_getter = object_getter or get_via_rest_gate got_file_path = get_object( wallet=wallet, @@ -294,16 +304,16 @@ def get_object_and_verify_hashes( shell=shell, endpoint=random_node.get_rpc_endpoint(), ) - got_file_path_http = object_getter(cid=cid, oid=oid, endpoint=endpoint) + got_file_path_rest = object_getter(cid=cid, oid=oid, endpoint=endpoint) - assert_hashes_are_equal(file_name, got_file_path, got_file_path_http) + assert_hashes_are_equal(file_name, got_file_path, got_file_path_rest) def assert_hashes_are_equal(orig_file_name: str, got_file_1: str, got_file_2: str) -> None: msg = "Expected hashes are equal for files {f1} and {f2}" - got_file_hash_http = get_file_hash(got_file_1) - assert get_file_hash(got_file_2) == got_file_hash_http, msg.format(f1=got_file_2, f2=got_file_1) - assert get_file_hash(orig_file_name) == got_file_hash_http, msg.format(f1=orig_file_name, f2=got_file_1) + got_file_hash_rest = get_file_hash(got_file_1) + assert get_file_hash(got_file_2) == got_file_hash_rest, msg.format(f1=got_file_2, f2=got_file_1) + assert get_file_hash(orig_file_name) == got_file_hash_rest, msg.format(f1=orig_file_name, f2=got_file_1) def attr_into_header(attrs: dict) -> dict: @@ -324,7 +334,7 @@ def attr_into_str_header(attrs: dict) -> list: return {f"X-Attribute-{k}": f"{v}" for k, v in attrs.items()} -@allure.step("Try to get object via http (pass http_request and optional attributes) and expect error") +@allure.step("Try to get object via REST gate (pass http_request and optional attributes) and expect error") def try_to_get_object_via_passed_request_and_expect_error( cid: str, oid: str, @@ -335,10 +345,92 @@ def try_to_get_object_via_passed_request_and_expect_error( ) -> None: try: if attrs is None: - get_via_http_gate(cid=cid, oid=oid, endpoint=endpoint, request_path=http_request_path) + get_via_rest_gate(cid=cid, oid=oid, endpoint=endpoint, request_path=http_request_path) else: - get_via_http_gate_by_attribute(cid=cid, attribute=attrs, endpoint=endpoint, request_path=http_request_path) + get_via_rest_gate_by_attribute(cid=cid, attribute=attrs, endpoint=endpoint, request_path=http_request_path) raise AssertionError(f"Expected error on getting object with cid: {cid}") except Exception as err: match = error_pattern.casefold() in str(err).casefold() assert match, f"Expected {err} to match {error_pattern}" + + +@allure.step("New Upload via REST Gate") +def new_upload_via_rest_gate( + cid: str, + path: str, + endpoint: str, + headers: dict = None, + cookies: dict = None, + file_content_type: str = None, + error_pattern: Optional[str] = None, +) -> str: + """ + This function upload given object through REST gate + cid: CID to get object from + path: File path to upload + endpoint: REST gate endpoint + headers: Object header + file_content_type: Content-Type header + """ + request = f"{endpoint}/objects/{cid}" + + with open(path, "rb") as file: + file_content = file.read() + + if headers is None: + headers = {} + + if file_content_type: + headers["Content-Type"] = file_content_type + + resp = requests.post(request, data=file_content, headers=headers, cookies=cookies) + + if not resp.ok: + if error_pattern: + match = error_pattern.casefold() in str(resp.text).casefold() + assert match, f"Expected {resp.text} to match {error_pattern}" + return "" + raise Exception( + f"""Failed to get object via REST gate: + request: {resp.request.path_url}, + response: {resp.text}, + status code: {resp.status_code} {resp.reason}""" + ) + + logger.info(f"Request: {request}") + _attach_allure_step(request, resp.json(), req_type="POST") + + assert resp.json().get("object_id"), f"OID found in response {resp}" + + return resp.json().get("object_id") + + +def new_attr_into_header(attrs: dict) -> dict: + json_string = json.dumps(attrs) + return {"X-Attributes": json_string} + + +@allure.step("Get epoch duration via REST Gate") +def get_epoch_duration_via_rest_gate(endpoint: str) -> int: + """ + This function gets network info from REST gate and extracts "epochDuration" from the response + endpoint: REST gate endpoint + """ + + request = f"{endpoint}/network-info" + + resp = requests.get(request, stream=True) + + if not resp.ok: + raise Exception( + f"""Failed to get network info via REST gate: + request: {resp.request.path_url}, + response: {resp.text}, + status code: {resp.status_code} {resp.reason}""" + ) + + logger.info(f"Request: {request}") + _attach_allure_step(request, resp.status_code) + + epoch_duration = resp.json().get("epochDuration") + return epoch_duration diff --git a/pytest_tests/lib/http_gw/http_utils.py b/pytest_tests/lib/rest_gw/rest_utils.py similarity index 89% rename from pytest_tests/lib/http_gw/http_utils.py rename to pytest_tests/lib/rest_gw/rest_utils.py index 5099920b6..e901137ef 100644 --- a/pytest_tests/lib/http_gw/http_utils.py +++ b/pytest_tests/lib/rest_gw/rest_utils.py @@ -2,7 +2,7 @@ from neofs_testlib.env.env import StorageNode from neofs_testlib.shell import Shell -from helpers.http_gate import assert_hashes_are_equal, get_via_http_gate +from helpers.rest_gate import assert_hashes_are_equal, get_via_rest_gate from helpers.neofs_verbs import get_object from helpers.complex_object_actions import get_nodes_without_object @@ -30,7 +30,7 @@ def get_object_and_verify_hashes( else: random_node = random.choice(nodes) - object_getter = object_getter or get_via_http_gate + object_getter = object_getter or get_via_rest_gate got_file_path = get_object( wallet=wallet, diff --git a/pytest_tests/tests/services/rest_gate/test_rest_bearer.py b/pytest_tests/tests/services/rest_gate/test_rest_bearer.py index 2bd53135e..13a00e887 100644 --- a/pytest_tests/tests/services/rest_gate/test_rest_bearer.py +++ b/pytest_tests/tests/services/rest_gate/test_rest_bearer.py @@ -16,9 +16,9 @@ ) from helpers.container import create_container from helpers.file_helper import generate_file -from helpers.http_gate import upload_via_http_gate +from helpers.rest_gate import upload_via_rest_gate from helpers.wellknown_acl import PUBLIC_ACL -from http_gw.http_utils import get_object_and_verify_hashes +from rest_gw.rest_utils import get_object_and_verify_hashes from neofs_env.neofs_env_test_base import NeofsEnvTestBase logger = logging.getLogger("NeoLogger") @@ -83,7 +83,7 @@ def test_unable_put_without_bearer_token( self, simple_object_size: int, user_container: str, eacl_deny_for_others, gw_endpoint ): eacl_deny_for_others - upload_via_http_gate( + upload_via_rest_gate( cid=user_container, path=generate_file(simple_object_size), endpoint=gw_endpoint, @@ -116,7 +116,7 @@ def test_put_with_bearer_when_eacl_restrict( if bearer_type == "cookie": cookies = {"Bearer": bearer} - oid = upload_via_http_gate( + oid = upload_via_rest_gate( cid=user_container, path=file_path, endpoint=gw_endpoint, diff --git a/pytest_tests/tests/services/rest_gate/test_rest_gate.py b/pytest_tests/tests/services/rest_gate/test_rest_gate.py index bf53fe24f..6c5a5693b 100644 --- a/pytest_tests/tests/services/rest_gate/test_rest_gate.py +++ b/pytest_tests/tests/services/rest_gate/test_rest_gate.py @@ -7,17 +7,18 @@ import pytest from helpers.container import create_container from helpers.file_helper import generate_file, generate_file_with_content -from helpers.http_gate import ( +from helpers.rest_gate import ( attr_into_header, get_object_by_attr_and_verify_hashes, - get_via_http_gate, + get_via_rest_gate, try_to_get_object_and_expect_error, - upload_via_http_gate, + upload_via_rest_gate, + quote, ) from helpers.neofs_verbs import put_object_to_random_node from helpers.utility import wait_for_gc_pass_on_storage_nodes from helpers.wellknown_acl import PUBLIC_ACL -from http_gw.http_utils import get_object_and_verify_hashes +from rest_gw.rest_utils import get_object_and_verify_hashes from neofs_env.neofs_env_test_base import NeofsEnvTestBase logger = logging.getLogger("NeoLogger") @@ -111,15 +112,16 @@ def test_put_http_get_http_content_disposition(self, simple_object_size, gw_para with allure.step("Verify Content-Disposition"): file_path = generate_file(simple_object_size) - oid = upload_via_http_gate( + oid = upload_via_rest_gate( cid=cid, path=file_path, endpoint=gw_params["endpoint"], ) - resp = get_via_http_gate( + resp = get_via_rest_gate( cid=cid, oid=oid, endpoint=gw_params["endpoint"], + request_path=f"/get/{cid}/{oid}", return_response=True, ) content_disposition_type, filename = resp.headers["Content-Disposition"].split(";") @@ -129,15 +131,16 @@ def test_put_http_get_http_content_disposition(self, simple_object_size, gw_para with allure.step("Verify Content-Disposition with download=true"): file_path = generate_file(simple_object_size) - oid = upload_via_http_gate( + oid = upload_via_rest_gate( cid=cid, path=file_path, endpoint=gw_params["endpoint"], ) - resp = get_via_http_gate( + resp = get_via_rest_gate( cid=cid, oid=oid, endpoint=gw_params["endpoint"], + request_path=f"/get/{cid}/{oid}", return_response=True, download=True, ) @@ -158,16 +161,17 @@ def test_put_http_get_http_without_content_type(self, simple_object_size, gw_par with allure.step("Upload binary object"): file_path = generate_file(simple_object_size) - oid = upload_via_http_gate( + oid = upload_via_rest_gate( cid=cid, path=file_path, endpoint=gw_params["endpoint"], ) - resp = get_via_http_gate( + resp = get_via_rest_gate( cid=cid, oid=oid, endpoint=gw_params["endpoint"], + request_path=f"/get/{cid}/{oid}", return_response=True, ) assert resp.headers["Content-Type"] == "application/octet-stream" @@ -175,16 +179,17 @@ def test_put_http_get_http_without_content_type(self, simple_object_size, gw_par with allure.step("Upload text object"): file_path = generate_file_with_content(simple_object_size, content="123") - oid = upload_via_http_gate( + oid = upload_via_rest_gate( cid=cid, path=file_path, endpoint=gw_params["endpoint"], ) - resp = get_via_http_gate( + resp = get_via_rest_gate( cid=cid, oid=oid, endpoint=gw_params["endpoint"], + request_path=f"/get/{cid}/{oid}", return_response=True, ) assert resp.headers["Content-Type"] == "text/plain; charset=utf-8" @@ -203,23 +208,24 @@ def test_put_http_get_http_with_x_atribute_content_type(self, simple_object_size file_path = generate_file(simple_object_size) headers = {"X-Attribute-Content-Type": "CoolContentType"} - oid = upload_via_http_gate( + oid = upload_via_rest_gate( cid=cid, path=file_path, headers=headers, endpoint=gw_params["endpoint"], ) - resp = get_via_http_gate( + resp = get_via_rest_gate( cid=cid, oid=oid, endpoint=gw_params["endpoint"], + request_path=f"/get/{cid}/{oid}", return_response=True, ) assert resp.headers["Content-Type"] == "CoolContentType" - @allure.title("Verify Content-Type if uploaded with multipart Content-Type") - def test_put_http_get_http_with_multipart_content_type(self, gw_params): + @allure.title("Verify Content-Type if uploaded with Content-Type") + def test_put_http_get_http_with_content_type(self, gw_params): cid = create_container( self.wallet.path, shell=self.shell, @@ -228,20 +234,21 @@ def test_put_http_get_http_with_multipart_content_type(self, gw_params): basic_acl=PUBLIC_ACL, ) - with allure.step("Upload object with multipart content type"): + with allure.step("Upload object with content type"): file_path = generate_file_with_content(0, content="123") - oid = upload_via_http_gate( + oid = upload_via_rest_gate( cid=cid, path=file_path, endpoint=gw_params["endpoint"], file_content_type="application/json", ) - resp = get_via_http_gate( + resp = get_via_rest_gate( cid=cid, oid=oid, endpoint=gw_params["endpoint"], + request_path=f"/get/{cid}/{oid}", return_response=True, ) assert resp.headers["Content-Type"] == "application/json" @@ -258,15 +265,16 @@ def test_put_http_get_http_special_attributes(self, simple_object_size, gw_param file_path = generate_file(simple_object_size) - oid = upload_via_http_gate( + oid = upload_via_rest_gate( cid=cid, path=file_path, endpoint=gw_params["endpoint"], ) - resp = get_via_http_gate( + resp = get_via_rest_gate( cid=cid, oid=oid, endpoint=gw_params["endpoint"], + request_path=f"/get/{cid}/{oid}", return_response=True, ) with open(gw_params["wallet_path"]) as wallet_file: @@ -303,8 +311,8 @@ def test_put_http_get_http(self, complex_object_size, simple_object_size, gw_par file_path_simple, file_path_large = generate_file(simple_object_size), generate_file(complex_object_size) with allure.step("Put objects using HTTP"): - oid_simple = upload_via_http_gate(cid=cid, path=file_path_simple, endpoint=gw_params["endpoint"]) - oid_large = upload_via_http_gate(cid=cid, path=file_path_large, endpoint=gw_params["endpoint"]) + oid_simple = upload_via_rest_gate(cid=cid, path=file_path_simple, endpoint=gw_params["endpoint"]) + oid_large = upload_via_rest_gate(cid=cid, path=file_path_large, endpoint=gw_params["endpoint"]) for oid, file_path in ((oid_simple, file_path_simple), (oid_large, file_path_large)): get_object_and_verify_hashes( @@ -359,19 +367,23 @@ def test_put_http_get_http_with_headers(self, attributes: dict, simple_object_si with allure.step("Put objects using HTTP with attribute"): headers = attr_into_header(attributes) - oid = upload_via_http_gate( + oid = upload_via_rest_gate( cid=cid, path=file_path, headers=headers, endpoint=gw_params["endpoint"], ) + attr_name = list(attributes.keys())[0] + attr_value = quote(str(attributes.get(attr_name))) get_object_by_attr_and_verify_hashes( oid=oid, file_name=file_path, cid=cid, attrs=attributes, endpoint=gw_params["endpoint"], + request_path=f"/get/{cid}/{oid}", + request_path_attr=f"/get_by_attribute/{cid}/{quote(str(attr_name))}/{attr_value}", ) @allure.title("Test Expiration-Epoch in HTTP header") @@ -396,14 +408,19 @@ def test_expiration_epoch_in_http(self, simple_object_size, gw_params): with allure.step("Put objects using HTTP with attribute Expiration-Epoch"): oids.append( - upload_via_http_gate(cid=cid, path=file_path, headers=headers, endpoint=gw_params["endpoint"]) + upload_via_rest_gate(cid=cid, path=file_path, headers=headers, endpoint=gw_params["endpoint"]) ) assert len(oids) == len(epochs), "Expected all objects have been put successfully" with allure.step("All objects can be get"): for oid in oids: - get_via_http_gate(cid=cid, oid=oid, endpoint=gw_params["endpoint"]) + get_via_rest_gate( + cid=cid, + oid=oid, + endpoint=gw_params["endpoint"], + request_path=f"/get/{cid}/{oid}", + ) for expired_objects, not_expired_objects in [(oids[:1], oids[1:]), (oids[:2], oids[2:])]: self.tick_epochs_and_wait(1) @@ -421,7 +438,9 @@ def test_expiration_epoch_in_http(self, simple_object_size, gw_params): with allure.step("Other objects can be get"): for oid in not_expired_objects: - get_via_http_gate(cid=cid, oid=oid, endpoint=gw_params["endpoint"]) + get_via_rest_gate( + cid=cid, oid=oid, endpoint=gw_params["endpoint"], request_path=f"/get/{cid}/{oid}" + ) @pytest.mark.long @allure.title("Test Put over HTTP, Get over HTTP for large object") @@ -442,7 +461,7 @@ def test_put_http_get_http_large_file(self, complex_object_size, gw_params): file_path = generate_file(obj_size) with allure.step("Put objects using HTTP"): - oid_gate = upload_via_http_gate(cid=cid, path=file_path, endpoint=gw_params["endpoint"]) + oid_gate = upload_via_rest_gate(cid=cid, path=file_path, endpoint=gw_params["endpoint"]) get_object_and_verify_hashes( oid=oid_gate, diff --git a/pytest_tests/tests/services/rest_gate/test_rest_gate_new.py b/pytest_tests/tests/services/rest_gate/test_rest_gate_new.py new file mode 100644 index 000000000..47b6d4de7 --- /dev/null +++ b/pytest_tests/tests/services/rest_gate/test_rest_gate_new.py @@ -0,0 +1,524 @@ +import json +import logging +import os +import time + +import allure +import neofs_env.neofs_epoch as neofs_epoch +import pytest +from helpers.container import create_container +from helpers.file_helper import generate_file, generate_file_with_content +from helpers.rest_gate import ( + new_attr_into_header, + get_object_by_attr_and_verify_hashes, + get_via_rest_gate, + try_to_get_object_and_expect_error, + new_upload_via_rest_gate, + get_epoch_duration_via_rest_gate, +) +from helpers.neofs_verbs import put_object_to_random_node +from helpers.utility import wait_for_gc_pass_on_storage_nodes +from helpers.wellknown_acl import PUBLIC_ACL +from rest_gw.rest_utils import get_object_and_verify_hashes +from neofs_env.neofs_env_test_base import NeofsEnvTestBase + +logger = logging.getLogger("NeoLogger") +OBJECT_NOT_FOUND_ERROR = "not found" + + +@allure.link( + "https://github.com/nspcc-dev/neofs-rest-gw?tab=readme-ov-file#neofs-rest-gw", + name="neofs-rest-gateway", +) +@pytest.mark.sanity +@pytest.mark.rest_gate +class TestRestGate(NeofsEnvTestBase): + PLACEMENT_RULE_1 = "REP 1 IN X CBF 1 SELECT 1 FROM * AS X" + PLACEMENT_RULE_2 = "REP 2 IN X CBF 2 SELECT 2 FROM * AS X" + + @pytest.fixture(scope="class", autouse=True) + @allure.title("[Class/Autouse]: Prepare wallet and deposit") + def prepare_wallet(self, default_wallet): + TestRestGate.wallet = default_wallet + + @pytest.fixture(scope="class") + def gw_params(self): + return { + "endpoint": f"http://{self.neofs_env.rest_gw.address}/v1", + "wallet_path": self.neofs_env.rest_gw.wallet.path, + } + + @allure.title("Test Put over gRPC, Get over REST") + def test_put_grpc_get_rest(self, complex_object_size, simple_object_size, gw_endpoint): + """ + Test that object can be put using gRPC interface and get using REST gateway. + + Steps: + 1. Create simple and large objects. + 2. Put objects using gRPC (neofs-cli). + 3. Download objects using REST gate (https://github.com/nspcc-dev/neofs-rest-gw). + 4. Get objects using gRPC (neofs-cli). + 5. Compare hashes for got objects. + 6. Compare hashes for got and original objects. + + Expected result: + Hashes must be the same. + """ + cid = create_container( + self.wallet.path, + shell=self.shell, + endpoint=self.neofs_env.sn_rpc, + rule=self.PLACEMENT_RULE_1, + basic_acl=PUBLIC_ACL, + ) + file_path_simple, file_path_large = generate_file(simple_object_size), generate_file(complex_object_size) + + with allure.step("Put objects using gRPC"): + oid_simple = put_object_to_random_node( + wallet=self.wallet.path, + path=file_path_simple, + cid=cid, + shell=self.shell, + neofs_env=self.neofs_env, + ) + oid_large = put_object_to_random_node( + wallet=self.wallet.path, + path=file_path_large, + cid=cid, + shell=self.shell, + neofs_env=self.neofs_env, + ) + + for oid, file_path in ((oid_simple, file_path_simple), (oid_large, file_path_large)): + get_object_and_verify_hashes( + oid=oid, + file_name=file_path, + wallet=self.wallet.path, + cid=cid, + shell=self.shell, + nodes=self.neofs_env.storage_nodes, + endpoint=gw_endpoint, + ) + + @allure.title("Verify Content-Disposition header") + def test_put_http_get_http_content_disposition(self, simple_object_size, gw_params): + cid = create_container( + self.wallet.path, + shell=self.shell, + endpoint=self.neofs_env.sn_rpc, + rule=self.PLACEMENT_RULE_2, + basic_acl=PUBLIC_ACL, + ) + + with allure.step("Verify Content-Disposition"): + file_path = generate_file(simple_object_size) + + oid = new_upload_via_rest_gate( + cid=cid, + path=file_path, + endpoint=gw_params["endpoint"], + ) + resp = get_via_rest_gate( + cid=cid, + oid=oid, + endpoint=gw_params["endpoint"], + return_response=True, + ) + + assert "inline" in resp.headers["Content-Disposition"] + + with allure.step("Verify Content-Disposition with download=true"): + file_path = generate_file(simple_object_size) + + oid = new_upload_via_rest_gate( + cid=cid, + path=file_path, + endpoint=gw_params["endpoint"], + ) + resp = get_via_rest_gate( + cid=cid, + oid=oid, + endpoint=gw_params["endpoint"], + return_response=True, + download=True, + ) + + assert "attachment" in resp.headers["Content-Disposition"] + + @allure.title("Verify Content-Type if uploaded without any Content-Type specified") + def test_put_http_get_http_without_content_type(self, simple_object_size, gw_params): + cid = create_container( + self.wallet.path, + shell=self.shell, + endpoint=self.neofs_env.sn_rpc, + rule=self.PLACEMENT_RULE_2, + basic_acl=PUBLIC_ACL, + ) + + with allure.step("Upload binary object"): + file_path = generate_file(simple_object_size) + + oid = new_upload_via_rest_gate( + cid=cid, + path=file_path, + endpoint=gw_params["endpoint"], + ) + + resp = get_via_rest_gate( + cid=cid, + oid=oid, + endpoint=gw_params["endpoint"], + return_response=True, + ) + assert resp.headers["Content-Type"] == "application/octet-stream" + + with allure.step("Upload text object"): + file_path = generate_file_with_content(simple_object_size, content="123") + + oid = new_upload_via_rest_gate( + cid=cid, + path=file_path, + endpoint=gw_params["endpoint"], + ) + + resp = get_via_rest_gate( + cid=cid, + oid=oid, + endpoint=gw_params["endpoint"], + return_response=True, + ) + assert resp.headers["Content-Type"] == "text/plain; charset=utf-8" + + @allure.title("Verify Content-Type if uploaded with X-Attribute-Content-Type") + def test_put_http_get_http_with_x_atribute_content_type(self, simple_object_size, gw_params): + cid = create_container( + self.wallet.path, + shell=self.shell, + endpoint=self.neofs_env.sn_rpc, + rule=self.PLACEMENT_RULE_2, + basic_acl=PUBLIC_ACL, + ) + + with allure.step("Upload object with X-Attribute-Content-Type"): + file_path = generate_file(simple_object_size) + + headers = {"X-Attributes": '{"Content-Type":"CoolContentType"}'} + oid = new_upload_via_rest_gate( + cid=cid, + path=file_path, + headers=headers, + endpoint=gw_params["endpoint"], + ) + + resp = get_via_rest_gate( + cid=cid, + oid=oid, + endpoint=gw_params["endpoint"], + return_response=True, + ) + assert resp.headers["Content-Type"] == "CoolContentType" + + @allure.title("Verify Content-Type if uploaded with Content-Type") + def test_put_http_get_http_with_content_type(self, gw_params): + cid = create_container( + self.wallet.path, + shell=self.shell, + endpoint=self.neofs_env.sn_rpc, + rule=self.PLACEMENT_RULE_2, + basic_acl=PUBLIC_ACL, + ) + + with allure.step("Upload object with content type"): + file_path = generate_file_with_content(0, content="123") + + oid = new_upload_via_rest_gate( + cid=cid, + path=file_path, + endpoint=gw_params["endpoint"], + file_content_type="application/json", + ) + + resp = get_via_rest_gate( + cid=cid, + oid=oid, + endpoint=gw_params["endpoint"], + return_response=True, + ) + assert resp.headers["Content-Type"] == "application/json" + + @allure.title("Verify special HTTP headers") + def test_put_http_get_http_special_attributes(self, simple_object_size, gw_params): + cid = create_container( + self.wallet.path, + shell=self.shell, + endpoint=self.neofs_env.sn_rpc, + rule=self.PLACEMENT_RULE_2, + basic_acl=PUBLIC_ACL, + ) + + file_path = generate_file(simple_object_size) + + oid = new_upload_via_rest_gate( + cid=cid, + path=file_path, + endpoint=gw_params["endpoint"], + ) + resp = get_via_rest_gate( + cid=cid, + oid=oid, + endpoint=gw_params["endpoint"], + return_response=True, + ) + with open(gw_params["wallet_path"]) as wallet_file: + wallet_json = json.load(wallet_file) + + assert resp.headers["X-Owner-Id"] == wallet_json["accounts"][0]["address"] + assert resp.headers["X-Object-Id"] == oid + assert resp.headers["X-Container-Id"] == cid + + @allure.link("https://github.com/nspcc-dev/neofs-rest-gw#docs", name="docs") + @allure.title("Test Put over REST, Get over REST") + @pytest.mark.smoke + def test_put_rest_get_rest(self, complex_object_size, simple_object_size, gw_params): + """ + Test that object can be put and get using REST interface. + + Steps: + 1. Create simple and large objects. + 2. Upload objects using REST (https://github.com/nspcc-dev/neofs-rest-gw#docs). + 3. Download objects using REST gate (https://github.com/nspcc-dev/neofs-rest-gw#docs). + 4. Compare hashes for got and original objects. + + Expected result: + Hashes must be the same. + """ + cid = create_container( + self.wallet.path, + shell=self.shell, + endpoint=self.neofs_env.sn_rpc, + rule=self.PLACEMENT_RULE_2, + basic_acl=PUBLIC_ACL, + ) + file_path_simple, file_path_large = generate_file(simple_object_size), generate_file(complex_object_size) + + with allure.step("Put objects using REST"): + oid_simple = new_upload_via_rest_gate(cid=cid, path=file_path_simple, endpoint=gw_params["endpoint"]) + oid_large = new_upload_via_rest_gate(cid=cid, path=file_path_large, endpoint=gw_params["endpoint"]) + + for oid, file_path in ((oid_simple, file_path_simple), (oid_large, file_path_large)): + get_object_and_verify_hashes( + oid=oid, + file_name=file_path, + wallet=self.wallet.path, + cid=cid, + shell=self.shell, + nodes=self.neofs_env.storage_nodes, + endpoint=gw_params["endpoint"], + ) + + @allure.link("https://github.com/nspcc-dev/neofs-rest-gw#docs", name="docs") + @allure.title("Test Put over REST, Get over REST with headers") + @pytest.mark.parametrize( + "attributes", + [ + {"File-Name": "simple obj filename"}, + {"FileName": "simple obj filename"}, + {"Filename": "simple_obj_filename"}, + pytest.param( + {"cat%jpeg": "cat%jpeg"}, + marks=pytest.mark.skip(reason="https://github.com/nspcc-dev/neofs-rest-gw/issues/195"), + ), + {"Filename": "simple_obj_filename\n"}, + {"Filename\n": "simple_obj_filename"}, + {"\n": "\n"}, + { + """ + key + """: """ + value + """ + }, + {"\x00" * 8: "simple_obj_filename"}, + {"Some key": "\x00" * 8}, + ], + ids=[ + "simple", + "hyphen", + "special", + "percent", + "linebreak in value", + "linebreak in key", + "linebreaks in key and value", + "other linebreaks in key and value", + "zero bytes key", + "zero bytes value", + ], + ) + def test_put_rest_get_rest_with_headers(self, attributes: dict, simple_object_size, gw_params): + """ + Test that object can be downloaded using different attributes in HTTP header. + + Steps: + 1. Create simple and large objects. + 2. Upload objects using REST gate with particular attributes in the header. + 3. Download objects by attributes using REST gate (https://github.com/nspcc-dev/neofs-rest-gw#docs). + 4. Compare hashes for got and original objects. + + Expected result: + Hashes must be the same. + """ + cid = create_container( + self.wallet.path, + shell=self.shell, + endpoint=self.neofs_env.sn_rpc, + rule=self.PLACEMENT_RULE_2, + basic_acl=PUBLIC_ACL, + ) + file_path = generate_file(simple_object_size) + + with allure.step("Put objects using REST with attribute"): + headers = new_attr_into_header(attributes) + oid = new_upload_via_rest_gate( + cid=cid, + path=file_path, + headers=headers, + endpoint=gw_params["endpoint"], + ) + + get_object_by_attr_and_verify_hashes( + oid=oid, + file_name=file_path, + cid=cid, + attrs=attributes, + endpoint=gw_params["endpoint"], + ) + + @allure.title("Test Expiration-Epoch in HTTP header") + def test_expiration_epoch_in_rest(self, simple_object_size, gw_params): + endpoint = self.neofs_env.sn_rpc + + cid = create_container( + self.wallet.path, + shell=self.shell, + endpoint=endpoint, + rule=self.PLACEMENT_RULE_2, + basic_acl=PUBLIC_ACL, + ) + file_path = generate_file(simple_object_size) + oids = [] + + curr_epoch = neofs_epoch.get_epoch(self.neofs_env) + epochs = (curr_epoch, curr_epoch + 1, curr_epoch + 2, curr_epoch + 100) + + for epoch in epochs: + headers = {"X-Attributes": '{"__NEOFS__EXPIRATION_EPOCH": "' + str(epoch) + '"}'} + + with allure.step("Put objects using REST with attribute Expiration-Epoch"): + oids.append( + new_upload_via_rest_gate(cid=cid, path=file_path, headers=headers, endpoint=gw_params["endpoint"]) + ) + + assert len(oids) == len(epochs), "Expected all objects have been put successfully" + + with allure.step("All objects can be get"): + for oid in oids: + get_via_rest_gate(cid=cid, oid=oid, endpoint=gw_params["endpoint"]) + + for expired_objects, not_expired_objects in [(oids[:1], oids[1:]), (oids[:2], oids[2:])]: + self.tick_epochs_and_wait(1) + + # Wait for GC, because object with expiration is counted as alive until GC removes it + wait_for_gc_pass_on_storage_nodes() + + for oid in expired_objects: + try_to_get_object_and_expect_error( + cid=cid, + oid=oid, + error_pattern=OBJECT_NOT_FOUND_ERROR, + endpoint=gw_params["endpoint"], + ) + + with allure.step("Other objects can be get"): + for oid in not_expired_objects: + get_via_rest_gate(cid=cid, oid=oid, endpoint=gw_params["endpoint"]) + + @allure.title("Test other Expiration-Epoch settings in HTTP header") + def test_expiration_headers_in_rest(self, simple_object_size, gw_params): + endpoint = self.neofs_env.sn_rpc + + cid = create_container( + self.wallet.path, + shell=self.shell, + endpoint=endpoint, + rule=self.PLACEMENT_RULE_2, + basic_acl=PUBLIC_ACL, + ) + file_path = generate_file(simple_object_size) + oids = [] + + epoch_duration = get_epoch_duration_via_rest_gate(endpoint=gw_params["endpoint"]) + current_ts = int(time.time()) + expiration_ts = current_ts + epoch_duration + expiration_rfc3339 = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(expiration_ts)) + + headers = ( + {"X-Neofs-Expiration-Timestamp": str(expiration_ts)}, + {"X-Neofs-Expiration-Duration": str(epoch_duration) + "s"}, + {"X-Neofs-Expiration-RFC3339": expiration_rfc3339}, + ) + + for header in headers: + keys_list = ",".join(header.keys()) + with allure.step("Put objects using REST with attributes: " + keys_list): + oids.append( + new_upload_via_rest_gate(cid=cid, path=file_path, headers=header, endpoint=gw_params["endpoint"]) + ) + + assert len(oids) == len(headers), "Expected all objects have been put successfully" + + with allure.step("All objects can be get"): + for oid in oids: + get_via_rest_gate(cid=cid, oid=oid, endpoint=gw_params["endpoint"]) + + # Wait 2 epochs because REST gate rounds time to the next epoch. + self.tick_epochs_and_wait(2) + wait_for_gc_pass_on_storage_nodes() + + for oid in oids: + try_to_get_object_and_expect_error( + cid=cid, + oid=oid, + error_pattern=OBJECT_NOT_FOUND_ERROR, + endpoint=gw_params["endpoint"], + ) + + @pytest.mark.long + @allure.title("Test Put over REST, Get over REST for large object") + def test_put_rest_get_rest_large_file(self, complex_object_size, gw_params): + """ + This test checks upload and download with 'large' object. + Large is object with size up to 20Mb. + """ + cid = create_container( + self.wallet.path, + shell=self.shell, + endpoint=self.neofs_env.sn_rpc, + rule=self.PLACEMENT_RULE_2, + basic_acl=PUBLIC_ACL, + ) + + obj_size = int(os.getenv("BIG_OBJ_SIZE", complex_object_size)) + file_path = generate_file(obj_size) + + with allure.step("Put objects using REST"): + oid_gate = new_upload_via_rest_gate(cid=cid, path=file_path, endpoint=gw_params["endpoint"]) + + get_object_and_verify_hashes( + oid=oid_gate, + file_name=file_path, + wallet=self.wallet.path, + cid=cid, + shell=self.shell, + nodes=self.neofs_env.storage_nodes, + endpoint=gw_params["endpoint"], + ) diff --git a/pytest_tests/tests/services/rest_gate/test_rest_headers.py b/pytest_tests/tests/services/rest_gate/test_rest_headers.py index f4e4c193d..8dc57356b 100644 --- a/pytest_tests/tests/services/rest_gate/test_rest_headers.py +++ b/pytest_tests/tests/services/rest_gate/test_rest_headers.py @@ -10,14 +10,14 @@ wait_for_container_deletion, ) from helpers.file_helper import generate_file -from helpers.http_gate import ( +from helpers.rest_gate import ( attr_into_str_header, attr_into_str_header_curl, get_object_by_attr_and_verify_hashes, try_to_get_object_and_expect_error, try_to_get_object_via_passed_request_and_expect_error, - upload_via_http_gate, - upload_via_http_gate_curl, + upload_via_rest_gate, + upload_via_rest_gate_curl, ) from helpers.neofs_verbs import delete_object from helpers.storage_object_info import StorageObjectInfo @@ -65,7 +65,7 @@ def storage_objects_with_attributes(self, request: FixtureRequest, gw_endpoint) ) file_path = generate_file(request.param) for attributes in self.OBJECT_ATTRIBUTES: - storage_object_id = upload_via_http_gate( + storage_object_id = upload_via_rest_gate( cid=cid, path=file_path, endpoint=gw_endpoint, @@ -182,7 +182,7 @@ def test_negative_put_and_get_object3(self, simple_object_size, gw_endpoint): basic_acl=PUBLIC_ACL, ) file_path = generate_file(simple_object_size) - upload_via_http_gate( + upload_via_rest_gate( cid=cid, path=file_path, endpoint=gw_endpoint, @@ -197,7 +197,7 @@ def test_negative_put_and_get_object3(self, simple_object_size, gw_endpoint): headers = attr_into_str_header_curl(attrs_obj3) headers.append(" ".join(attr_into_str_header_curl({"Writer": "peace"}))) error_pattern = "key duplication error: X-Attribute-Writer" - upload_via_http_gate_curl( + upload_via_rest_gate_curl( cid=cid, filepath=file_path_3, endpoint=gw_endpoint, diff --git a/pytest_tests/tests/services/rest_gate/test_rest_object.py b/pytest_tests/tests/services/rest_gate/test_rest_object.py index d867b45b1..2adfb9a1c 100644 --- a/pytest_tests/tests/services/rest_gate/test_rest_object.py +++ b/pytest_tests/tests/services/rest_gate/test_rest_object.py @@ -5,13 +5,14 @@ import pytest from helpers.container import create_container from helpers.file_helper import generate_file -from helpers.http_gate import ( +from helpers.rest_gate import ( get_object_by_attr_and_verify_hashes, try_to_get_object_via_passed_request_and_expect_error, + quote, ) from helpers.neofs_verbs import put_object_to_random_node from helpers.wellknown_acl import PUBLIC_ACL -from http_gw.http_utils import get_object_and_verify_hashes +from rest_gw.rest_utils import get_object_and_verify_hashes from neofs_env.neofs_env_test_base import NeofsEnvTestBase logger = logging.getLogger("NeoLogger") @@ -107,7 +108,7 @@ def test_object_put_get_attributes(self, object_size: int, gw_attributes): with allure.step("[Negative] try to get object: [get/$CID/chapter1/peace]"): attrs = {obj_key1: obj_value1, obj_key2: obj_value2} request = f"/get/{cid}/{obj_key1}/{obj_value1}" - expected_err_msg = "Failed to get object via HTTP gate:" + expected_err_msg = "Failed to get object via REST gate:" try_to_get_object_via_passed_request_and_expect_error( cid=cid, oid=oid, @@ -118,12 +119,16 @@ def test_object_put_get_attributes(self, object_size: int, gw_attributes): ) with allure.step("Download the object with attribute [get_by_attribute/$CID/chapter1/peace]"): + attr_name = list(attrs.keys())[0] + attr_value = quote(str(attrs.get(attr_name))) get_object_by_attr_and_verify_hashes( oid=oid, file_name=file_path, cid=cid, attrs=attrs, endpoint=gw_attributes["endpoint"], + request_path=f"/get/{cid}/{oid}", + request_path_attr=f"/get_by_attribute/{cid}/{quote(str(attr_name))}/{attr_value}", ) with allure.step("[Negative] try to get object: get_by_attribute/$CID/$OID"): request = f"/get_by_attribute/{cid}/{oid}" @@ -136,11 +141,16 @@ def test_object_put_get_attributes(self, object_size: int, gw_attributes): ) with allure.step("[Negative] Try to get object with invalid attribute [get_by_attribute/$CID/chapter1/war]"): + attrs = {obj_key1: obj_value2} + attr_name = list(attrs.keys())[0] + attr_value = quote(str(attrs.get(attr_name))) with pytest.raises(Exception, match=".*object not found.*"): get_object_by_attr_and_verify_hashes( oid=oid, file_name=file_path, cid=cid, - attrs={obj_key1: obj_value2}, + attrs=attrs, endpoint=gw_attributes["endpoint"], + request_path=f"/get/{cid}/{oid}", + request_path_attr=f"/get_by_attribute/{cid}/{quote(str(attr_name))}/{attr_value}", ) diff --git a/pytest_tests/tests/services/rest_gate/test_rest_object_new.py b/pytest_tests/tests/services/rest_gate/test_rest_object_new.py new file mode 100644 index 000000000..f2c474957 --- /dev/null +++ b/pytest_tests/tests/services/rest_gate/test_rest_object_new.py @@ -0,0 +1,148 @@ +import logging +import os + +import allure +import pytest +from helpers.container import create_container +from helpers.file_helper import generate_file +from helpers.rest_gate import ( + get_object_by_attr_and_verify_hashes, + try_to_get_object_via_passed_request_and_expect_error, +) +from helpers.neofs_verbs import put_object_to_random_node +from helpers.wellknown_acl import PUBLIC_ACL +from rest_gw.rest_utils import get_object_and_verify_hashes +from neofs_env.neofs_env_test_base import NeofsEnvTestBase + +logger = logging.getLogger("NeoLogger") + + +@pytest.mark.sanity +@pytest.mark.rest_gate +class Test_rest_object(NeofsEnvTestBase): + PLACEMENT_RULE = "REP 2 IN X CBF 1 SELECT 4 FROM * AS X" + + @pytest.fixture(scope="class", autouse=True) + @allure.title("[Class/Autouse]: Prepare wallet and deposit") + def prepare_wallet(self, default_wallet): + Test_rest_object.wallet = default_wallet + + @pytest.fixture(scope="class") + def gw_attributes(self): + return { + "endpoint": f"http://{self.neofs_env.rest_gw.address}/v1", + # List of Key=Value attributes + # REST gateway accepts attributes in the Canonical MIME Header Key. + "obj_key1": "Chapter1", + "obj_value1": "peace", + "obj_key2": "Chapter2", + "obj_value2": "war", + } + + @allure.title("Test Put over gRPC, Get over HTTP") + @pytest.mark.parametrize( + "object_size", + [pytest.lazy_fixture("simple_object_size"), pytest.lazy_fixture("complex_object_size")], + ids=["simple object", "complex object"], + ) + def test_object_put_get_attributes(self, object_size: int, gw_attributes): + """ + Test that object can be put using gRPC interface and get using HTTP. + + Steps: + 1. Create object; + 2. Put objects using gRPC (neofs-cli) with attributes [--attributes chapter1=peace,chapter2=war]; + 3. Download object using HTTP gate (https://github.com/nspcc-dev/neofs-http-gw#downloading); + 4. Compare hashes between original and downloaded object; + 5. [Negative] Try to the get object with specified attributes and `by_id` request: [objects/$CID/by_id/chapter1/peace]; + 6. Download the object with specified attributes and `by_attribute` request: [objects/$CID/by_attribute/chapter1/peace]; + 7. Compare hashes between original and downloaded object; + 8. [Negative] Try to the get object via `by_attribute` request: [objects/$CID/by_attribute/$OID]; + + + Expected result: + Hashes must be the same. + """ + with allure.step("Create public container"): + cid = create_container( + self.wallet.path, + shell=self.shell, + endpoint=self.neofs_env.sn_rpc, + rule=self.PLACEMENT_RULE, + basic_acl=PUBLIC_ACL, + ) + + # Generate file + file_path = generate_file(object_size) + + # List of Key=Value attributes + obj_key1 = gw_attributes["obj_key1"] + obj_value1 = gw_attributes["obj_value1"] + obj_key2 = gw_attributes["obj_key2"] + obj_value2 = gw_attributes["obj_value2"] + + # Prepare for grpc PUT request + key_value1 = obj_key1 + "=" + obj_value1 + key_value2 = obj_key2 + "=" + obj_value2 + + with allure.step("Put objects using gRPC [--attributes chapter1=peace,chapter2=war]"): + oid = put_object_to_random_node( + wallet=self.wallet.path, + path=file_path, + cid=cid, + shell=self.shell, + neofs_env=self.neofs_env, + attributes=f"{key_value1},{key_value2}", + ) + with allure.step("Get object and verify hashes [ objects/$CID/by_id/$OID ]"): + get_object_and_verify_hashes( + oid=oid, + file_name=file_path, + wallet=self.wallet.path, + cid=cid, + shell=self.shell, + nodes=self.neofs_env.storage_nodes, + endpoint=gw_attributes["endpoint"], + ) + with allure.step("[Negative] try to get object: [objects/$CID/by_id/chapter1/peace]"): + attrs = {obj_key1: obj_value1, obj_key2: obj_value2} + request = f"/objects/{cid}/by_id/{obj_key1}/{obj_value1}" + expected_err_msg = "Failed to get object via REST gate:" + try_to_get_object_via_passed_request_and_expect_error( + cid=cid, + oid=oid, + error_pattern=expected_err_msg, + http_request_path=request, + attrs=attrs, + endpoint=gw_attributes["endpoint"], + ) + + with allure.step("Download the object with attribute [objects/$CID/by_attribute/chapter1/peace]"): + get_object_by_attr_and_verify_hashes( + oid=oid, + file_name=file_path, + cid=cid, + attrs=attrs, + endpoint=gw_attributes["endpoint"], + ) + with allure.step("[Negative] try to get object: objects/$CID/by_attribute/$OID"): + request = f"/objects/{cid}/by_attribute/{oid}" + try_to_get_object_via_passed_request_and_expect_error( + cid=cid, + oid=oid, + error_pattern=expected_err_msg, + http_request_path=request, + endpoint=gw_attributes["endpoint"], + ) + + with allure.step( + "[Negative] Try to get object with invalid attribute [objects/$CID/by_attribute/chapter1/war]" + ): + with pytest.raises(Exception, match=".*object not found.*"): + get_object_by_attr_and_verify_hashes( + oid=oid, + file_name=file_path, + cid=cid, + attrs={obj_key1: obj_value2}, + endpoint=gw_attributes["endpoint"], + ) diff --git a/pytest_tests/tests/services/rest_gate/test_rest_streaming.py b/pytest_tests/tests/services/rest_gate/test_rest_streaming.py index 2c9f33843..73c0294c5 100644 --- a/pytest_tests/tests/services/rest_gate/test_rest_streaming.py +++ b/pytest_tests/tests/services/rest_gate/test_rest_streaming.py @@ -4,9 +4,9 @@ import pytest from helpers.container import create_container from helpers.file_helper import generate_file -from helpers.http_gate import upload_via_http_gate +from helpers.rest_gate import upload_via_rest_gate from helpers.wellknown_acl import PUBLIC_ACL -from http_gw.http_utils import get_object_and_verify_hashes +from rest_gw.rest_utils import get_object_and_verify_hashes from neofs_env.neofs_env_test_base import NeofsEnvTestBase logger = logging.getLogger("NeoLogger") @@ -54,7 +54,7 @@ def test_object_can_be_put_get_by_streaming(self, object_size: int, gw_endpoint) file_path = generate_file(object_size) with allure.step("Put objects and Get object and verify hashes [ get/$CID/$OID ]"): - oid = upload_via_http_gate(cid=cid, path=file_path, endpoint=gw_endpoint) + oid = upload_via_rest_gate(cid=cid, path=file_path, endpoint=gw_endpoint) get_object_and_verify_hashes( oid=oid, file_name=file_path, diff --git a/pytest_tests/tests/services/rest_gate/test_rest_system_header.py b/pytest_tests/tests/services/rest_gate/test_rest_system_header.py index c7b7b0515..b0aeda9ea 100644 --- a/pytest_tests/tests/services/rest_gate/test_rest_system_header.py +++ b/pytest_tests/tests/services/rest_gate/test_rest_system_header.py @@ -9,14 +9,14 @@ from helpers.container import create_container from helpers.file_helper import generate_file from helpers.grpc_responses import OBJECT_NOT_FOUND -from helpers.http_gate import ( +from helpers.rest_gate import ( attr_into_str_header, try_to_get_object_and_expect_error, - upload_via_http_gate, + upload_via_rest_gate, ) from helpers.neofs_verbs import get_netmap_netinfo, get_object_from_random_node, head_object from helpers.wellknown_acl import PUBLIC_ACL -from http_gw.http_utils import get_object_and_verify_hashes +from rest_gw.rest_utils import get_object_and_verify_hashes from neofs_env.neofs_env_test_base import NeofsEnvTestBase logger = logging.getLogger("NeoLogger") @@ -107,7 +107,7 @@ def validation_for_http_header_attr(self, head_info: dict, expected_epoch: int) @allure.title("Put / get / verify object and return head command result to invoker") def oid_header_info_for_object(self, file_path: str, attributes: dict, user_container: str, gw_endpoint: str): - oid = upload_via_http_gate( + oid = upload_via_rest_gate( cid=user_container, path=file_path, endpoint=gw_endpoint, @@ -136,7 +136,7 @@ def test_unable_put_expired_epoch(self, user_container: str, simple_object_size: headers = attr_into_str_header({"Neofs-Expiration-Epoch": str(neofs_epoch.get_epoch(self.neofs_env) - 1)}) file_path = generate_file(simple_object_size) with allure.step("Put object using HTTP with attribute Expiration-Epoch where epoch is expired"): - upload_via_http_gate( + upload_via_rest_gate( cid=user_container, path=file_path, endpoint=gw_endpoint, @@ -149,7 +149,7 @@ def test_unable_put_negative_duration(self, user_container: str, simple_object_s headers = attr_into_str_header({"Neofs-Expiration-Duration": "-1h"}) file_path = generate_file(simple_object_size) with allure.step("Put object using HTTP with attribute Neofs-Expiration-Duration where duration is negative"): - upload_via_http_gate( + upload_via_rest_gate( cid=user_container, path=file_path, endpoint=gw_endpoint, @@ -164,7 +164,7 @@ def test_unable_put_expired_timestamp(self, user_container: str, simple_object_s with allure.step( "Put object using HTTP with attribute Neofs-Expiration-Timestamp where duration is in the past" ): - upload_via_http_gate( + upload_via_rest_gate( cid=user_container, path=file_path, endpoint=gw_endpoint, @@ -178,7 +178,7 @@ def test_unable_put_expired_timestamp(self, user_container: str, simple_object_s def test_unable_put_expired_rfc(self, user_container: str, simple_object_size: int, gw_endpoint): headers = attr_into_str_header({"Neofs-Expiration-RFC3339": "2021-11-22T09:55:49Z"}) file_path = generate_file(simple_object_size) - upload_via_http_gate( + upload_via_rest_gate( cid=user_container, path=file_path, endpoint=gw_endpoint,