# Copyright (c) Ansible project # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # SPDX-License-Identifier: GPL-3.0-or-later from __future__ import annotations import unittest from unittest.mock import patch from ansible.module_utils import basic from ansible_collections.community.internal_test_tools.tests.unit.plugins.modules.utils import ( AnsibleExitJson, AnsibleFailJson, exit_json, fail_json, set_module_args, ) import ansible_collections.community.general.plugins.modules.ocapi_info as module MOCK_BASE_URI = "mockBaseUri" MOCK_JOB_NAME_IN_PROGRESS = "MockJobInProgress" MOCK_JOB_NAME_COMPLETE = "MockJobComplete" MOCK_JOB_NAME_DOES_NOT_EXIST = "MockJobDoesNotExist" ACTION_WAS_SUCCESSFUL = "Action was successful." MOCK_SUCCESSFUL_HTTP_RESPONSE = {"ret": True, "data": {}} MOCK_404_RESPONSE = {"ret": False, "status": 404} MOCK_HTTP_RESPONSE_JOB_IN_PROGRESS = { "ret": True, "data": { "Self": "https://openflex-data24-usalp02120qo0012-iomb:443/Storage/Devices/openflex-data24-usalp02120qo0012/Jobs/FirmwareUpdate/", "ID": MOCK_JOB_NAME_IN_PROGRESS, "PercentComplete": 10, "Status": {"State": {"ID": 16, "Name": "In service"}, "Health": [{"ID": 5, "Name": "OK"}]}, }, } MOCK_HTTP_RESPONSE_JOB_COMPLETE = { "ret": True, "data": { "Self": "https://openflex-data24-usalp02120qo0012-iomb:443/Storage/Devices/openflex-data24-usalp02120qo0012/Jobs/FirmwareUpdate/", "ID": MOCK_JOB_NAME_COMPLETE, "PercentComplete": 100, "Status": { "State": {"ID": 65540, "Name": "Activate needed"}, "Health": [{"ID": 5, "Name": "OK"}], "Details": ["Completed."], }, }, } def get_bin_path(self, arg, required=False): """Mock AnsibleModule.get_bin_path""" return arg def get_exception_message(ansible_exit_json): """From an AnsibleExitJson exception, get the message string.""" return ansible_exit_json.exception.args[0]["msg"] def mock_get_request(*args, **kwargs): """Mock for get_request.""" url = args[1] if url == f"https://{MOCK_BASE_URI}": return MOCK_SUCCESSFUL_HTTP_RESPONSE elif url == f"https://{MOCK_BASE_URI}/Jobs/{MOCK_JOB_NAME_IN_PROGRESS}": return MOCK_HTTP_RESPONSE_JOB_IN_PROGRESS elif url == f"https://{MOCK_BASE_URI}/Jobs/{MOCK_JOB_NAME_COMPLETE}": return MOCK_HTTP_RESPONSE_JOB_COMPLETE elif url == f"https://{MOCK_BASE_URI}/Jobs/{MOCK_JOB_NAME_DOES_NOT_EXIST}": return MOCK_404_RESPONSE else: raise RuntimeError(f"Illegal GET call to: {args[1]}") def mock_put_request(*args, **kwargs): """Mock put_request. PUT should never happen so it will raise an error.""" raise RuntimeError(f"Illegal PUT call to: {args[1]}") def mock_delete_request(*args, **kwargs): """Mock delete request. DELETE should never happen so it will raise an error.""" raise RuntimeError(f"Illegal DELETE call to: {args[1]}") def mock_post_request(*args, **kwargs): """Mock post_request. POST should never happen so it will raise an error.""" raise RuntimeError(f"Illegal POST call to: {args[1]}") class TestOcapiInfo(unittest.TestCase): def setUp(self): self.mock_module_helper = patch.multiple( basic.AnsibleModule, exit_json=exit_json, fail_json=fail_json, get_bin_path=get_bin_path ) self.mock_module_helper.start() self.addCleanup(self.mock_module_helper.stop) def test_module_fail_when_required_args_missing(self): with self.assertRaises(AnsibleFailJson) as ansible_fail_json: with set_module_args({}): module.main() self.assertIn("missing required arguments:", get_exception_message(ansible_fail_json)) def test_module_fail_when_unknown_category(self): with self.assertRaises(AnsibleFailJson) as ansible_fail_json: with set_module_args( { "category": "unknown", "command": "JobStatus", "username": "USERID", "password": "PASSW0RD=21", "baseuri": MOCK_BASE_URI, } ): module.main() self.assertIn("Invalid Category 'unknown", get_exception_message(ansible_fail_json)) def test_module_fail_when_unknown_command(self): with self.assertRaises(AnsibleFailJson) as ansible_fail_json: with set_module_args( { "category": "Jobs", "command": "unknown", "username": "USERID", "password": "PASSW0RD=21", "baseuri": MOCK_BASE_URI, } ): module.main() self.assertIn("Invalid Command 'unknown", get_exception_message(ansible_fail_json)) def test_job_status_in_progress(self): with patch.multiple( "ansible_collections.community.general.plugins.module_utils.ocapi_utils.OcapiUtils", get_request=mock_get_request, put_request=mock_put_request, delete_request=mock_delete_request, post_request=mock_post_request, ): with self.assertRaises(AnsibleExitJson) as ansible_exit_json: with set_module_args( { "category": "Jobs", "command": "JobStatus", "job_name": MOCK_JOB_NAME_IN_PROGRESS, "baseuri": MOCK_BASE_URI, "username": "USERID", "password": "PASSWORD=21", } ): module.main() self.assertEqual(ACTION_WAS_SUCCESSFUL, get_exception_message(ansible_exit_json)) response_data = ansible_exit_json.exception.args[0] self.assertEqual( MOCK_HTTP_RESPONSE_JOB_IN_PROGRESS["data"]["PercentComplete"], response_data["percentComplete"] ) self.assertEqual( MOCK_HTTP_RESPONSE_JOB_IN_PROGRESS["data"]["Status"]["State"]["ID"], response_data["operationStatusId"] ) self.assertEqual( MOCK_HTTP_RESPONSE_JOB_IN_PROGRESS["data"]["Status"]["State"]["Name"], response_data["operationStatus"] ) self.assertEqual( MOCK_HTTP_RESPONSE_JOB_IN_PROGRESS["data"]["Status"]["Health"][0]["Name"], response_data["operationHealth"], ) self.assertEqual( MOCK_HTTP_RESPONSE_JOB_IN_PROGRESS["data"]["Status"]["Health"][0]["ID"], response_data["operationHealthId"], ) self.assertTrue(response_data["jobExists"]) self.assertFalse(response_data["changed"]) self.assertEqual(ACTION_WAS_SUCCESSFUL, response_data["msg"]) self.assertIsNone(response_data["details"]) def test_job_status_complete(self): with patch.multiple( "ansible_collections.community.general.plugins.module_utils.ocapi_utils.OcapiUtils", get_request=mock_get_request, put_request=mock_put_request, delete_request=mock_delete_request, post_request=mock_post_request, ): with self.assertRaises(AnsibleExitJson) as ansible_exit_json: with set_module_args( { "category": "Jobs", "command": "JobStatus", "job_name": MOCK_JOB_NAME_COMPLETE, "baseuri": MOCK_BASE_URI, "username": "USERID", "password": "PASSWORD=21", } ): module.main() self.assertEqual(ACTION_WAS_SUCCESSFUL, get_exception_message(ansible_exit_json)) response_data = ansible_exit_json.exception.args[0] self.assertEqual( MOCK_HTTP_RESPONSE_JOB_COMPLETE["data"]["PercentComplete"], response_data["percentComplete"] ) self.assertEqual( MOCK_HTTP_RESPONSE_JOB_COMPLETE["data"]["Status"]["State"]["ID"], response_data["operationStatusId"] ) self.assertEqual( MOCK_HTTP_RESPONSE_JOB_COMPLETE["data"]["Status"]["State"]["Name"], response_data["operationStatus"] ) self.assertEqual( MOCK_HTTP_RESPONSE_JOB_COMPLETE["data"]["Status"]["Health"][0]["Name"], response_data["operationHealth"] ) self.assertEqual( MOCK_HTTP_RESPONSE_JOB_COMPLETE["data"]["Status"]["Health"][0]["ID"], response_data["operationHealthId"] ) self.assertTrue(response_data["jobExists"]) self.assertFalse(response_data["changed"]) self.assertEqual(ACTION_WAS_SUCCESSFUL, response_data["msg"]) self.assertEqual(["Completed."], response_data["details"]) def test_job_status_not_found(self): with patch.multiple( "ansible_collections.community.general.plugins.module_utils.ocapi_utils.OcapiUtils", get_request=mock_get_request, put_request=mock_put_request, delete_request=mock_delete_request, post_request=mock_post_request, ): with self.assertRaises(AnsibleExitJson) as ansible_exit_json: with set_module_args( { "category": "Jobs", "command": "JobStatus", "job_name": MOCK_JOB_NAME_DOES_NOT_EXIST, "baseuri": MOCK_BASE_URI, "username": "USERID", "password": "PASSWORD=21", } ): module.main() self.assertEqual(ACTION_WAS_SUCCESSFUL, get_exception_message(ansible_exit_json)) response_data = ansible_exit_json.exception.args[0] self.assertFalse(response_data["jobExists"]) self.assertEqual(0, response_data["percentComplete"]) self.assertEqual(1, response_data["operationStatusId"]) self.assertEqual("Not Available", response_data["operationStatus"]) self.assertIsNone(response_data["operationHealth"]) self.assertIsNone(response_data["operationHealthId"]) self.assertFalse(response_data["changed"]) self.assertEqual(ACTION_WAS_SUCCESSFUL, response_data["msg"]) self.assertEqual("Job does not exist.", response_data["details"])