1# -*- coding: utf-8 -*-
2
3#
4# Dell EMC OpenManage Ansible Modules
5# Version 2.1.3
6# Copyright (C) 2019-2020 Dell Inc. or its subsidiaries. All Rights Reserved.
7
8# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
9#
10
11from __future__ import (absolute_import, division, print_function)
12
13__metaclass__ = type
14
15import pytest
16from ansible.module_utils.six.moves.urllib.error import HTTPError
17from ansible_collections.dellemc.openmanage.plugins.modules import ome_device_info
18from ansible_collections.dellemc.openmanage.tests.unit.plugins.modules.common import FakeAnsibleModule, Constants
19
20resource_basic_inventory = {"basic_inventory": "DeviceService/Devices"}
21resource_detailed_inventory = {"detailed_inventory:": {"device_id": {Constants.device_id1: None},
22                                                       "device_service_tag": {
23                                                           Constants.device_id2: Constants.service_tag1}}}
24
25MODULE_PATH = 'ansible_collections.dellemc.openmanage.plugins.modules.'
26
27
28class TestOmeDeviceInfo(FakeAnsibleModule):
29    module = ome_device_info
30
31    @pytest.fixture
32    def validate_device_inputs_mock(self, mocker):
33        validate_device_inputs_mock = mocker.patch(MODULE_PATH + 'ome_device_info._validate_inputs')
34        validate_device_inputs_mock.return_value = None
35
36    @pytest.fixture
37    def get_device_resource_parameters_mock(self, mocker):
38        response_class_mock = mocker.patch(MODULE_PATH + 'ome_device_info._get_resource_parameters',
39                                           return_value=resource_basic_inventory)
40        return response_class_mock
41
42    def test_main_basic_inventory_success_case(self, ome_default_args, module_mock, validate_device_inputs_mock,
43                                               ome_connection_mock,
44                                               get_device_resource_parameters_mock, ome_response_mock):
45        ome_response_mock.json_data = {"@odata.context": "/api/$metadata#Collection(DeviceService.Device)",
46                                       "@odata.count": 1}
47        increment_device_details = {"resp_obj": ome_response_mock,
48                                    "report_list": [{"DeviceServiceTag": Constants.service_tag1,
49                                                     "Id": Constants.device_id1}]}
50        ome_connection_mock.get_all_report_details.return_value = increment_device_details
51        ome_response_mock.status_code = 200
52        result = self._run_module(ome_default_args)
53        assert result['changed'] is False
54        assert 'device_info' in result
55        assert result["device_info"] == {"@odata.context": "/api/$metadata#Collection(DeviceService.Device)",
56                                         "@odata.count": 1,
57                                         "value": [{"DeviceServiceTag": Constants.service_tag1,
58                                                    "Id": Constants.device_id1}]}
59
60    def test_main_basic_inventory_query_param_success_case(self, mocker, ome_default_args, module_mock,
61                                                           validate_device_inputs_mock, ome_connection_mock,
62                                                           get_device_resource_parameters_mock, ome_response_mock):
63        quer_param_mock = mocker.patch(MODULE_PATH + 'ome_device_info._get_query_parameters')
64        quer_param_mock.return_value = {"filter": "Type eq '1000'"}
65        ome_response_mock.json_data = {"value": [{"device_id1": "details", "device_id2": "details"}]}
66        ome_response_mock.status_code = 200
67        result = self._run_module(ome_default_args)
68        assert result['changed'] is False
69        assert 'device_info' in result
70        assert result["device_info"] == {"value": [{"device_id1": "details", "device_id2": "details"}]}
71
72    def test_main_basic_inventory_failure_case(self, ome_default_args, module_mock, validate_device_inputs_mock,
73                                               ome_connection_mock,
74                                               get_device_resource_parameters_mock, ome_response_mock):
75        ome_response_mock.status_code = 500
76        ome_response_mock.json_data = {"@odata.context": "/api/$metadata#Collection(DeviceService.Device)",
77                                       "@odata.count": 0}
78        ome_connection_mock.get_all_report_details.return_value = {"resp_obj": ome_response_mock, "report_list": []}
79        result = self._run_module_with_fail_json(ome_default_args)
80        assert result['msg'] == 'Failed to fetch the device information'
81
82    def test_main_detailed_inventory_success_case(self, ome_default_args, module_mock, validate_device_inputs_mock,
83                                                  ome_connection_mock,
84                                                  get_device_resource_parameters_mock, ome_response_mock):
85        ome_default_args.update(
86            {"fact_subset": "detailed_inventory", "system_query_options": {"device_id": [Constants.device_id1],
87                                                                           "device_service_tag": [
88                                                                               Constants.service_tag1]}})
89        detailed_inventory = {"detailed_inventory:": {
90            "device_id": {Constants.device_id1: "DeviceService/Devices(Constants.device_id1)/InventoryDetails"},
91            "device_service_tag": {Constants.service_tag1: "DeviceService/Devices(4321)/InventoryDetails"}}}
92        get_device_resource_parameters_mock.return_value = detailed_inventory
93        ome_response_mock.json_data = {
94            "value": [{"device_id": {"1234": "details"}}, {"device_service_tag": {Constants.service_tag1: "details"}}]}
95        ome_response_mock.status_code = 200
96        result = self._run_module(ome_default_args)
97        assert result['changed'] is False
98        assert 'device_info' in result
99
100    def test_main_detailed_inventory_http_error_case(self, ome_default_args, module_mock, validate_device_inputs_mock,
101                                                     ome_connection_mock,
102                                                     get_device_resource_parameters_mock, ome_response_mock):
103        ome_default_args.update(
104            {"fact_subset": "detailed_inventory", "system_query_options": {"device_id": [Constants.device_id1],
105                                                                           "device_service_tag": [
106                                                                               Constants.service_tag1]}})
107        detailed_inventory = {"detailed_inventory:": {
108            "device_id": {Constants.device_id1: "DeviceService/Devices(Constants.device_id1)/InventoryDetails"},
109            "device_service_tag": {Constants.service_tag1: "DeviceService/Devices(4321)/InventoryDetails"}}}
110        get_device_resource_parameters_mock.return_value = detailed_inventory
111        ome_connection_mock.invoke_request.side_effect = HTTPError('http://testhost.com', 400, '', {}, None)
112        result = self._run_module_with_fail_json(ome_default_args)
113        assert 'device_info' not in result
114
115    def test_main_HTTPError_error_case(self, ome_default_args, module_mock, validate_device_inputs_mock,
116                                       ome_connection_mock,
117                                       get_device_resource_parameters_mock, ome_response_mock):
118        ome_connection_mock.invoke_request.side_effect = HTTPError('http://testhost.com', 400, '', {}, None)
119        ome_response_mock.json_data = {"value": [{"device_id1": "details", "device_id2": "details"}]}
120        ome_response_mock.status_code = 400
121        result = self._run_module_with_fail_json(ome_default_args)
122        assert 'device_info' not in result
123        assert result['failed'] is True
124
125    @pytest.mark.parametrize("fact_subset, mutually_exclusive_call",
126                             [("basic_inventory", False), ("detailed_inventory", True)])
127    def test_validate_inputs(self, fact_subset, mutually_exclusive_call, mocker):
128        module_params = {"fact_subset": fact_subset}
129        check_mutually_inclusive_arguments_mock = mocker.patch(MODULE_PATH +
130                                                               'ome_device_info._check_mutually_inclusive_arguments')
131        check_mutually_inclusive_arguments_mock.return_value = None
132        self.module._validate_inputs(module_params)
133        if mutually_exclusive_call:
134            check_mutually_inclusive_arguments_mock.assert_called()
135        else:
136            check_mutually_inclusive_arguments_mock.assert_not_called()
137        check_mutually_inclusive_arguments_mock.reset_mock()
138
139    system_query_options_params = [{"system_query_options": None}, {"system_query_options": {"device_id": None}},
140                                   {"system_query_options": {"device_service_tag": None}}]
141
142    @pytest.mark.parametrize("system_query_options_params", system_query_options_params)
143    def test_check_mutually_inclusive_arguments(self, system_query_options_params):
144        module_params = {"fact_subset": "subsystem_health"}
145        required_args = ["device_id", "device_service_tag"]
146        module_params.update(system_query_options_params)
147        with pytest.raises(ValueError) as ex:
148            self.module._check_mutually_inclusive_arguments(module_params["fact_subset"], module_params,
149                                                            ["device_id", "device_service_tag"])
150        assert "One of the following {0} is required for {1}".format(required_args,
151                                                                     module_params["fact_subset"]) == str(ex.value)
152
153    params = [{"fact_subset": "basic_inventory", "system_query_options": {"device_id": [Constants.device_id1]}},
154              {"fact_subset": "subsystem_health",
155               "system_query_options": {"device_service_tag": [Constants.service_tag1]}},
156              {"fact_subset": "detailed_inventory",
157               "system_query_options": {"device_id": [Constants.device_id1], "inventory_type": "serverDeviceCards"}}]
158
159    @pytest.mark.parametrize("module_params", params)
160    def test_get_resource_parameters(self, module_params, ome_connection_mock):
161        self.module._get_resource_parameters(module_params, ome_connection_mock)
162
163    @pytest.mark.parametrize("module_params,data", [({"system_query_options": None}, None),
164                                                    ({"system_query_options": {"fileter": None}}, None),
165                                                    ({"system_query_options": {"filter": "abc"}}, "$filter")])
166    def test_get_query_parameters(self, module_params, data):
167        res = self.module._get_query_parameters(module_params)
168        if data is not None:
169            assert data in res
170        else:
171            assert res is None
172
173    @pytest.mark.parametrize("module_params", params)
174    def test_get_device_identifier_map(self, module_params, ome_connection_mock, mocker):
175        get_device_id_from_service_tags_mock = mocker.patch(MODULE_PATH +
176                                                            'ome_device_info._get_device_id_from_service_tags')
177        get_device_id_from_service_tags_mock.return_value = None
178        res = self.module._get_device_identifier_map(module_params, ome_connection_mock)
179        assert isinstance(res, dict)
180
181    def test_check_duplicate_device_id(self):
182        self.module._check_duplicate_device_id([Constants.device_id1],
183                                               {Constants.device_id1: Constants.service_tag1})
184        assert self.module.device_fact_error_report[Constants.service_tag1] == "Duplicate report of device_id: 1234"
185
186    @pytest.mark.parametrize("val,expected_res", [(123, True), ("abc", False)])
187    def test_is_int(self, val, expected_res):
188        actual_res = self.module.is_int(val)
189        assert actual_res == expected_res
190
191    def test_get_device_id_from_service_tags(self, ome_connection_mock, ome_response_mock, mocker):
192        mocker.patch(MODULE_PATH + 'ome_device_info.update_device_details_with_filtering')
193        ome_response_mock.json_data.update({"@odata.context": "/api/$metadata#Collection(DeviceService.Device)"})
194        ome_response_mock.json_data.update({"@odata.count": 1})
195        ome_connection_mock.get_all_report_details.return_value = {"resp_obj": ome_response_mock, "report_list": [
196            {"DeviceServiceTag": Constants.service_tag1,
197             "Id": Constants.device_id1}]}
198        self.module._get_device_id_from_service_tags([Constants.service_tag1, "INVALID"], ome_connection_mock)
199
200    def test_get_device_id_from_service_tags_error_case(self, ome_connection_mock, ome_response_mock):
201        ome_connection_mock.get_all_report_details.side_effect = HTTPError('http://testhost.com', 400, '', {}, None)
202        with pytest.raises(HTTPError) as ex:
203            self.module._get_device_id_from_service_tags(["INVALID"], ome_connection_mock)
204
205    def test_update_device_details_with_filtering_success_case_01(self, ome_connection_mock, ome_response_mock):
206        non_available_tags = [Constants.service_tag2]
207        service_tag_dict = {Constants.device_id1: Constants.service_tag1}
208        ome_response_mock.json_data = {
209            "value": [{"DeviceServiceTag": Constants.service_tag2, "Id": Constants.device_id2}]}
210        self.module.update_device_details_with_filtering(non_available_tags, service_tag_dict, ome_connection_mock)
211        assert service_tag_dict[Constants.device_id1] == Constants.service_tag1
212        assert service_tag_dict[Constants.device_id2] == Constants.service_tag2
213        assert len(non_available_tags) == 0
214
215    def test_update_device_details_with_filtering_success_case_02(self, ome_connection_mock, ome_response_mock):
216        non_available_tags = ["MX700"]
217        service_tag_dict = {Constants.device_id1: Constants.service_tag1}
218        ome_response_mock.json_data = {"value": [{"DeviceServiceTag": "MX7000", "Id": Constants.device_id2}]}
219        self.module.update_device_details_with_filtering(non_available_tags, service_tag_dict, ome_connection_mock)
220        assert service_tag_dict[Constants.device_id1] == Constants.service_tag1
221        assert Constants.device_id2 not in service_tag_dict
222        assert len(non_available_tags) == 1
223
224    def test_update_device_details_with_filtering_failure_case_01(self, ome_connection_mock, ome_response_mock):
225        error_msg = '400: Bad Request'
226        service_tag_dict = {}
227        non_available_tags = [Constants.service_tag2]
228        ome_connection_mock.invoke_request.side_effect = HTTPError('http://testhost.com', 400, error_msg, {}, None)
229        with pytest.raises(HTTPError, match=error_msg) as ex:
230            self.module.update_device_details_with_filtering(non_available_tags, service_tag_dict, ome_connection_mock)
231
232    def test_main_detailed_inventory_device_fact_error_report_case_01(self, ome_default_args, module_mock,
233                                                                      validate_device_inputs_mock, ome_connection_mock,
234                                                                      get_device_resource_parameters_mock,
235                                                                      ome_response_mock):
236        ome_default_args.update(
237            {"fact_subset": "detailed_inventory", "system_query_options": {"device_id": [Constants.device_id1],
238                                                                           "device_service_tag": [
239                                                                               Constants.service_tag1]}})
240        detailed_inventory = {
241            "detailed_inventory:": {
242                "device_id": {
243                    Constants.device_id1: "DeviceService/Devices(Constants.device_id1)/InventoryDetails"
244                },
245                "device_service_tag": {
246                    Constants.service_tag1: "DeviceService/Devices(4321)/InventoryDetails"
247                }
248            }
249        }
250        get_device_resource_parameters_mock.return_value = detailed_inventory
251        ome_response_mock.json_data = {"value": [{"device_id": {Constants.device_id1: "details"}},
252                                                 {"device_service_tag": {Constants.service_tag1: "details"}}]}
253        ome_response_mock.status_code = 200
254        self.module.device_fact_error_report = {
255            Constants.service_tag1: "Duplicate report of device_id: {0}".format(Constants.device_id1)}
256        result = self._run_module(ome_default_args)
257        assert result['changed'] is False
258        assert 'device_info' in result
259
260    def test_main_detailed_inventory_device_fact_error_report_case_02(self, ome_default_args, module_mock,
261                                                                      validate_device_inputs_mock,
262                                                                      ome_connection_mock,
263                                                                      get_device_resource_parameters_mock,
264                                                                      ome_response_mock):
265        ome_default_args.update(
266            {"fact_subset": "detailed_inventory", "system_query_options": {"device_id": [Constants.device_id1],
267                                                                           "device_service_tag": [
268                                                                               Constants.service_tag1]}})
269        detailed_inventory = {
270            "device_service_tag": {
271                Constants.service_tag1: "DeviceService/Devices(4321)/InventoryDetails"
272            }
273        }
274        get_device_resource_parameters_mock.return_value = detailed_inventory
275        ome_response_mock.json_data = {"value": [{"device_id": {Constants.device_id1: "details"}},
276                                                 {"device_service_tag": {Constants.service_tag1: "details"}}]}
277        ome_response_mock.status_code = 200
278        self.module.device_fact_error_report = {
279            Constants.service_tag1: "Duplicate report of device_id: {0}".format(Constants.device_id1)}
280        result = self._run_module(ome_default_args)
281        assert result['changed'] is False
282        assert 'device_info' in result
283