1# -*- coding: utf-8 -*-
2
3#
4# Dell EMC OpenManage Ansible Modules
5# Version 3.5.0
6# Copyright (C) 2021 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 json
16import pytest
17from ssl import SSLError
18from io import StringIO
19from ansible.module_utils.six.moves.urllib.error import HTTPError, URLError
20from ansible.module_utils.urls import ConnectionError, SSLValidationError
21from ansible.module_utils._text import to_text
22from ansible_collections.dellemc.openmanage.plugins.modules import ome_groups
23from ansible_collections.dellemc.openmanage.tests.unit.plugins.modules.common import FakeAnsibleModule, Constants
24
25MULTIPLE_GROUPS_MSG = "Provide only one unique device group when state is present."
26NONEXIST_GROUP_ID = "A device group with the provided ID does not exist."
27NONEXIST_PARENT_ID = "A parent device group with the provided ID does not exist."
28INVALID_PARENT = "The provided parent device group is not a valid user-defined static device group."
29INVALID_GROUPS_DELETE = "Provide valid static device group(s) for deletion."
30INVALID_GROUPS_MODIFY = "Provide valid static device group for modification."
31PARENT_CREATION_FAILED = "Unable to create a parent device group with the name {pname}."
32PARENT_IN_SUBTREE = "The parent group is already under the provided group."
33CREATE_SUCCESS = "Successfully {op}d the device group."
34GROUP_PARENT_SAME = "Provided parent and the device group cannot be the same."
35DELETE_SUCCESS = "Successfully deleted the device group(s)."
36NO_CHANGES_MSG = "No changes found to be applied."
37CHANGES_FOUND = "Changes found to be applied."
38STATIC_ROOT = 'Static Groups'
39SETTLING_TIME = 2
40
41MODULE_PATH = 'ansible_collections.dellemc.openmanage.plugins.modules.ome_groups.'
42
43
44@pytest.fixture
45def ome_connection_mock_for_groups(mocker, ome_response_mock):
46    connection_class_mock = mocker.patch('ansible_collections.dellemc.openmanage.plugins.modules.ome_groups.RestOME')
47    ome_connection_mock_obj = connection_class_mock.return_value.__enter__.return_value
48    ome_connection_mock_obj.invoke_request.return_value = ome_response_mock
49    return ome_connection_mock_obj
50
51
52class TestOmeGroups(FakeAnsibleModule):
53    module = ome_groups
54
55    @pytest.mark.parametrize("params", [
56        {"json_data": {"value": [{'Name': 'g1', 'Id': 24, 'TypeId': 3000, 'MembershipTypeId': 12}]},
57         'message': DELETE_SUCCESS, "success": True, 'mparams': {'name': 'g1', 'state': 'absent'}},
58        {"json_data": {"value": [{'Name': 'g1', 'Id': 24, 'TypeId': 3000, 'MembershipTypeId': 12}]},
59         'message': DELETE_SUCCESS, "success": True, 'mparams': {'name': 'g1', 'state': 'absent'}},
60        {"json_data": {"value": [{'Name': 'g1', 'Id': 24, 'TypeId': 3000, 'MembershipTypeId': 12}]},
61         'message': CHANGES_FOUND, "success": True, 'mparams': {'group_id': 24, 'state': 'absent'}, 'check_mode': True},
62        {"json_data": {"value": [{'Name': 'g1', 'Id': 24, 'TypeId': 3000, 'MembershipTypeId': 12}]},
63         'message': NO_CHANGES_MSG, "success": True, 'mparams': {'name': 'g2', 'state': 'absent'}},
64        {"json_data": {"value": [{'Name': 'g1', 'Id': 24, 'TypeId': 3000, 'MembershipTypeId': 12}]},
65         'message': NO_CHANGES_MSG, "success": True, 'mparams': {'name': 'g2', 'state': 'absent'}, 'check_mode': True}])
66    def test_ome_groups_delete(self, params, ome_connection_mock_for_groups, ome_response_mock, ome_default_args,
67                               module_mock):
68        ome_response_mock.success = params.get("success", True)
69        ome_response_mock.json_data = params['json_data']
70        ome_connection_mock_for_groups.get_all_items_with_pagination.return_value = params['json_data']
71        ome_default_args.update(params['mparams'])
72        result = self._run_module(ome_default_args, check_mode=params.get('check_mode', False))
73        assert result['msg'] == params['message']
74
75    @pytest.mark.parametrize("params", [{"json_data": {
76        "value": [{'Name': 'g2', 'Id': 24, 'TypeId': 3000, 'MembershipTypeId': 12},
77                  {'Name': 'gp1', 'Id': 25, 'TypeId': 3000, 'MembershipTypeId': 12},
78                  {'Name': 'Static Groups', 'Id': 1, 'TypeId': 2000, 'MembershipTypeId': 12}]},
79        'message': CREATE_SUCCESS, "success": True,
80        'mparams': {'name': 'g1', 'parent_group_name': 'gp1', 'description': 'My group described'},
81        'return_data': 22, 'created_group': {'Name': 'g1', 'Id': 26, 'ParentId': 25, 'MembershipTypeId': 12}},
82        {"json_data": {'value': [{'Name': 'g2', 'Id': 24, 'TypeId': 3000, 'MembershipTypeId': 12},
83                                 {'Name': 'gp1', 'Id': 25, 'TypeId': 3000, 'MembershipTypeId': 12},
84                                 {'Name': 'Static Groups', 'Id': 1, 'TypeId': 2000, 'MembershipTypeId': 12}]},
85         'message': CREATE_SUCCESS, "success": True,
86         'mparams': {'name': 'g1', 'parent_group_name': 'gp21', 'description': 'My group described'}, 'return_data': 22,
87         'created_group': {'Name': 'g1', 'Id': 26, 'ParentId': 25, 'MembershipTypeId': 12}},
88        {"json_data": {'value': [{'Name': 'g2', 'Id': 24, 'TypeId': 3000, 'MembershipTypeId': 12},
89                                 {'Name': 'gp1', 'Id': 25, 'TypeId': 3000, 'MembershipTypeId': 12},
90                                 {'Name': 'Static Groups', 'Id': 1, 'TypeId': 2000, 'MembershipTypeId': 12}]},
91         'message': CREATE_SUCCESS, "success": True,
92         'mparams': {'name': 'g1', 'parent_group_id': 25, 'description': 'My group described'}, 'return_data': 22,
93         'created_group': {'Name': 'g1', 'Id': 26, 'ParentId': 25, 'MembershipTypeId': 12}},
94        {"json_data": {'value': [{'Name': 'g2', 'Id': 24, 'TypeId': 3000, 'MembershipTypeId': 12},
95                                 {'Name': 'gp1', 'Id': 25, 'TypeId': 3000, 'MembershipTypeId': 12},
96                                 {'Name': 'Static Groups', 'Id': 1, 'TypeId': 2000, 'MembershipTypeId': 12}]},
97         'message': CREATE_SUCCESS, "success": True,
98         'mparams': {'name': 'g1', 'parent_group_name': 'Static Groups', 'description': 'My group described'},
99         'return_data': 22, 'created_group': {'Name': 'g1', 'Id': 26, 'ParentId': 1, 'MembershipTypeId': 12}},
100        {"json_data": {'value': [{'Name': 'g2', 'Id': 24, 'TypeId': 3000, 'MembershipTypeId': 12},
101                                 {'Name': 'gp1', 'Id': 25, 'TypeId': 3000, 'MembershipTypeId': 12},
102                                 {'Name': 'Static Groups', 'Id': 1, 'TypeId': 2000, 'MembershipTypeId': 12}]},
103         'message': CREATE_SUCCESS, "success": True,
104         'mparams': {'name': 'g1', 'parent_group_id': 1, 'description': 'My group described'}, 'return_data': 22,
105         'created_group': {'Name': 'g1', 'Id': 26, 'ParentId': 1, 'MembershipTypeId': 12}},
106        {"json_data": {'value': [{'Name': 'g2', 'Id': 24, 'TypeId': 3000, 'MembershipTypeId': 12},
107                                 {'Name': 'gp1', 'Id': 25, 'TypeId': 3000, 'MembershipTypeId': 12},
108                                 {'Name': 'Static Groups', 'Id': 1, 'TypeId': 2000, 'MembershipTypeId': 12}]},
109         'message': CHANGES_FOUND, "success": True,
110         'mparams': {'name': 'g1', 'parent_group_name': 'gp21', 'description': 'My group described'}, 'return_data': 22,
111         'created_group': {'Name': 'g1', 'Id': 26, 'ParentId': 25, 'MembershipTypeId': 12}, 'check_mode': True}])
112    def test_ome_groups_create(self, params, ome_connection_mock_for_groups, ome_response_mock, ome_default_args,
113                               module_mock, mocker):
114        ome_response_mock.success = params.get("success", True)
115        ome_response_mock.json_data = params['return_data']
116        ome_connection_mock_for_groups.get_all_items_with_pagination.return_value = params['json_data']
117        ome_connection_mock_for_groups.strip_substr_dict.return_value = params.get('created_group', {})
118        mocker.patch(MODULE_PATH + 'get_ome_group_by_id', return_value=params.get('created_group', {}))
119        mocker.patch(MODULE_PATH + 'create_parent', return_value=params['created_group'].get('ParentId'))
120        ome_default_args.update(params['mparams'])
121        result = self._run_module(ome_default_args, check_mode=params.get('check_mode', False))
122        assert result['msg'] == (params['message']).format(op='create')
123
124    @pytest.mark.parametrize("params", [{"json_data": {
125        'value': [{'Name': 'g1', 'Id': 24, 'TypeId': 3000, 'MembershipTypeId': 12, 'description': 'My group described'},
126                  {'Name': 'gp1', 'Id': 25, 'TypeId': 3000, 'MembershipTypeId': 12},
127                  {'Name': 'Static Groups', 'Id': 1, 'TypeId': 2000, 'MembershipTypeId': 12}]},
128        'message': CREATE_SUCCESS, "success": True,
129        'mparams': {'name': 'g1', 'new_name': 'j1', 'parent_group_name': 'gp1', 'description': 'description modified'},
130        'return_data': 22, 'created_group': {'Name': 'g1', 'Id': 26, 'ParentId': 25, 'MembershipTypeId': 12}}, {
131        "json_data": {'value': [{'Name': 'g1', 'Id': 24, 'TypeId': 3000, 'ParentId': 25, 'MembershipTypeId': 12,
132                                 'description': 'My group described'},
133                                {'Name': 'gp1', 'Id': 25, 'TypeId': 3000, 'MembershipTypeId': 12},
134                                {'Name': 'Static Groups', 'Id': 1, 'TypeId': 2000, 'MembershipTypeId': 12}]},
135        'message': CHANGES_FOUND, "success": True,
136        'mparams': {'name': 'g1', 'parent_group_name': 'gp1', 'description': 'description modified'}, 'return_data': 22,
137        'created_group': {'Name': 'g1', 'Id': 26, 'ParentId': 25, 'MembershipTypeId': 12}, 'check_mode': True}, {
138        "json_data": {'value': [{'Name': 'g1', 'Id': 24, 'TypeId': 3000, 'ParentId': 25, 'MembershipTypeId': 12,
139                                 'Description': 'My group described'},
140                                {'Name': 'gp1', 'Id': 25, 'TypeId': 3000, 'MembershipTypeId': 12},
141                                {'Name': 'Static Groups', 'Id': 1, 'TypeId': 2000, 'MembershipTypeId': 12}]},
142        'message': NO_CHANGES_MSG, "success": True,
143        'mparams': {'name': 'g1', 'new_name': 'g1', 'parent_group_name': 'gp1', 'description': 'My group described'},
144        'return_data': 22, 'created_group': {'Name': 'g1', 'Id': 24, 'ParentId': 25, 'MembershipTypeId': 12},
145        'check_mode': True}, ])
146    def test_ome_groups_modify(self, params, ome_connection_mock_for_groups, ome_response_mock, ome_default_args,
147                               module_mock, mocker):
148        ome_response_mock.success = params.get("success", True)
149        ome_response_mock.json_data = params['return_data']
150        ome_connection_mock_for_groups.get_all_items_with_pagination.return_value = params['json_data']
151        ome_connection_mock_for_groups.strip_substr_dict.return_value = params.get('created_group', {})
152        mocker.patch(MODULE_PATH + 'get_ome_group_by_id', return_value=params.get('created_group', {}))
153        mocker.patch(MODULE_PATH + 'create_parent', return_value=params['created_group'].get('ParentId'))
154        # mocker.patch(MODULE_PATH + 'is_parent_in_subtree', return_value=False)
155        ome_default_args.update(params['mparams'])
156        result = self._run_module(ome_default_args, check_mode=params.get('check_mode', False))
157        assert result['msg'] == (params['message']).format(op='update')
158
159    @pytest.mark.parametrize("params", [{"json_data": {
160        'value': [{'Name': 'g2', 'Id': 24, 'TypeId': 3000, 'MembershipTypeId': 12},
161                  {'Name': 'g3', 'Id': 12, 'TypeId': 2000, 'MembershipTypeId': 24},
162                  {'Name': 'gp1', 'Id': 25, 'TypeId': 3000, 'MembershipTypeId': 12},
163                  {'Name': 'Static Groups', 'Id': 1, 'TypeId': 2000, 'MembershipTypeId': 12}]},
164        'message': MULTIPLE_GROUPS_MSG, "success": True, 'mparams': {'name': ['g1', 'g3'], 'parent_group_name': 'gp1',
165                                                                     'description': 'State present and multiple groups'},
166        'return_data': 22, 'created_group': {'Name': 'g1', 'Id': 26, 'ParentId': 25, 'MembershipTypeId': 12}}, {
167        "json_data": {'value': [{'Name': 'g2', 'Id': 24, 'TypeId': 3000, 'MembershipTypeId': 12},
168                                {'Name': 'g3', 'Id': 12, 'TypeId': 2000, 'MembershipTypeId': 24},
169                                {'Name': 'gp1', 'Id': 25, 'TypeId': 3000, 'MembershipTypeId': 12},
170                                {'Name': 'Static Groups', 'Id': 1, 'TypeId': 2000, 'MembershipTypeId': 12}]},
171        'message': NONEXIST_GROUP_ID, "success": True,
172        'mparams': {'group_id': 13, 'parent_group_name': 'gp1', 'description': 'State present and no group_id'},
173        'return_data': 22, 'created_group': {'Name': 'g1', 'Id': 26, 'ParentId': 25, 'MembershipTypeId': 12}}, {
174        "json_data": {'value': [{'Name': 'g2', 'Id': 24, 'TypeId': 3000, 'MembershipTypeId': 12},
175                                {'Name': 'g3', 'Id': 12, 'TypeId': 2000, 'MembershipTypeId': 24},
176                                {'Name': 'gp1', 'Id': 25, 'TypeId': 3000, 'MembershipTypeId': 12},
177                                {'Name': 'Static Groups', 'Id': 1, 'TypeId': 2000, 'MembershipTypeId': 12}]},
178        'message': INVALID_PARENT, "success": True,
179        'mparams': {'name': 'g1', 'parent_group_name': 'g3', 'description': 'State present and invalid parent'},
180        'return_data': 22, 'created_group': {'Name': 'g1', 'Id': 26, 'ParentId': 25, 'MembershipTypeId': 12}}, {
181        "json_data": {'value': [{'Name': 'g2', 'Id': 24, 'TypeId': 3000, 'MembershipTypeId': 12},
182                                {'Name': 'g3', 'Id': 12, 'TypeId': 2000, 'MembershipTypeId': 24},
183                                {'Name': 'gp1', 'Id': 25, 'TypeId': 3000, 'MembershipTypeId': 12},
184                                {'Name': 'Static Groups', 'Id': 1, 'TypeId': 2000, 'MembershipTypeId': 12}]},
185        'message': INVALID_GROUPS_DELETE, "success": True,
186        'mparams': {'name': ['g1', 'g3'], 'state': 'absent', 'description': 'State absent and invalid group'},
187        'return_data': 22, 'created_group': {'Name': 'g1', 'Id': 26, 'ParentId': 25, 'MembershipTypeId': 12}}, {
188        "json_data": {'value': [{'Name': 'g2', 'Id': 24, 'TypeId': 3000, 'MembershipTypeId': 12},
189                                {'Name': 'gp1', 'Id': 25, 'TypeId': 3000, 'MembershipTypeId': 12},
190                                {'Name': 'Static Groups', 'Id': 1, 'TypeId': 2000, 'MembershipTypeId': 12}]},
191        'message': NONEXIST_PARENT_ID, "success": True,
192        'mparams': {'name': 'g1', 'parent_group_id': 26, 'description': 'create with non exist parent id'},
193        'return_data': 22, 'created_group': {'Name': 'g1', 'Id': 26, 'ParentId': 25, 'MembershipTypeId': 12}}, {
194        "json_data": {'value': [{'Name': 'g2', 'Id': 24, 'TypeId': 2000, 'MembershipTypeId': 24},
195                                {'Name': 'gp1', 'Id': 25, 'TypeId': 3000, 'MembershipTypeId': 12},
196                                {'Name': 'Static Groups', 'Id': 1, 'TypeId': 2000, 'MembershipTypeId': 12}]},
197        'message': INVALID_PARENT, "success": True,
198        'mparams': {'name': 'g1', 'parent_group_id': 24, 'description': 'create with non exist parent id'},
199        'return_data': 22, 'created_group': {'Name': 'g1', 'Id': 26, 'ParentId': 25, 'MembershipTypeId': 12}}, {
200        "json_data": {'value': [{'Name': 'g1', 'Id': 24, 'TypeId': 2000, 'ParentId': 25, 'MembershipTypeId': 24,
201                                 'Description': 'My group described'},
202                                {'Name': 'gp1', 'Id': 25, 'TypeId': 3000, 'MembershipTypeId': 12},
203                                {'Name': 'Static Groups', 'Id': 1, 'TypeId': 2000, 'MembershipTypeId': 12}]},
204        'message': INVALID_GROUPS_MODIFY, "success": True,
205        'mparams': {'name': 'g1', 'new_name': 'g1', 'parent_group_name': 'gp1', 'description': 'My group described'},
206        'return_data': 22, 'created_group': {'Name': 'g1', 'Id': 24, 'ParentId': 25, 'MembershipTypeId': 12},
207        'check_mode': True},
208        {"json_data": {'value': [{'Name': 'g1', 'Id': 24, 'TypeId': 3000, 'ParentId': 25, 'MembershipTypeId': 12,
209         'Description': 'My group described'}, {'Name': 'gp1', 'Id': 24, 'TypeId': 3000, 'MembershipTypeId': 12},
210                                 {'Name': 'Static Groups', 'Id': 1, 'TypeId': 2000, 'MembershipTypeId': 12}]},
211         'message': GROUP_PARENT_SAME, "success": True,
212         'mparams': {'name': 'g1', 'new_name': 'g1', 'parent_group_name': 'gp1', 'description': 'My group described'},
213         'return_data': 22, 'created_group': {'Name': 'g1', 'Id': 24, 'ParentId': 25, 'MembershipTypeId': 12},
214         'check_mode': True},
215        {"json_data": {'value': [{'Name': 'x1', 'Id': 24, 'TypeId': 3000, 'ParentId': 25, 'MembershipTypeId': 12,
216                                  'Description': 'My group described'},
217                                 {'Name': 'g2', 'Id': 24, 'TypeId': 3000, 'MembershipTypeId': 12},
218                                 {'Name': 'Static Groups', 'Id': 1, 'TypeId': 2000, 'MembershipTypeId': 12}]},
219         'message': GROUP_PARENT_SAME, "success": True,
220         'mparams': {'name': 'g1', 'parent_group_name': 'g1', 'description': 'My group described'},
221         'return_data': 22, 'created_group': {'Name': 'g1', 'Id': 24, 'ParentId': 25, 'MembershipTypeId': 12},
222         'check_mode': True}])
223    def test_ome_groups_fail_jsons(self, params, ome_connection_mock_for_groups, ome_response_mock, ome_default_args,
224                                   module_mock, mocker):
225        ome_response_mock.success = params.get("success", True)
226        ome_response_mock.json_data = params['return_data']
227        ome_connection_mock_for_groups.get_all_items_with_pagination.return_value = params['json_data']
228        ome_connection_mock_for_groups.strip_substr_dict.return_value = params.get('created_group', {})
229        mocker.patch(MODULE_PATH + 'get_ome_group_by_id', return_value=params.get('created_group', {}))
230        mocker.patch(MODULE_PATH + 'create_parent', return_value=params['created_group'].get('ParentId'))
231        ome_default_args.update(params['mparams'])
232        result = self._run_module_with_fail_json(ome_default_args)
233        assert result['msg'] == params['message']
234
235    @pytest.mark.parametrize("params", [{"json_data": 12, "mparams": {'name': 'g1', 'parent_group_name': 'gp21'}}])
236    def test_create_parent(self, params, ome_connection_mock_for_groups, ome_response_mock):
237        ome_response_mock.success = params.get("success", True)
238        ome_response_mock.json_data = params["json_data"]
239        f_module = self.get_module_mock(params=params['mparams'])
240        static_root = {'Name': 'Static Groups', 'Id': 1, 'TypeId': 2000, 'MembershipTypeId': 12}
241        group_id = self.module.create_parent(ome_connection_mock_for_groups, f_module, static_root)
242        assert group_id == params['json_data']
243
244    @pytest.mark.parametrize("params",
245                             [{"json_data": {'Name': 'g2', 'Id': 24, 'TypeId': 3000, 'MembershipTypeId': 12}}])
246    def test_get_ome_group_by_id(self, params, ome_connection_mock_for_groups, ome_response_mock):
247        ome_response_mock.success = params.get("success", True)
248        ome_response_mock.json_data = params["json_data"]
249        group = self.module.get_ome_group_by_id(ome_connection_mock_for_groups, 24)
250        assert group == params['json_data']
251
252    @pytest.mark.parametrize("exc_type",
253                             [IOError, ValueError, SSLError, TypeError, ConnectionError, HTTPError, URLError])
254    def test_ome_groups_main_exception_failure_case(self, exc_type, mocker, ome_default_args,
255                                                    ome_connection_mock_for_groups, ome_response_mock):
256        ome_default_args.update({"state": "absent", "name": "t1"})
257        ome_response_mock.status_code = 400
258        ome_response_mock.success = False
259        json_str = to_text(json.dumps({"info": "error_details"}))
260        if exc_type == URLError:
261            mocker.patch(MODULE_PATH + 'get_valid_groups', side_effect=exc_type("url open error"))
262            result = self._run_module(ome_default_args)
263            assert result["unreachable"] is True
264        elif exc_type not in [HTTPError, SSLValidationError]:
265            mocker.patch(MODULE_PATH + 'get_valid_groups', side_effect=exc_type("exception message"))
266            result = self._run_module_with_fail_json(ome_default_args)
267            assert result['failed'] is True
268        else:
269            mocker.patch(MODULE_PATH + 'get_valid_groups',
270                         side_effect=exc_type('http://testhost.com', 400, 'http error message',
271                                              {"accept-type": "application/json"}, StringIO(json_str)))
272            result = self._run_module_with_fail_json(ome_default_args)
273            assert result['failed'] is True
274        assert 'msg' in result
275