This is an automated email from the ASF dual-hosted git repository.
pradeepagrawal8184 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ranger.git
The following commit(s) were added to refs/heads/master by this push:
new aaec8328f RANGER-5843 - Add Docker system tests to verify
user_rest_apis cases (#1314)
aaec8328f is described below
commit aaec8328f5c5d161f70c92f318c5062606fe3ab7
Author: harshal1172 <[email protected]>
AuthorDate: Fri Oct 16 10:58:23 2026 +0530
RANGER-5843 - Add Docker system tests to verify user_rest_apis cases (#1314)
---
functional-tests/.gitignore | 5 +-
functional-tests/api_tests/conftest.py | 36 ++
.../test_jsons/test_create_default_user.json | 17 +
...t_create_default_user_with_invalid_payload.json | 16 +
.../test_jsons/test_create_user.json | 17 +
.../test_create_user_with_invalid_payload.json | 16 +
.../test_edit_user_role_with_invalid_id.json | 7 +
.../test_jsons/test_edit_user_using_id.json | 7 +
.../user_rest_apis/variable_jsons/user_1_id.json | 17 +
.../test_user_rest_apis_for_admin_user.py | 248 +++++++++
.../test_user_rest_apis_for_auditor_user.py | 184 +++++++
.../test_user_rest_apis_for_keyadmin_user.py | 187 +++++++
.../test_user_rest_apis_for_user_role_user.py | 184 +++++++
.../test_user_rest_apis_negative_scenarios.py | 365 +++++++++++++
functional-tests/conftest.py | 61 +++
functional-tests/pytest.ini | 3 +-
functional-tests/ranger_test_utils/api_data.py | 59 +++
.../ranger_test_utils/api_test_utils.py | 580 +++++++++++++++++++++
functional-tests/ranger_test_utils/test_env.py | 53 ++
functional-tests/ranger_test_utils/utils.py | 2 +-
functional-tests/run-tests.sh | 4 +-
21 files changed, 2063 insertions(+), 5 deletions(-)
diff --git a/functional-tests/.gitignore b/functional-tests/.gitignore
index 1734553a9..fba7cc9d0 100644
--- a/functional-tests/.gitignore
+++ b/functional-tests/.gitignore
@@ -7,5 +7,8 @@ myenv/
# Pytest cache
.pytest_cache/
-# Pytest file log (pytest.ini log_file=logs/pytest.log)
+# Pytest file logs (logs/<test_module_stem>.log via conftest.py)
logs/
+
+# HTML reports from run-tests.sh (pytest --html=report_<suite>.html)
+report_*.html
diff --git a/functional-tests/api_tests/conftest.py
b/functional-tests/api_tests/conftest.py
new file mode 100644
index 000000000..f240d40ce
--- /dev/null
+++ b/functional-tests/api_tests/conftest.py
@@ -0,0 +1,36 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Session setup for all modules under ``api_tests/`` (asset, plugin, role, …
ports).
+
+Pytest loads this file for any test collected under ``api_tests/`` and
subfolders.
+"""
+
+import pytest
+
+from ranger_test_utils.api_test_utils import create_kmsauditor_test_user,
create_required_test_users
+
+
[email protected](scope="session", autouse=True)
+def api_test_standard_role_users():
+ """Users for ``*_for_user_role_user`` and ``*_for_auditor_user``
modules."""
+ create_required_test_users()
+
+
[email protected](scope="session")
+def api_test_kms_auditor_user():
+ """User for ``*_for_kmsauditor_user`` public API v2 modules."""
+ create_kmsauditor_test_user()
diff --git
a/functional-tests/api_tests/data/user_rest_apis/test_jsons/test_create_default_user.json
b/functional-tests/api_tests/data/user_rest_apis/test_jsons/test_create_default_user.json
new file mode 100644
index 000000000..30c567436
--- /dev/null
+++
b/functional-tests/api_tests/data/user_rest_apis/test_jsons/test_create_default_user.json
@@ -0,0 +1,17 @@
+{
+ "loginId":"test_user_{random_str}",
+ "firstName":"first_name_test_user_{random_str}",
+ "lastName":"last_name_test_user_{random_str}",
+ "emailAddress":"test_user_{random_str}@abc.com",
+ "password":"Password@123456789#!",
+ "description":"Create test user test_user_{random_str}",
+ "groupIdList":[
+ 1
+ ],
+ "groupNameList":[
+ "public"
+ ],
+ "userRoleList":[
+ "ROLE_USER"
+ ]
+}
\ No newline at end of file
diff --git
a/functional-tests/api_tests/data/user_rest_apis/test_jsons/test_create_default_user_with_invalid_payload.json
b/functional-tests/api_tests/data/user_rest_apis/test_jsons/test_create_default_user_with_invalid_payload.json
new file mode 100644
index 000000000..e3f701829
--- /dev/null
+++
b/functional-tests/api_tests/data/user_rest_apis/test_jsons/test_create_default_user_with_invalid_payload.json
@@ -0,0 +1,16 @@
+{
+ "firstName":"first_name_test_user_{random_str}",
+ "lastName":"last_name_test_user_{random_str}",
+ "emailAddress":"test_user_{random_str}@abc.com",
+ "password":"Password@123456789#!",
+ "description":"Create test user test_user_{random_str}",
+ "groupIdList":[
+ 1
+ ],
+ "groupNameList":[
+ "public"
+ ],
+ "userRoleList":[
+ "ROLE_USER"
+ ]
+}
\ No newline at end of file
diff --git
a/functional-tests/api_tests/data/user_rest_apis/test_jsons/test_create_user.json
b/functional-tests/api_tests/data/user_rest_apis/test_jsons/test_create_user.json
new file mode 100644
index 000000000..30c567436
--- /dev/null
+++
b/functional-tests/api_tests/data/user_rest_apis/test_jsons/test_create_user.json
@@ -0,0 +1,17 @@
+{
+ "loginId":"test_user_{random_str}",
+ "firstName":"first_name_test_user_{random_str}",
+ "lastName":"last_name_test_user_{random_str}",
+ "emailAddress":"test_user_{random_str}@abc.com",
+ "password":"Password@123456789#!",
+ "description":"Create test user test_user_{random_str}",
+ "groupIdList":[
+ 1
+ ],
+ "groupNameList":[
+ "public"
+ ],
+ "userRoleList":[
+ "ROLE_USER"
+ ]
+}
\ No newline at end of file
diff --git
a/functional-tests/api_tests/data/user_rest_apis/test_jsons/test_create_user_with_invalid_payload.json
b/functional-tests/api_tests/data/user_rest_apis/test_jsons/test_create_user_with_invalid_payload.json
new file mode 100644
index 000000000..e3f701829
--- /dev/null
+++
b/functional-tests/api_tests/data/user_rest_apis/test_jsons/test_create_user_with_invalid_payload.json
@@ -0,0 +1,16 @@
+{
+ "firstName":"first_name_test_user_{random_str}",
+ "lastName":"last_name_test_user_{random_str}",
+ "emailAddress":"test_user_{random_str}@abc.com",
+ "password":"Password@123456789#!",
+ "description":"Create test user test_user_{random_str}",
+ "groupIdList":[
+ 1
+ ],
+ "groupNameList":[
+ "public"
+ ],
+ "userRoleList":[
+ "ROLE_USER"
+ ]
+}
\ No newline at end of file
diff --git
a/functional-tests/api_tests/data/user_rest_apis/test_jsons/test_edit_user_role_with_invalid_id.json
b/functional-tests/api_tests/data/user_rest_apis/test_jsons/test_edit_user_role_with_invalid_id.json
new file mode 100644
index 000000000..248c973e4
--- /dev/null
+++
b/functional-tests/api_tests/data/user_rest_apis/test_jsons/test_edit_user_role_with_invalid_id.json
@@ -0,0 +1,7 @@
+{
+ "vXStrings":[
+ {
+ "value":"ROLE_KEY_ADMIN"
+ }
+ ]
+}
\ No newline at end of file
diff --git
a/functional-tests/api_tests/data/user_rest_apis/test_jsons/test_edit_user_using_id.json
b/functional-tests/api_tests/data/user_rest_apis/test_jsons/test_edit_user_using_id.json
new file mode 100644
index 000000000..48d6056d6
--- /dev/null
+++
b/functional-tests/api_tests/data/user_rest_apis/test_jsons/test_edit_user_using_id.json
@@ -0,0 +1,7 @@
+{
+ "vXStrings":[
+ {
+ "value":"ROLE_USER"
+ }
+ ]
+}
\ No newline at end of file
diff --git
a/functional-tests/api_tests/data/user_rest_apis/variable_jsons/user_1_id.json
b/functional-tests/api_tests/data/user_rest_apis/variable_jsons/user_1_id.json
new file mode 100644
index 000000000..30c567436
--- /dev/null
+++
b/functional-tests/api_tests/data/user_rest_apis/variable_jsons/user_1_id.json
@@ -0,0 +1,17 @@
+{
+ "loginId":"test_user_{random_str}",
+ "firstName":"first_name_test_user_{random_str}",
+ "lastName":"last_name_test_user_{random_str}",
+ "emailAddress":"test_user_{random_str}@abc.com",
+ "password":"Password@123456789#!",
+ "description":"Create test user test_user_{random_str}",
+ "groupIdList":[
+ 1
+ ],
+ "groupNameList":[
+ "public"
+ ],
+ "userRoleList":[
+ "ROLE_USER"
+ ]
+}
\ No newline at end of file
diff --git
a/functional-tests/api_tests/user_rest_apis/test_user_rest_apis_for_admin_user.py
b/functional-tests/api_tests/user_rest_apis/test_user_rest_apis_for_admin_user.py
new file mode 100644
index 000000000..3b1df49bf
--- /dev/null
+++
b/functional-tests/api_tests/user_rest_apis/test_user_rest_apis_for_admin_user.py
@@ -0,0 +1,248 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import json
+
+import pytest
+import requests
+
+from ranger_test_utils.api_data import api_data_paths
+from ranger_test_utils.api_test_utils import (
+ admin_auth,
+ base_url,
+ compare_response_data,
+ get_request_data,
+ get_updated_request_data,
+ get_variable,
+ headers,
+ logger,
+)
+
+variable_dict = {}
+str_variable_dict = {}
+_data_paths = api_data_paths(__file__, "user_rest_apis", layout="ranger_qe")
+variables_data_path = _data_paths["variables_data_path"]
+test_data_path = _data_paths["test_data_path"]
+
+
+def setup_module():
+ variable_specifier_list = [
+ ("user_1_id", "POST", "/users", "user_1_id.json", "id"),
+ ("user_1", "GET", "/users/{user_1_id}", None, "same"),
+ ]
+
+ for variable_specification in variable_specifier_list:
+ variable_name = variable_specification[0]
+ variable_dict[variable_name] = get_variable(
+ variable_specification, str_variable_dict, variables_data_path
+ )
+ str_variable_dict[variable_name] = str(variable_dict[variable_name])
+
+
[email protected]
+def test_get_users_by_admin():
+ request_url = base_url + "/users"
+ logger.info("The request url is :- %s", request_url)
+ resp = requests.get(request_url, verify=False, auth=admin_auth,
headers=headers)
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 200, "Expected status code not returned"
+
+
[email protected]
+def test_get_user_profile_by_admin():
+ request_url = base_url + "/users/profile"
+ logger.info("The request url is :- %s", request_url)
+ resp = requests.get(request_url, verify=False, auth=admin_auth,
headers=headers)
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 200, "Expected status code not returned"
+
+
[email protected]
+def test_get_user_using_id_by_admin():
+ request_url = base_url + "/users/{user_1_id}"
+ request_url = request_url.format(**str_variable_dict)
+
+ logger.info("The request url is :- %s", request_url)
+ resp = requests.get(request_url, verify=False, auth=admin_auth,
headers=headers)
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 200, "Expected status code not returned"
+ resp_data = resp.json()
+
+ expected_response_data = variable_dict["user_1"]
+ compare_response_data(resp_data, expected_response_data, True)
+
+
[email protected]
+def test_create_user_by_admin():
+ request_url = base_url + "/users"
+ request_data = get_request_data("test_create_user.json",
str_variable_dict, test_data_path)
+ logger.info("The request url is :- %s", request_url)
+ logger.info("The request data is :- %s", request_data)
+ resp = requests.post(
+ request_url, verify=False, auth=admin_auth, headers=headers,
data=json.dumps(request_data)
+ )
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 200, "Expected status code not returned"
+
+
[email protected]
+def test_create_default_user_by_admin():
+ request_url = base_url + "/users/default"
+ request_data = get_request_data(
+ "test_create_default_user.json", str_variable_dict, test_data_path
+ )
+ logger.info("The request url is :- %s", request_url)
+ logger.info("The request data is :- %s", request_data)
+ resp = requests.post(
+ request_url, verify=False, auth=admin_auth, headers=headers,
data=json.dumps(request_data)
+ )
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 200, "Expected status code not returned"
+
+
[email protected]
+def test_edit_user_by_admin():
+ request_url = base_url + "/users"
+ request_data = variable_dict["user_1"]
+ fields_to_update = {"firstName": "{random_str}"}
+ request_data = get_updated_request_data(request_data=request_data,
fields_to_update=fields_to_update)
+ logger.info("The request url is :- %s", request_url)
+ logger.info("The request data is :- %s", request_data)
+ resp = requests.put(
+ request_url, verify=False, auth=admin_auth, headers=headers,
data=json.dumps(request_data)
+ )
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 200, "Expected status code not returned"
+
+
[email protected]
+def test_edit_user_using_id_by_admin():
+ request_url = base_url + "/users/{user_1_id}/roles"
+ request_url = request_url.format(**str_variable_dict)
+
+ request_data = get_request_data("test_edit_user_using_id.json",
str_variable_dict, test_data_path)
+ logger.info("The request url is :- %s", request_url)
+ logger.info("The request data is :- %s", request_data)
+ resp = requests.put(
+ request_url, verify=False, auth=admin_auth, headers=headers,
data=json.dumps(request_data)
+ )
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 200, "Expected status code not returned"
+
+
[email protected]
+def test_change_email_by_admin():
+ request_url = base_url + "/users/{user_1_id}/emailchange"
+ request_url = request_url.format(**str_variable_dict)
+
+ request_data = variable_dict["user_1"]
+ fields_to_update = {
+ "oldPassword": "Password@123456789#!",
+ "emailAddress": "test_user_{random_str}[email protected]",
+ }
+ request_data = get_updated_request_data(request_data=request_data,
fields_to_update=fields_to_update)
+ logger.info("The request url is :- %s", request_url)
+ logger.info("The request data is :- %s", request_data)
+ resp = requests.post(
+ request_url, verify=False, auth=admin_auth, headers=headers,
data=json.dumps(request_data)
+ )
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 200, "Expected status code not returned"
+
+
+def test_change_email_with_null_id_by_admin():
+ """
+ Verify that null ID in request body is set from path parameter for email
change.
+ """
+ request_url = base_url + "/users/{user_1_id}/emailchange"
+ request_url = request_url.format(**str_variable_dict)
+
+ request_data = variable_dict["user_1"]
+ fields_to_update = {
+ "id": None,
+ "oldPassword": "Password@123456789#!",
+ "emailAddress": "test_user_{random_str}[email protected]",
+ }
+ request_data = get_updated_request_data(request_data=request_data,
fields_to_update=fields_to_update)
+ logger.info("The request url is :- %s", request_url)
+ logger.info("The request data is :- %s", request_data)
+ resp = requests.post(
+ request_url, verify=False, auth=admin_auth, headers=headers,
data=json.dumps(request_data)
+ )
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 200, "Expected status code not returned"
+
+
[email protected]
+def test_change_password_by_admin():
+ request_url = base_url + "/users/{user_1_id}/passwordchange"
+ request_url = request_url.format(**str_variable_dict)
+
+ request_data = variable_dict["user_1"]
+ fields_to_update = {"oldPassword": "Password@123456789#!", "updPassword":
"Password@123456789#!56"}
+ request_data = get_updated_request_data(request_data=request_data,
fields_to_update=fields_to_update)
+ logger.info("The request url is :- %s", request_url)
+ logger.info("The request data is :- %s", request_data)
+ resp = requests.post(
+ request_url, verify=False, auth=admin_auth, headers=headers,
data=json.dumps(request_data)
+ )
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 200, "Expected status code not returned"
+
+
+def test_change_password_with_null_id_by_admin():
+ """
+ Verify that null ID in request body is set from path parameter.
+ """
+ request_url = base_url + "/users/{user_1_id}/passwordchange"
+ request_url = request_url.format(**str_variable_dict)
+
+ request_data = variable_dict["user_1"]
+ fields_to_update = {
+ "id": None,
+ "oldPassword": "Password@123456789#!56",
+ "updPassword": "Password@123456789#!789",
+ }
+ request_data = get_updated_request_data(request_data=request_data,
fields_to_update=fields_to_update)
+ logger.info("The request url is :- %s", request_url)
+ logger.info("The request data is :- %s", request_data)
+ resp = requests.post(
+ request_url, verify=False, auth=admin_auth, headers=headers,
data=json.dumps(request_data)
+ )
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 200, "Expected status code not returned"
+
+
[email protected]
+def test_deactivate_user_by_admin():
+ request_url = base_url + "/users/{user_1_id}/deactivate"
+ request_url = request_url.format(**str_variable_dict)
+
+ logger.info("The request url is :- %s", request_url)
+ resp = requests.post(request_url, verify=False, auth=admin_auth,
headers=headers)
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 200, "Expected status code not returned"
diff --git
a/functional-tests/api_tests/user_rest_apis/test_user_rest_apis_for_auditor_user.py
b/functional-tests/api_tests/user_rest_apis/test_user_rest_apis_for_auditor_user.py
new file mode 100644
index 000000000..3a6870c44
--- /dev/null
+++
b/functional-tests/api_tests/user_rest_apis/test_user_rest_apis_for_auditor_user.py
@@ -0,0 +1,184 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import json
+
+import requests
+
+from ranger_test_utils.api_data import api_data_paths
+from ranger_test_utils.api_test_utils import (
+ auditor_auth,
+ base_url,
+ get_request_data,
+ get_updated_request_data,
+ get_variable,
+ headers,
+ logger,
+)
+
+variable_dict = {}
+str_variable_dict = {}
+_data_paths = api_data_paths(__file__, "user_rest_apis", layout="ranger_qe")
+variables_data_path = _data_paths["variables_data_path"]
+test_data_path = _data_paths["test_data_path"]
+
+
+def setup_module():
+ variable_specifier_list = [
+ ("user_1_id", "POST", "/users", "user_1_id.json", "id"),
+ ("user_1", "GET", "/users/{user_1_id}", None, "same"),
+ ]
+
+ for variable_specification in variable_specifier_list:
+ variable_name = variable_specification[0]
+ variable_dict[variable_name] = get_variable(
+ variable_specification, str_variable_dict, variables_data_path
+ )
+ str_variable_dict[variable_name] = str(variable_dict[variable_name])
+
+
+def test_get_users_by_auditor():
+ request_url = base_url + "/users"
+ logger.info("The request url is :- %s", request_url)
+ resp = requests.get(request_url, verify=False, auth=auditor_auth,
headers=headers)
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 200, "Expected status code not returned"
+
+
+def test_get_user_profile_by_auditor():
+ request_url = base_url + "/users/profile"
+ logger.info("The request url is :- %s", request_url)
+ resp = requests.get(request_url, verify=False, auth=auditor_auth,
headers=headers)
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 200, "Expected status code not returned"
+
+
+def test_get_user_using_id_by_auditor():
+ request_url = base_url + "/users/{user_1_id}"
+ request_url = request_url.format(**str_variable_dict)
+
+ logger.info("The request url is :- %s", request_url)
+ resp = requests.get(request_url, verify=False, auth=auditor_auth,
headers=headers)
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 204, "Expected status code not returned"
+
+
+def test_create_user_by_auditor():
+ request_url = base_url + "/users"
+ request_data = get_request_data("test_create_user.json",
str_variable_dict, test_data_path)
+ logger.info("The request url is :- %s", request_url)
+ logger.info("The request data is :- %s", request_data)
+ resp = requests.post(
+ request_url, verify=False, auth=auditor_auth, headers=headers,
data=json.dumps(request_data)
+ )
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 403, "Expected status code not returned"
+
+
+def test_create_default_user_by_auditor():
+ request_url = base_url + "/users/default"
+ request_data = get_request_data(
+ "test_create_default_user.json", str_variable_dict, test_data_path
+ )
+ logger.info("The request url is :- %s", request_url)
+ logger.info("The request data is :- %s", request_data)
+ resp = requests.post(
+ request_url, verify=False, auth=auditor_auth, headers=headers,
data=json.dumps(request_data)
+ )
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 403, "Expected status code not returned"
+
+
+def test_edit_user_by_auditor():
+ request_url = base_url + "/users"
+ request_data = variable_dict["user_1"]
+ fields_to_update = {"firstName": "{random_str}"}
+ request_data = get_updated_request_data(request_data=request_data,
fields_to_update=fields_to_update)
+ logger.info("The request url is :- %s", request_url)
+ logger.info("The request data is :- %s", request_data)
+ resp = requests.put(
+ request_url, verify=False, auth=auditor_auth, headers=headers,
data=json.dumps(request_data)
+ )
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 403, "Expected status code not returned"
+
+
+def test_edit_user_using_id_by_auditor():
+ request_url = base_url + "/users/{user_1_id}/roles"
+ request_url = request_url.format(**str_variable_dict)
+
+ request_data = get_request_data("test_edit_user_using_id.json",
str_variable_dict, test_data_path)
+ logger.info("The request url is :- %s", request_url)
+ logger.info("The request data is :- %s", request_data)
+ resp = requests.put(
+ request_url, verify=False, auth=auditor_auth, headers=headers,
data=json.dumps(request_data)
+ )
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 403, "Expected status code not returned"
+
+
+def test_change_email_by_auditor():
+ request_url = base_url + "/users/{user_1_id}/emailchange"
+ request_url = request_url.format(**str_variable_dict)
+
+ request_data = variable_dict["user_1"]
+ fields_to_update = {
+ "oldPassword": "Password@123",
+ "emailAddress": "test_user_{random_str}[email protected]",
+ }
+ request_data = get_updated_request_data(request_data=request_data,
fields_to_update=fields_to_update)
+ logger.info("The request url is :- %s", request_url)
+ logger.info("The request data is :- %s", request_data)
+ resp = requests.post(
+ request_url, verify=False, auth=auditor_auth, headers=headers,
data=json.dumps(request_data)
+ )
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 403, "Expected status code not returned"
+
+
+def test_change_password_by_auditor():
+ request_url = base_url + "/users/{user_1_id}/passwordchange"
+ request_url = request_url.format(**str_variable_dict)
+
+ request_data = variable_dict["user_1"]
+ fields_to_update = {"oldPassword": "Password@123", "updPassword":
"Password@123456"}
+ request_data = get_updated_request_data(request_data=request_data,
fields_to_update=fields_to_update)
+ logger.info("The request url is :- %s", request_url)
+ logger.info("The request data is :- %s", request_data)
+ resp = requests.post(
+ request_url, verify=False, auth=auditor_auth, headers=headers,
data=json.dumps(request_data)
+ )
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 403, "Expected status code not returned"
+
+
+def test_deactivate_user_by_auditor():
+ request_url = base_url + "/users/{user_1_id}/deactivate"
+ request_url = request_url.format(**str_variable_dict)
+
+ logger.info("The request url is :- %s", request_url)
+ resp = requests.post(request_url, verify=False, auth=auditor_auth,
headers=headers)
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 403, "Expected status code not returned"
diff --git
a/functional-tests/api_tests/user_rest_apis/test_user_rest_apis_for_keyadmin_user.py
b/functional-tests/api_tests/user_rest_apis/test_user_rest_apis_for_keyadmin_user.py
new file mode 100644
index 000000000..c6c790c55
--- /dev/null
+++
b/functional-tests/api_tests/user_rest_apis/test_user_rest_apis_for_keyadmin_user.py
@@ -0,0 +1,187 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import json
+
+import requests
+
+from ranger_test_utils.api_data import api_data_paths
+from ranger_test_utils.api_test_utils import (
+ base_url,
+ get_request_data,
+ get_updated_request_data,
+ get_variable,
+ headers,
+ keyadmin_auth,
+ logger,
+)
+
+variable_dict = {}
+str_variable_dict = {}
+_data_paths = api_data_paths(__file__, "user_rest_apis", layout="ranger_qe")
+variables_data_path = _data_paths["variables_data_path"]
+test_data_path = _data_paths["test_data_path"]
+
+
+def setup_module():
+ variable_specifier_list = [
+ ("user_1_id", "POST", "/users", "user_1_id.json", "id"),
+ ("user_1", "GET", "/users/{user_1_id}", None, "same"),
+ ]
+
+ for variable_specification in variable_specifier_list:
+ variable_name = variable_specification[0]
+ variable_dict[variable_name] = get_variable(
+ variable_specification, str_variable_dict, variables_data_path
+ )
+ str_variable_dict[variable_name] = str(variable_dict[variable_name])
+
+
+def test_get_users_by_keyadmin():
+ request_url = base_url + "/users"
+ logger.info("The request url is :- %s", request_url)
+ resp = requests.get(request_url, verify=False, auth=keyadmin_auth,
headers=headers)
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 200, "Expected status code not returned"
+
+
+def test_get_user_profile_by_keyadmin():
+ request_url = base_url + "/users/profile"
+ logger.info("The request url is :- %s", request_url)
+ resp = requests.get(request_url, verify=False, auth=keyadmin_auth,
headers=headers)
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 200, "Expected status code not returned"
+
+
+def test_get_user_using_id_by_keyadmin():
+ request_url = base_url + "/users/{user_1_id}"
+ request_url = request_url.format(**str_variable_dict)
+
+ logger.info("The request url is :- %s", request_url)
+ resp = requests.get(request_url, verify=False, auth=keyadmin_auth,
headers=headers)
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 200, "Expected status code not returned"
+
+
+def test_create_user_by_keyadmin():
+ request_url = base_url + "/users"
+ request_data = get_request_data("test_create_user.json",
str_variable_dict, test_data_path)
+ logger.info("The request url is :- %s", request_url)
+ logger.info("The request data is :- %s", request_data)
+ resp = requests.post(
+ request_url, verify=False, auth=keyadmin_auth, headers=headers,
data=json.dumps(request_data)
+ )
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 403, "Expected status code not returned"
+
+
+def test_create_default_user_by_keyadmin():
+ request_url = base_url + "/users/default"
+ request_data = get_request_data(
+ "test_create_default_user.json", str_variable_dict, test_data_path
+ )
+ logger.info("The request url is :- %s", request_url)
+ logger.info("The request data is :- %s", request_data)
+ resp = requests.post(
+ request_url, verify=False, auth=keyadmin_auth, headers=headers,
data=json.dumps(request_data)
+ )
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 403, "Expected status code not returned"
+
+
+def test_edit_user_by_keyadmin():
+ request_url = base_url + "/users"
+ request_data = variable_dict["user_1"]
+ fields_to_update = {"firstName": "{random_str}"}
+ request_data = get_updated_request_data(request_data=request_data,
fields_to_update=fields_to_update)
+ logger.info("The request url is :- %s", request_url)
+ logger.info("The request data is :- %s", request_data)
+ resp = requests.put(
+ request_url, verify=False, auth=keyadmin_auth, headers=headers,
data=json.dumps(request_data)
+ )
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 200, "Expected status code not returned"
+
+
+def test_edit_user_using_id_by_keyadmin():
+ request_url = base_url + "/users/{user_1_id}/roles"
+ request_url = request_url.format(**str_variable_dict)
+
+ request_data = get_request_data("test_edit_user_using_id.json",
str_variable_dict, test_data_path)
+ logger.info("The request url is :- %s", request_url)
+ logger.info("The request data is :- %s", request_data)
+ resp = requests.put(
+ request_url, verify=False, auth=keyadmin_auth, headers=headers,
data=json.dumps(request_data)
+ )
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 200, "Expected status code not returned"
+
+
+def test_change_email_by_keyadmin():
+ request_url = base_url + "/users/{user_1_id}/emailchange"
+ request_url = request_url.format(**str_variable_dict)
+
+ request_data = variable_dict["user_1"]
+ fields_to_update = {
+ "oldPassword": "Password@123456789#!",
+ "emailAddress": "test_user_{random_str}[email protected]",
+ }
+ request_data = get_updated_request_data(request_data=request_data,
fields_to_update=fields_to_update)
+ logger.info("The request url is :- %s", request_url)
+ logger.info("The request data is :- %s", request_data)
+ resp = requests.post(
+ request_url, verify=False, auth=keyadmin_auth, headers=headers,
data=json.dumps(request_data)
+ )
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 200, "Expected status code not returned"
+
+
+def test_change_password_by_keyadmin():
+ request_url = base_url + "/users/{user_1_id}/passwordchange"
+ request_url = request_url.format(**str_variable_dict)
+
+ request_data = variable_dict["user_1"]
+ fields_to_update = {
+ "oldPassword": "Password@123456789#!",
+ "updPassword": "Password@123456789#!56",
+ }
+ request_data = get_updated_request_data(request_data=request_data,
fields_to_update=fields_to_update)
+ logger.info("The request url is :- %s", request_url)
+ logger.info("The request data is :- %s", request_data)
+ resp = requests.post(
+ request_url, verify=False, auth=keyadmin_auth, headers=headers,
data=json.dumps(request_data)
+ )
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 200, "Expected status code not returned"
+
+
+def test_deactivate_user_by_keyadmin():
+ request_url = base_url + "/users/{user_1_id}/deactivate"
+ request_url = request_url.format(**str_variable_dict)
+
+ logger.info("The request url is :- %s", request_url)
+ resp = requests.post(request_url, verify=False, auth=keyadmin_auth,
headers=headers)
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 403, "Expected status code not returned"
diff --git
a/functional-tests/api_tests/user_rest_apis/test_user_rest_apis_for_user_role_user.py
b/functional-tests/api_tests/user_rest_apis/test_user_rest_apis_for_user_role_user.py
new file mode 100644
index 000000000..ef5a5e2f0
--- /dev/null
+++
b/functional-tests/api_tests/user_rest_apis/test_user_rest_apis_for_user_role_user.py
@@ -0,0 +1,184 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import json
+
+import requests
+
+from ranger_test_utils.api_data import api_data_paths
+from ranger_test_utils.api_test_utils import (
+ base_url,
+ get_request_data,
+ get_updated_request_data,
+ get_variable,
+ headers,
+ logger,
+ user_auth,
+)
+
+variable_dict = {}
+str_variable_dict = {}
+_data_paths = api_data_paths(__file__, "user_rest_apis", layout="ranger_qe")
+variables_data_path = _data_paths["variables_data_path"]
+test_data_path = _data_paths["test_data_path"]
+
+
+def setup_module():
+ variable_specifier_list = [
+ ("user_1_id", "POST", "/users", "user_1_id.json", "id"),
+ ("user_1", "GET", "/users/{user_1_id}", None, "same"),
+ ]
+
+ for variable_specification in variable_specifier_list:
+ variable_name = variable_specification[0]
+ variable_dict[variable_name] = get_variable(
+ variable_specification, str_variable_dict, variables_data_path
+ )
+ str_variable_dict[variable_name] = str(variable_dict[variable_name])
+
+
+def test_get_users_by_ROLE_USER():
+ request_url = base_url + "/users"
+ logger.info("The request url is :- %s", request_url)
+ resp = requests.get(request_url, verify=False, auth=user_auth,
headers=headers)
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 200, "Expected status code not returned"
+
+
+def test_get_user_profile_by_ROLE_USER():
+ request_url = base_url + "/users/profile"
+ logger.info("The request url is :- %s", request_url)
+ resp = requests.get(request_url, verify=False, auth=user_auth,
headers=headers)
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 200, "Expected status code not returned"
+
+
+def test_get_user_using_id_by_ROLE_USER():
+ request_url = base_url + "/users/{user_1_id}"
+ request_url = request_url.format(**str_variable_dict)
+
+ logger.info("The request url is :- %s", request_url)
+ resp = requests.get(request_url, verify=False, auth=user_auth,
headers=headers)
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 204, "Expected status code not returned"
+
+
+def test_create_user_by_ROLE_USER():
+ request_url = base_url + "/users"
+ request_data = get_request_data("test_create_user.json",
str_variable_dict, test_data_path)
+ logger.info("The request url is :- %s", request_url)
+ logger.info("The request data is :- %s", request_data)
+ resp = requests.post(
+ request_url, verify=False, auth=user_auth, headers=headers,
data=json.dumps(request_data)
+ )
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 403, "Expected status code not returned"
+
+
+def test_create_default_user_by_ROLE_USER():
+ request_url = base_url + "/users/default"
+ request_data = get_request_data(
+ "test_create_default_user.json", str_variable_dict, test_data_path
+ )
+ logger.info("The request url is :- %s", request_url)
+ logger.info("The request data is :- %s", request_data)
+ resp = requests.post(
+ request_url, verify=False, auth=user_auth, headers=headers,
data=json.dumps(request_data)
+ )
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 403, "Expected status code not returned"
+
+
+def test_edit_user_by_ROLE_USER():
+ request_url = base_url + "/users"
+ request_data = variable_dict["user_1"]
+ fields_to_update = {"firstName": "{random_str}"}
+ request_data = get_updated_request_data(request_data=request_data,
fields_to_update=fields_to_update)
+ logger.info("The request url is :- %s", request_url)
+ logger.info("The request data is :- %s", request_data)
+ resp = requests.put(
+ request_url, verify=False, auth=user_auth, headers=headers,
data=json.dumps(request_data)
+ )
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 403, "Expected status code not returned"
+
+
+def test_edit_user_using_id_by_ROLE_USER():
+ request_url = base_url + "/users/{user_1_id}/roles"
+ request_url = request_url.format(**str_variable_dict)
+
+ request_data = get_request_data("test_edit_user_using_id.json",
str_variable_dict, test_data_path)
+ logger.info("The request url is :- %s", request_url)
+ logger.info("The request data is :- %s", request_data)
+ resp = requests.put(
+ request_url, verify=False, auth=user_auth, headers=headers,
data=json.dumps(request_data)
+ )
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 403, "Expected status code not returned"
+
+
+def test_change_email_by_ROLE_USER():
+ request_url = base_url + "/users/{user_1_id}/emailchange"
+ request_url = request_url.format(**str_variable_dict)
+
+ request_data = variable_dict["user_1"]
+ fields_to_update = {
+ "oldPassword": "Password@123",
+ "emailAddress": "test_user_{random_str}[email protected]",
+ }
+ request_data = get_updated_request_data(request_data=request_data,
fields_to_update=fields_to_update)
+ logger.info("The request url is :- %s", request_url)
+ logger.info("The request data is :- %s", request_data)
+ resp = requests.post(
+ request_url, verify=False, auth=user_auth, headers=headers,
data=json.dumps(request_data)
+ )
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 403, "Expected status code not returned"
+
+
+def test_change_password_by_ROLE_USER():
+ request_url = base_url + "/users/{user_1_id}/passwordchange"
+ request_url = request_url.format(**str_variable_dict)
+
+ request_data = variable_dict["user_1"]
+ fields_to_update = {"oldPassword": "Password@123", "updPassword":
"Password@123456"}
+ request_data = get_updated_request_data(request_data=request_data,
fields_to_update=fields_to_update)
+ logger.info("The request url is :- %s", request_url)
+ logger.info("The request data is :- %s", request_data)
+ resp = requests.post(
+ request_url, verify=False, auth=user_auth, headers=headers,
data=json.dumps(request_data)
+ )
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 403, "Expected status code not returned"
+
+
+def test_deactivate_user_by_ROLE_USER():
+ request_url = base_url + "/users/{user_1_id}/deactivate"
+ request_url = request_url.format(**str_variable_dict)
+
+ logger.info("The request url is :- %s", request_url)
+ resp = requests.post(request_url, verify=False, auth=user_auth,
headers=headers)
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 403, "Expected status code not returned"
diff --git
a/functional-tests/api_tests/user_rest_apis/test_user_rest_apis_negative_scenarios.py
b/functional-tests/api_tests/user_rest_apis/test_user_rest_apis_negative_scenarios.py
new file mode 100644
index 000000000..f622d0f7b
--- /dev/null
+++
b/functional-tests/api_tests/user_rest_apis/test_user_rest_apis_negative_scenarios.py
@@ -0,0 +1,365 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import json
+import random
+
+import requests
+
+from ranger_test_utils.api_data import api_data_paths
+from ranger_test_utils.api_test_utils import (
+ admin_auth,
+ base_url,
+ get_request_data,
+ get_updated_request_data,
+ get_variable,
+ headers,
+ logger,
+ return_random_str,
+)
+
+variable_dict = {}
+str_variable_dict = {}
+variable_dict["random_num"] = random.randint(100000, 1000000)
+variable_dict["random_str"] = return_random_str(7)
+str_variable_dict["random_num"] = str(variable_dict["random_num"])
+str_variable_dict["random_str"] = str(variable_dict["random_str"])
+_data_paths = api_data_paths(__file__, "user_rest_apis", layout="ranger_qe")
+variables_data_path = _data_paths["variables_data_path"]
+test_data_path = _data_paths["test_data_path"]
+
+
+def setup_module():
+ variable_specifier_list = [
+ ("user_1_id", "POST", "/users", "user_1_id.json", "id"),
+ ("user_1", "GET", "/users/{user_1_id}", None, "same"),
+ ]
+
+ for variable_specification in variable_specifier_list:
+ variable_name = variable_specification[0]
+ variable_dict[variable_name] = get_variable(
+ variable_specification, str_variable_dict, variables_data_path
+ )
+ str_variable_dict[variable_name] = str(variable_dict[variable_name])
+
+
+def test_get_user_by_invalid_id():
+ request_url = base_url + "/users/{random_num}"
+ request_url = request_url.format(**str_variable_dict)
+
+ logger.info("The request url is :- %s", request_url)
+ resp = requests.get(request_url, verify=False, auth=admin_auth,
headers=headers)
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 204, "Expected status code not returned"
+
+
+def test_create_user_with_invalid_payload():
+ request_url = base_url + "/users"
+ request_data = get_request_data(
+ "test_create_user_with_invalid_payload.json", str_variable_dict,
test_data_path
+ )
+ logger.info("The request url is :- %s", request_url)
+ logger.info("The request data is :- %s", request_data)
+ resp = requests.post(
+ request_url, verify=False, auth=admin_auth, headers=headers,
data=json.dumps(request_data)
+ )
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 400, "Expected status code not returned"
+
+
+def test_create_default_user_with_invalid_payload():
+ request_url = base_url + "/users/default"
+ request_data = get_request_data(
+ "test_create_default_user_with_invalid_payload.json",
str_variable_dict, test_data_path
+ )
+ logger.info("The request url is :- %s", request_url)
+ logger.info("The request data is :- %s", request_data)
+ resp = requests.post(
+ request_url, verify=False, auth=admin_auth, headers=headers,
data=json.dumps(request_data)
+ )
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 204, "Expected status code not returned"
+
+
+def test_edit_user_with_invalid_payload():
+ request_url = base_url + "/users"
+ request_data = variable_dict["user_1"]
+ fields_to_update = {"firstName": "{random_str}"}
+ request_data = get_updated_request_data(request_data=request_data,
fields_to_update=fields_to_update)
+ field_to_del = "loginId"
+ request_data = get_updated_request_data(request_data=request_data,
field_to_del=field_to_del)
+ logger.info("The request url is :- %s", request_url)
+ logger.info("The request data is :- %s", request_data)
+ resp = requests.put(
+ request_url, verify=False, auth=admin_auth, headers=headers,
data=json.dumps(request_data)
+ )
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 400, "Expected status code not returned"
+
+
+def test_edit_user_role_with_invalid_id():
+ request_url = base_url + "/users/{user_1_id}/roles"
+ request_url = request_url.format(**str_variable_dict)
+
+ request_data = get_request_data(
+ "test_edit_user_role_with_invalid_id.json", str_variable_dict,
test_data_path
+ )
+ logger.info("The request url is :- %s", request_url)
+ logger.info("The request data is :- %s", request_data)
+ resp = requests.put(
+ request_url, verify=False, auth=admin_auth, headers=headers,
data=json.dumps(request_data)
+ )
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 403, "Expected status code not returned"
+
+
+def test_emailchange_with_invalid_payload():
+ request_url = base_url + "/users/{user_1_id}/emailchange"
+ request_url = request_url.format(**str_variable_dict)
+
+ request_data = variable_dict["user_1"]
+ fields_to_update = {"oldPassword": "{random_str}"}
+ request_data = get_updated_request_data(request_data=request_data,
fields_to_update=fields_to_update)
+ logger.info("The request url is :- %s", request_url)
+ logger.info("The request data is :- %s", request_data)
+ resp = requests.post(
+ request_url, verify=False, auth=admin_auth, headers=headers,
data=json.dumps(request_data)
+ )
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 400, "Expected status code not returned"
+
+
+def test_change_email_with_null_body():
+ """Verify that null request body throws an error when attempting email
change."""
+ request_url = base_url + "/users/{user_1_id}/emailchange"
+ request_url = request_url.format(**str_variable_dict)
+
+ logger.info("The request url is :- %s", request_url)
+ resp = requests.post(request_url, verify=False, auth=admin_auth,
headers=headers, data="{}")
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 400, f"Expected 400 Bad Request, but got
{resp.status_code}"
+
+
+def test_change_email_with_empty_loginId():
+ """Verify that empty loginId is rejected when attempting email change."""
+ request_url = base_url + "/users/{user_1_id}/emailchange"
+ request_url = request_url.format(**str_variable_dict)
+
+ request_data = variable_dict["user_1"]
+ fields_to_update = {"loginId": "", "emailAddress":
"newemail_{random_str}@example.com"}
+ request_data = get_updated_request_data(request_data=request_data,
fields_to_update=fields_to_update)
+ logger.info("The request url is :- %s", request_url)
+ logger.info("The request data is :- %s", request_data)
+ resp = requests.post(
+ request_url, verify=False, auth=admin_auth, headers=headers,
data=json.dumps(request_data)
+ )
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 400, f"Expected 400 Bad Request, but got
{resp.status_code}"
+
+
+def test_change_email_with_mismatched_userId():
+ """Verify mismatched userId between path parameter and request body is
rejected."""
+ request_url = base_url + "/users/{user_1_id}/emailchange"
+ request_url = request_url.format(**str_variable_dict)
+ mismatched_id = 999999
+
+ request_data = variable_dict["user_1"]
+ fields_to_update = {"id": mismatched_id, "emailAddress":
"newemail_{random_str}@example.com"}
+ request_data = get_updated_request_data(request_data=request_data,
fields_to_update=fields_to_update)
+ logger.info("The request url is :- %s", request_url)
+ logger.info("The request data with mismatched ID is :- %s", request_data)
+ resp = requests.post(
+ request_url, verify=False, auth=admin_auth, headers=headers,
data=json.dumps(request_data)
+ )
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 400, f"Expected 400 Bad Request for ID
mismatch, but got {resp.status_code}"
+
+
+def test_change_email_with_nonexistent_loginId():
+ """Verify that non-existent loginId is rejected when attempting email
change."""
+ request_url = base_url + "/users/{user_1_id}/emailchange"
+ request_url = request_url.format(**str_variable_dict)
+
+ request_data = variable_dict["user_1"]
+ fields_to_update = {
+ "loginId": "nonexistent_user_{random_str}",
+ "emailAddress": "newemail_{random_str}@example.com",
+ }
+ request_data = get_updated_request_data(request_data=request_data,
fields_to_update=fields_to_update)
+ logger.info("The request url is :- %s", request_url)
+ logger.info("The request data with non-existent loginId is :- %s",
request_data)
+ resp = requests.post(
+ request_url, verify=False, auth=admin_auth, headers=headers,
data=json.dumps(request_data)
+ )
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert (
+ resp.status_code == 400
+ ), f"Expected 400 Bad Request for non-existent loginId, but got
{resp.status_code}"
+
+
+def test_password_change_with_invalid_payload():
+ request_url = base_url + "/users/{user_1_id}/passwordchange"
+ request_url = request_url.format(**str_variable_dict)
+
+ request_data = variable_dict["user_1"]
+ fields_to_update = {"oldPassword": "Password@123"}
+ request_data = get_updated_request_data(request_data=request_data,
fields_to_update=fields_to_update)
+ logger.info("The request url is :- %s", request_url)
+ logger.info("The request data is :- %s", request_data)
+ resp = requests.post(
+ request_url, verify=False, auth=admin_auth, headers=headers,
data=json.dumps(request_data)
+ )
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 400, "Expected status code not returned"
+
+
+def test_deactivate_user_with_invalid_id():
+ request_url = base_url + "/users/{random_num}/deactivate"
+ request_url = request_url.format(**str_variable_dict)
+
+ logger.info("The request url is :- %s", request_url)
+ resp = requests.post(request_url, verify=False, auth=admin_auth,
headers=headers)
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 400, "Expected status code not returned"
+
+
+def test_update_user_with_non_existent_id():
+ """Verify PUT to update a user profile with a userId that does not
exist."""
+ request_url = base_url + "/users"
+
+ request_data = {
+ "id": 999999,
+ "loginId": "admin",
+ "status": 1,
+ "firstName": "Admin",
+ "lastName": "",
+ "publicScreenName": "Admin",
+ "userSource": 0,
+ "userRoleList": ["ROLE_SYS_ADMIN"],
+ "userPermList": [],
+ "groupPermissions": [],
+ }
+
+ logger.info("The request url is :- %s", request_url)
+ logger.info("The request data is :- %s", request_data)
+ resp = requests.put(
+ request_url, verify=False, auth=admin_auth, headers=headers,
data=json.dumps(request_data)
+ )
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 403, "Expected status code 403 Forbidden not
returned"
+
+
+def test_get_user_profile_without_authentication():
+ """Verify getUserProfile() without authentication credentials."""
+ request_url = base_url + "/users/profile"
+ logger.info("The request url is :- %s", request_url)
+ resp = requests.get(request_url, verify=False, headers=headers)
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 401, f"Expected 401 Unauthorized, but got
{resp.status_code}"
+
+
+def test_change_password_with_null_body():
+ """Verify that null request body throws an error when attempting password
change."""
+ request_url = base_url + "/users/{user_1_id}/passwordchange"
+ request_url = request_url.format(**str_variable_dict)
+
+ logger.info("The request url is :- %s", request_url)
+ resp = requests.post(request_url, verify=False, auth=admin_auth,
headers=headers, data="{}")
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 400, f"Expected 400 Bad Request, but got
{resp.status_code}"
+
+
+def test_change_password_with_empty_loginId():
+ """Verify that empty loginId is rejected when attempting password
change."""
+ request_url = base_url + "/users/{user_1_id}/passwordchange"
+ request_url = request_url.format(**str_variable_dict)
+
+ request_data = variable_dict["user_1"]
+ fields_to_update = {
+ "loginId": "",
+ "oldPassword": "Password@123456789#!",
+ "updPassword": "Password@123456789#!56",
+ }
+ request_data = get_updated_request_data(request_data=request_data,
fields_to_update=fields_to_update)
+ logger.info("The request url is :- %s", request_url)
+ logger.info("The request data is :- %s", request_data)
+ resp = requests.post(
+ request_url, verify=False, auth=admin_auth, headers=headers,
data=json.dumps(request_data)
+ )
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 400, f"Expected 400 Bad Request, but got
{resp.status_code}"
+
+
+def test_change_password_with_mismatched_userId():
+ """Verify mismatched userId between path parameter and request body is
rejected."""
+ request_url = base_url + "/users/{user_1_id}/passwordchange"
+ request_url = request_url.format(**str_variable_dict)
+ mismatched_id = 999999
+
+ request_data = variable_dict["user_1"]
+ fields_to_update = {
+ "id": mismatched_id,
+ "oldPassword": "Password@123456789#!",
+ "updPassword": "Password@123456789#!56",
+ }
+ request_data = get_updated_request_data(request_data=request_data,
fields_to_update=fields_to_update)
+ logger.info("The request url is :- %s", request_url)
+ logger.info("The request data with mismatched ID is :- %s", request_data)
+ resp = requests.post(
+ request_url, verify=False, auth=admin_auth, headers=headers,
data=json.dumps(request_data)
+ )
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert resp.status_code == 400, f"Expected 400 Bad Request for ID
mismatch, but got {resp.status_code}"
+
+
+def test_change_password_with_nonexistent_loginId():
+ """Verify that non-existent loginId is rejected when attempting password
change."""
+ request_url = base_url + "/users/{user_1_id}/passwordchange"
+ request_url = request_url.format(**str_variable_dict)
+
+ request_data = variable_dict["user_1"]
+ fields_to_update = {
+ "loginId": "nonexistent_user_{random_str}",
+ "oldPassword": "Password@123456789#!",
+ "updPassword": "Password@123456789#!56",
+ }
+ request_data = get_updated_request_data(request_data=request_data,
fields_to_update=fields_to_update)
+ logger.info("The request url is :- %s", request_url)
+ logger.info("The request data with non-existent loginId is :- %s",
request_data)
+ resp = requests.post(
+ request_url, verify=False, auth=admin_auth, headers=headers,
data=json.dumps(request_data)
+ )
+ logger.info("The resp status code is :- %s", resp.status_code)
+ logger.info("The resp content is :- %s", resp.content)
+ assert (
+ resp.status_code == 400
+ ), f"Expected 400 Bad Request for non-existent loginId, but got
{resp.status_code}"
diff --git a/functional-tests/conftest.py b/functional-tests/conftest.py
new file mode 100644
index 000000000..1d7c35856
--- /dev/null
+++ b/functional-tests/conftest.py
@@ -0,0 +1,61 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Suite-wide pytest hooks (logging directory, per-module log files)."""
+
+import logging
+from pathlib import Path
+
+import pytest
+
+from ranger_test_utils.utils import LOGS_DIR, ensure_logs_directory
+
+LOG_FORMAT = "%(asctime)s %(levelname)s [%(name)s] %(message)s"
+LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
+
+
+def log_path_for_test_module(module_file):
+ """``logs/<test_module_stem>.log`` under functional-tests/."""
+ return LOGS_DIR / (Path(module_file).stem + ".log")
+
+
+def pytest_configure(config):
+ """Prepare logs/ and disable pytest.ini single shared log file (we use
per-module files)."""
+ ensure_logs_directory()
+ if getattr(config.option, "log_file", None):
+ config.option.log_file = None
+
+
[email protected](scope="module", autouse=True)
+def _log_to_module_file(request):
+ """Write logger.info (and above) to logs/<test_file_name>.log for each
test module."""
+ module = getattr(request, "module", None)
+ if module is None or not getattr(module, "__file__", None):
+ yield
+ return
+
+ log_path = log_path_for_test_module(module.__file__)
+ handler = logging.FileHandler(log_path, mode="w", encoding="utf-8")
+ handler.setLevel(logging.INFO)
+ handler.setFormatter(logging.Formatter(LOG_FORMAT, LOG_DATE_FORMAT))
+
+ root = logging.getLogger()
+ root.setLevel(logging.INFO)
+ root.addHandler(handler)
+ try:
+ yield
+ finally:
+ root.removeHandler(handler)
+ handler.close()
diff --git a/functional-tests/pytest.ini b/functional-tests/pytest.ini
index 132c00b2c..1cc7adb7e 100644
--- a/functional-tests/pytest.ini
+++ b/functional-tests/pytest.ini
@@ -27,11 +27,12 @@ markers =
secure_endpoint: secure endpoint test cases
rolerest: rolerest test cases
api: public API v2 test cases
+ api_test: legacy asset REST API test cases (admin/auditor/user/keyadmin)
hive: Hive plugin enforcement (Docker HS2)
hbase: HBase plugin enforcement (Docker HBase shell)
hdfs_policy: HDFS Ranger plugin path policy enforcement (Docker dfs)
pythonpath = .
-# No console logging during pytest; logger.info -> logs/pytest.log (tail -f
logs/pytest.log).
+# No console logging during pytest; logger.info -> logs/<test_module_name>.log
(see conftest.py).
log_cli = false
log_file = logs/pytest.log
log_file_mode = w
diff --git a/functional-tests/ranger_test_utils/api_data.py
b/functional-tests/ranger_test_utils/api_data.py
new file mode 100644
index 000000000..e2f137598
--- /dev/null
+++ b/functional-tests/ranger_test_utils/api_data.py
@@ -0,0 +1,59 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""JSON test-data path layout for migrated ranger-qe api_tests suites."""
+
+import os
+from pathlib import Path
+
+
+def api_data_paths(test_module_file, data_name, layout="suite"):
+ """
+ Resolve ``variable_jsons`` and ``test_jsons`` directories for a suite.
+
+ layout ``suite`` (functional-tests convention)::
+
+ <suite>/data/<data_name>/{variable_jsons,test_jsons}
+
+ layout ``ranger_qe`` (``api_tests/<area>/`` tests + shared
``api_tests/data/``)::
+
+ <area>/../data/<data_name>/{variable_jsons,test_jsons}
+ """
+ test_path = Path(test_module_file).resolve().parent
+ if layout == "ranger_qe":
+ data_folder = test_path.parent / "data" / data_name
+ else:
+ data_folder = test_path / "data" / data_name
+
+ return {
+ "data_folder_path": str(data_folder),
+ "variables_data_path": str(data_folder / "variable_jsons"),
+ "test_data_path": str(data_folder / "test_jsons"),
+ }
+
+
+def patch_json_file(json_path, updates):
+ """Load JSON from disk, apply ``updates`` dict, write back (setup_module
helper)."""
+ import json
+
+ with open(json_path, "r", encoding="utf-8") as json_file:
+ data = json.load(json_file)
+ data.update(updates)
+ with open(json_path, "w", encoding="utf-8") as json_file:
+ json.dump(data, json_file, indent=4)
+
+
+def join_under_variables(variables_data_path, filename):
+ return os.path.join(variables_data_path, filename)
diff --git a/functional-tests/ranger_test_utils/api_test_utils.py
b/functional-tests/ranger_test_utils/api_test_utils.py
new file mode 100644
index 000000000..e8d8df50e
--- /dev/null
+++ b/functional-tests/ranger_test_utils/api_test_utils.py
@@ -0,0 +1,580 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Ranger Admin REST helpers for migrating ranger-qe ``api_tests`` suites.
+
+This module mirrors ``ranger-qe/ranger_test_utils/api_test_utils.py`` without
+Beaver / XA dependencies. Migrated tests should import from here::
+
+ from ranger_test_utils.api_test_utils import (
+ base_url, admin_auth, headers, get_variable, get_request_data,
+ compare_response_data, create_required_test_users, logger,
+ )
+ from ranger_test_utils.api_data import api_data_paths
+
+Variable setup uses the same tuple spec as ranger-qe::
+
+ ('asset_id', 'POST', '/assets/assets', 'asset_id.json', 'id')
+
+Apache Ranger Docker functional-tests do not use CDH parcel version gates; port
+tests as-is and drop ``Version`` / ``skipif`` from ranger-qe when migrating.
+
+See ``api_tests/`` for a reference port of ``asset_rest_apis``.
+"""
+
+import copy
+import json
+import os
+import random
+import string
+
+import requests
+from requests.auth import HTTPBasicAuth
+
+from ranger_test_utils.test_env import get_env, hdfs_repo_name,
ranger_service_base_url
+from ranger_test_utils.utils import get_test_logger
+base_url = ranger_service_base_url()
+
+admin_user = get_env("XA_ADMIN_USER", "admin")
+keyadmin_user = get_env("XA_KEYADMIN_USER", "keyadmin")
+admin_password = get_env("XA_ADMIN_PASSWORD", "rangerR0cks!")
+keyadmin_password = get_env("XA_KEYADMIN_PASSWORD", "rangerR0cks!")
+
+user_role_user = "user_role_test_user"
+auditor_role_test_user = "auditor_role_test_user"
+kmsauditor_role_test_user = "kmsauditor_role_test_user"
+
+test_user_password = "Admin123"
+
+headers = {
+ "Content-Type": "application/json",
+ "Accept": "application/json",
+ "X-XSRF-HEADER": "valid",
+}
+
+admin_auth = HTTPBasicAuth(admin_user, admin_password)
+keyadmin_auth = HTTPBasicAuth(keyadmin_user, keyadmin_password)
+user_auth = HTTPBasicAuth(user_role_user, test_user_password)
+auditor_auth = HTTPBasicAuth(auditor_role_test_user, test_user_password)
+kmsauditor_auth = HTTPBasicAuth(kmsauditor_role_test_user, test_user_password)
+
+logger = get_test_logger(__name__)
+
+def return_random_str(length=10):
+ letters = string.ascii_lowercase + string.digits
+ return "".join(random.choice(letters) for _ in range(length))
+
+
+def compare_object(a, b):
+ if type(a) != type(b): # pylint: disable=unidiomatic-typecheck
+ return False
+ if isinstance(a, dict):
+ return compare_dict(a, b)
+ if isinstance(a, list):
+ return compare_list(a, b)
+ return a == b
+
+
+def compare_dict(a, b):
+ if len(a) != len(b):
+ return False
+ for k, v in a.items():
+ if k not in b:
+ return False
+ if not compare_object(v, b[k]):
+ return False
+ return True
+
+
+def compare_list(a, b):
+ if len(a) != len(b):
+ return False
+ for i, _ in enumerate(a):
+ if not compare_object(a[i], b[i]):
+ return False
+ return True
+
+
+def get_ignore_fields_for_comparison():
+ """Fields ignored when ``compare_response_data(...,
complete_comparison=True)``."""
+ return [
+ "id",
+ "guid",
+ "createdBy",
+ "updatedBy",
+ "createTime",
+ "updateTime",
+ "version",
+ "resourceSignature",
+ "lastLoginTime",
+ ]
+
+
+def compare_response_data(response_data, expected_response_data,
complete_comparison=False):
+ logger.info("The response data is :- %s", response_data)
+ logger.info("The expected response data is :- %s", expected_response_data)
+ logger.info("Complete comparison = %s", complete_comparison)
+
+ if complete_comparison:
+ json_obj_1 = copy.deepcopy(response_data)
+ json_obj_2 = copy.deepcopy(expected_response_data)
+
+ for dict_key in json_obj_1:
+ if dict_key in get_ignore_fields_for_comparison():
+ json_obj_1[dict_key] = None
+ json_obj_2[dict_key] = None
+
+ for dict_key in json_obj_2:
+ if dict_key in get_ignore_fields_for_comparison():
+ json_obj_1[dict_key] = None
+ json_obj_2[dict_key] = None
+
+ assert compare_dict(json_obj_1, json_obj_2), (
+ "Obtained response not matching the expected response"
+ )
+ else:
+ for key in expected_response_data:
+ logger.info("The key being compared is :- %s", key)
+ assert response_data[key] == expected_response_data[key], (
+ "Obtained response not matching expected response"
+ )
+
+
+def compare_response_data_without_assert(
+ response_data, expected_response_data, complete_comparison=False
+):
+ logger.info("The response data is :- %s", response_data)
+ logger.info("The expected response data is :- %s", expected_response_data)
+ logger.info("Complete comparison = %s", complete_comparison)
+
+ if complete_comparison:
+ json_obj_1 = copy.deepcopy(response_data)
+ json_obj_2 = copy.deepcopy(expected_response_data)
+
+ for dict_key in json_obj_1:
+ if dict_key in get_ignore_fields_for_comparison():
+ json_obj_1[dict_key] = None
+ json_obj_2[dict_key] = None
+
+ for dict_key in json_obj_2:
+ if dict_key in get_ignore_fields_for_comparison():
+ json_obj_1[dict_key] = None
+ json_obj_2[dict_key] = None
+
+ return compare_dict(json_obj_1, json_obj_2)
+ for key in expected_response_data:
+ logger.info("The key being compared is :- %s", key)
+ if response_data.get(key) != expected_response_data[key]:
+ return False
+ return True
+
+
+def check_if_response_list_contains_expected_obj(response_list, expected_obj):
+ is_expected_obj_found = False
+ for json_obj in response_list:
+ if compare_response_data_without_assert(json_obj, expected_obj, True):
+ is_expected_obj_found = True
+ break
+ assert is_expected_obj_found, "Expected response obj not found"
+
+
+def _delete_user_if_exists(username):
+ request_url = base_url + "/xusers/users/userName/{}".format(username)
+ resp = requests.get(request_url, auth=admin_auth, verify=False,
headers=headers)
+ if resp.status_code == 200:
+ delete_url = request_url + "?forceDelete=true"
+ resp = requests.delete(delete_url, verify=False, auth=admin_auth,
headers=headers)
+ assert resp.status_code == 204, "Existing user deletion failed"
+
+
+def create_required_test_users():
+ """Create ``user_role_test_user`` and ``auditor_role_test_user`` for role
API tests."""
+ for username, role in (
+ (user_role_user, "ROLE_USER"),
+ (auditor_role_test_user, "ROLE_ADMIN_AUDITOR"),
+ ):
+ _delete_user_if_exists(username)
+
+ request_payload = {
+ "name": username,
+ "firstName": username,
+ "password": test_user_password,
+ "description": username,
+ "groupIdList": [],
+ "groupNameList": [],
+ "status": 1,
+ "isVisible": 1,
+ "userSource": 0,
+ "userRoleList": [role],
+ }
+
+ request_url = base_url + "/xusers/secure/users"
+ resp = requests.post(
+ request_url,
+ verify=False,
+ auth=admin_auth,
+ data=json.dumps(request_payload),
+ headers=headers,
+ )
+ assert resp.status_code == 200, "Test user creation failed"
+
+
+def create_kmsauditor_test_user():
+ """Create ``kmsauditor_role_test_user`` with ``ROLE_KEY_ADMIN_AUDITOR``
(keyadmin promote)."""
+ username = kmsauditor_role_test_user
+ request_url = base_url + "/xusers/users/userName/{}".format(username)
+
+ resp = requests.get(request_url, auth=admin_auth, verify=False,
headers=headers)
+ if resp.status_code == 200:
+ delete_url = request_url + "?forceDelete=true"
+ resp = requests.delete(delete_url, verify=False, auth=admin_auth,
headers=headers)
+ assert resp.status_code == 204, "Existing kmsauditor user deletion
failed"
+
+ request_payload = {
+ "name": username,
+ "firstName": username,
+ "password": test_user_password,
+ "description": username,
+ "groupIdList": [],
+ "groupNameList": [],
+ "status": 1,
+ "isVisible": 1,
+ "userSource": 0,
+ "userRoleList": ["ROLE_USER"],
+ }
+
+ request_url = base_url + "/xusers/secure/users"
+ resp = requests.post(
+ request_url,
+ verify=False,
+ auth=admin_auth,
+ data=json.dumps(request_payload),
+ headers=headers,
+ )
+ if resp.status_code != 200:
+ request_url = base_url + "/xusers/users"
+ resp = requests.post(
+ request_url,
+ verify=False,
+ auth=admin_auth,
+ data=json.dumps(request_payload),
+ headers=headers,
+ )
+
+ assert resp.status_code == 200, (
+ "KMS auditor user creation failed. Status: {}, Response: {}".format(
+ resp.status_code, resp.text
+ )
+ )
+
+ user_data = resp.json()
+ user_id = user_data["id"]
+
+ update_payload = {
+ "id": user_id,
+ "name": username,
+ "firstName": username,
+ "password": test_user_password,
+ "description": username,
+ "groupIdList": [],
+ "groupNameList": [],
+ "status": 1,
+ "isVisible": 1,
+ "userSource": 0,
+ "userRoleList": ["ROLE_KEY_ADMIN_AUDITOR"],
+ }
+
+ update_url = base_url + "/xusers/secure/users/{}".format(user_id)
+ resp = requests.put(
+ update_url,
+ verify=False,
+ auth=keyadmin_auth,
+ data=json.dumps(update_payload),
+ headers=headers,
+ )
+ if resp.status_code != 200:
+ update_url = base_url + "/xusers/users/{}".format(user_id)
+ resp = requests.put(
+ update_url,
+ verify=False,
+ auth=keyadmin_auth,
+ data=json.dumps(update_payload),
+ headers=headers,
+ )
+
+ assert resp.status_code == 200, (
+ "KMS auditor user role update failed. Status: {}, Response: {}".format(
+ resp.status_code, resp.text
+ )
+ )
+
+
+def get_variable(
+ variable_specification, variable_dict, data_folder_path, is_keyadmin=False
+):
+ user = keyadmin_user if is_keyadmin else admin_user
+ password = keyadmin_password if is_keyadmin else admin_password
+ auth = HTTPBasicAuth(user, password)
+
+ variable_name = variable_specification[0]
+ request_method = variable_specification[1]
+
+ request_url = base_url + variable_specification[2]
+ request_url = request_url.format(**variable_dict)
+
+ response_to_be_saved = variable_specification[4]
+ request_payload = None
+
+ if variable_specification[3] is not None:
+ request_data_file_path = os.path.join(data_folder_path,
variable_specification[3])
+ logger.info("The file path is :- %s", request_data_file_path)
+
+ with open(request_data_file_path, "r", encoding="utf-8") as fp:
+ request_payload = fp.read()
+ request_payload = request_payload.replace("{random_str}",
return_random_str(7))
+
+ for key in variable_dict:
+ regex = "{" + str(key) + "}"
+ if regex in request_payload:
+ request_payload = request_payload.replace(regex,
str(variable_dict[key]))
+
+ if request_method == "POST":
+ resp = requests.post(
+ request_url, data=request_payload, verify=False, auth=auth,
headers=headers
+ )
+ elif request_method == "GET":
+ resp = requests.get(request_url, verify=False, auth=auth,
headers=headers)
+ else:
+ raise ValueError("Unsupported HTTP method: {}".format(request_method))
+
+ logger.info("response status: %s, response body: %s", resp.status_code,
resp.content)
+
+ assert resp.status_code in (200, 201), (
+ "get_variable failed for {}: {} {}".format(
+ variable_name, resp.status_code, resp.text
+ )
+ )
+
+ if response_to_be_saved == "same":
+ variable = resp.json()
+ else:
+ dict_path = response_to_be_saved.split(",")
+ variable = resp.json()
+ for key in dict_path:
+ if isinstance(variable, list):
+ key = int(key)
+ variable = variable[key]
+
+ logger.info("The variable %s is %s", variable_name, variable)
+ return variable
+
+
+def get_request_data(file_name, variable_dict, test_data_path):
+ file_path = os.path.join(test_data_path, file_name)
+
+ with open(file_path, "r", encoding="utf-8") as fp:
+ request_payload = fp.read()
+ request_payload = request_payload.replace("{random_str}",
return_random_str(7))
+
+ for key in variable_dict:
+ regex = "{" + str(key) + "}"
+ if regex in request_payload:
+ request_payload = request_payload.replace(regex,
variable_dict[key])
+
+ return json.loads(request_payload)
+
+
+def get_updated_request_data(request_data, fields_to_update=None,
field_to_del=None):
+ request_payload = copy.deepcopy(request_data)
+ fields_to_update = json.dumps(fields_to_update)
+ fields_to_update = fields_to_update.replace("{random_str}",
return_random_str(7))
+ fields_to_update = json.loads(fields_to_update)
+
+ if fields_to_update:
+ logger.info("The fields to update are :- %s", fields_to_update)
+ for key in fields_to_update:
+ request_payload[key] = fields_to_update[key]
+
+ if field_to_del:
+ logger.info("The field to del is :- %s", field_to_del)
+ del request_payload[field_to_del]
+
+ return request_payload
+
+
+def get_expected_response_data(expected_response_data_str, variable_dict):
+ for key in variable_dict:
+ regex = "{" + str(key) + "}"
+ if regex in expected_response_data_str:
+ expected_response_data_str = expected_response_data_str.replace(
+ regex, variable_dict[key]
+ )
+ return expected_response_data_str
+
+
+def create_custom_user(
+ user_name,
+ first_name=None,
+ last_name=None,
+ email=None,
+ password=None,
+ user_roles=None,
+):
+ if first_name is None:
+ first_name = user_name
+ if last_name is None:
+ last_name = "{}_lstname".format(user_name)
+ if email is None:
+ email = "{}@test.com".format(user_name)
+ if password is None:
+ password = test_user_password
+ if user_roles is None:
+ user_roles = ["ROLE_USER"]
+
+ user_data = {
+ "name": user_name,
+ "firstName": first_name,
+ "lastName": last_name,
+ "emailAddress": email,
+ "password": password,
+ "userRoleList": user_roles,
+ "groupIdList": [],
+ "groupNameList": [],
+ "status": 1,
+ "isVisible": 1,
+ "userSource": 0,
+ }
+
+ response = requests.post(
+ "{}/xusers/secure/users".format(base_url),
+ json=user_data,
+ auth=admin_auth,
+ headers=headers,
+ verify=False,
+ )
+
+ if response.status_code in (200, 201):
+ logger.info("Successfully created user: %s", user_name)
+ return response.json()
+ logger.error(
+ "Failed to create user %s: %s - %s",
+ user_name,
+ response.status_code,
+ response.text,
+ )
+ return None
+
+
+def create_custom_group(group_name, description=None, group_type=1):
+ if description is None:
+ description = "Test group {} for testing".format(group_name)
+
+ group_data = {
+ "name": group_name,
+ "description": description,
+ "groupType": group_type,
+ }
+
+ response = requests.post(
+ "{}/xusers/secure/groups".format(base_url),
+ json=group_data,
+ auth=admin_auth,
+ headers=headers,
+ verify=False,
+ )
+
+ if response.status_code in (200, 201):
+ logger.info("Successfully created group: %s", group_name)
+ return response.json()
+ logger.error(
+ "Failed to create group %s: %s - %s",
+ group_name,
+ response.status_code,
+ response.text,
+ )
+ return None
+
+
+def create_custom_role(role_name, description=None, users=None, groups=None,
roles=None):
+ if description is None:
+ description = "Test role {} for testing".format(role_name)
+ if users is None:
+ users = []
+ if groups is None:
+ groups = []
+ if roles is None:
+ roles = []
+
+ role_data = {
+ "name": role_name,
+ "description": description,
+ "users": users,
+ "groups": groups,
+ "roles": roles,
+ }
+
+ response = requests.post(
+ "{}/roles/roles".format(base_url),
+ json=role_data,
+ auth=admin_auth,
+ headers=headers,
+ verify=False,
+ )
+
+ if response.status_code in (200, 201):
+ logger.info("Successfully created role: %s", role_name)
+ return response.json()
+ logger.error(
+ "Failed to create role %s: %s - %s",
+ role_name,
+ response.status_code,
+ response.text,
+ )
+ return None
+
+
+# Re-export for setup_module blocks that used getEnv / repo name helpers.
+getEnv = get_env # pylint: disable=invalid-name
+
+__all__ = [
+ "admin_auth",
+ "auditor_auth",
+ "base_url",
+ "check_if_response_list_contains_expected_obj",
+ "compare_response_data",
+ "compare_response_data_without_assert",
+ "create_custom_group",
+ "create_custom_role",
+ "create_custom_user",
+ "create_kmsauditor_test_user",
+ "create_required_test_users",
+ "getEnv",
+ "get_expected_response_data",
+ "get_request_data",
+ "get_updated_request_data",
+ "get_variable",
+ "headers",
+ "hdfs_repo_name",
+ "keyadmin_auth",
+ "kmsauditor_auth",
+ "logger",
+ "return_random_str",
+ "test_user_password",
+ "user_auth",
+ "user_role_user",
+ "auditor_role_test_user",
+ "kmsauditor_role_test_user",
+]
diff --git a/functional-tests/ranger_test_utils/test_env.py
b/functional-tests/ranger_test_utils/test_env.py
new file mode 100644
index 000000000..e5b3d68e5
--- /dev/null
+++ b/functional-tests/ranger_test_utils/test_env.py
@@ -0,0 +1,53 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Environment defaults for ranger-qe-style API tests on Docker Ranger
Admin."""
+
+import os
+
+from ranger_test_utils.utils import RANGER_ADMIN_BASE_URL
+
+# Docker functional-tests defaults (override via env vars).
+_ENV_DEFAULTS = {
+ "XA_ADMIN_USER": "admin",
+ "XA_ADMIN_PASSWORD": "rangerR0cks!",
+ "XA_KEYADMIN_USER": "keyadmin",
+ "XA_KEYADMIN_PASSWORD": "rangerR0cks!",
+ "XA_HDFS_REPO_NAME": "dev_hdfs",
+ "RANGER_ADMIN_BASE_URL": RANGER_ADMIN_BASE_URL,
+}
+
+
+def get_env(name, default=None):
+ """
+ Read a test env var (os.environ first, then functional-tests defaults).
+
+ Matches ranger-qe ``getEnv`` call sites: ``get_env('XA_ADMIN_USER',
'admin')``.
+ """
+ if name in os.environ:
+ return os.environ[name]
+ if default is not None:
+ return default
+ return _ENV_DEFAULTS.get(name)
+
+
+def ranger_service_base_url():
+ """Ranger Admin REST base (``.../service``), same as ranger-qe
``base_url``."""
+ return get_env("RANGER_ADMIN_BASE_URL", RANGER_ADMIN_BASE_URL).rstrip("/")
+
+
+def hdfs_repo_name():
+ """HDFS repository/service name seeded in Docker
(``create-ranger-services.py``)."""
+ return get_env("XA_HDFS_REPO_NAME", "dev_hdfs")
diff --git a/functional-tests/ranger_test_utils/utils.py
b/functional-tests/ranger_test_utils/utils.py
index 534c8bfac..d751f5a62 100644
--- a/functional-tests/ranger_test_utils/utils.py
+++ b/functional-tests/ranger_test_utils/utils.py
@@ -201,7 +201,7 @@ def ensure_user_exists(
session,
username,
password="Test@123",
- last_name="api_test",
+ last_name="api_tests",
):
"""Create a Ranger user via xusers REST if it is not already present."""
lookup_url = XUSERS_BASE + "/users/userName/" + username
diff --git a/functional-tests/run-tests.sh b/functional-tests/run-tests.sh
index bd3d4940b..e2d797a6f 100755
--- a/functional-tests/run-tests.sh
+++ b/functional-tests/run-tests.sh
@@ -17,10 +17,10 @@
# All available test suites (pytest folders)
-ALL_TEST_SUITES=(rolerest xuserrest servicerest tagrest hdfs kms api hive
hbase)
+ALL_TEST_SUITES=(rolerest xuserrest servicerest tagrest hdfs kms api api_tests
hive hbase)
# Test-only suites (no docker-compose file needed)
-TEST_ONLY_SUITES=(rolerest xuserrest servicerest tagrest api)
+TEST_ONLY_SUITES=(rolerest xuserrest servicerest tagrest api api_tests)
# All optional containers (on top of base services) — shown in the containers
prompt
# Each maps to a docker-compose file and a set of archives to download.