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.

Reply via email to