1# (c) 2018, NetApp, Inc
2# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
3
4''' unit test template for ONTAP Ansible module '''
5
6from __future__ import print_function
7import json
8import pytest
9
10from units.compat import unittest
11from units.compat.mock import patch, Mock
12from ansible.module_utils import basic
13from ansible.module_utils._text import to_bytes
14import ansible.module_utils.netapp as netapp_utils
15
16from ansible.modules.storage.netapp.na_ontap_unix_group \
17    import NetAppOntapUnixGroup as group_module  # module under test
18
19if not netapp_utils.has_netapp_lib():
20    pytestmark = pytest.mark.skip('skipping as missing required netapp_lib')
21
22
23def set_module_args(args):
24    """prepare arguments so that they will be picked up during module creation"""
25    args = json.dumps({'ANSIBLE_MODULE_ARGS': args})
26    basic._ANSIBLE_ARGS = to_bytes(args)  # pylint: disable=protected-access
27
28
29class AnsibleExitJson(Exception):
30    """Exception class to be raised by module.exit_json and caught by the test case"""
31    pass
32
33
34class AnsibleFailJson(Exception):
35    """Exception class to be raised by module.fail_json and caught by the test case"""
36    pass
37
38
39def exit_json(*args, **kwargs):  # pylint: disable=unused-argument
40    """function to patch over exit_json; package return data into an exception"""
41    if 'changed' not in kwargs:
42        kwargs['changed'] = False
43    raise AnsibleExitJson(kwargs)
44
45
46def fail_json(*args, **kwargs):  # pylint: disable=unused-argument
47    """function to patch over fail_json; package return data into an exception"""
48    kwargs['failed'] = True
49    raise AnsibleFailJson(kwargs)
50
51
52class MockONTAPConnection(object):
53    ''' mock server connection to ONTAP host '''
54
55    def __init__(self, kind=None, data=None):
56        ''' save arguments '''
57        self.kind = kind
58        self.params = data
59        self.xml_in = None
60        self.xml_out = None
61
62    def invoke_successfully(self, xml, enable_tunneling):  # pylint: disable=unused-argument
63        ''' mock invoke_successfully returning xml data '''
64        self.xml_in = xml
65        if self.kind == 'group':
66            xml = self.build_group_info(self.params)
67        elif self.kind == 'group-fail':
68            raise netapp_utils.zapi.NaApiError(code='TEST', message="This exception is from the unit test")
69        self.xml_out = xml
70        return xml
71
72    @staticmethod
73    def build_group_info(data):
74        ''' build xml data for vserser-info '''
75        xml = netapp_utils.zapi.NaElement('xml')
76        attributes = \
77            {'attributes-list': {'unix-group-info': {'group-name': data['name'],
78                                                     'group-id': data['id']}},
79             'num-records': 1}
80        xml.translate_struct(attributes)
81        return xml
82
83
84class TestMyModule(unittest.TestCase):
85    ''' a group of related Unit Tests '''
86
87    def setUp(self):
88        self.mock_module_helper = patch.multiple(basic.AnsibleModule,
89                                                 exit_json=exit_json,
90                                                 fail_json=fail_json)
91        self.mock_module_helper.start()
92        self.addCleanup(self.mock_module_helper.stop)
93        self.server = MockONTAPConnection()
94        self.mock_group = {
95            'name': 'test',
96            'id': '11',
97            'vserver': 'something',
98        }
99
100    def mock_args(self):
101        return {
102            'name': self.mock_group['name'],
103            'id': self.mock_group['id'],
104            'vserver': self.mock_group['vserver'],
105            'hostname': 'test',
106            'username': 'test_user',
107            'password': 'test_pass!'
108        }
109
110    def get_group_mock_object(self, kind=None, data=None):
111        """
112        Helper method to return an na_ontap_unix_group object
113        :param kind: passes this param to MockONTAPConnection()
114        :return: na_ontap_unix_group object
115        """
116        obj = group_module()
117        obj.autosupport_log = Mock(return_value=None)
118        if data is None:
119            data = self.mock_group
120        obj.server = MockONTAPConnection(kind=kind, data=data)
121        return obj
122
123    def test_module_fail_when_required_args_missing(self):
124        ''' required arguments are reported as errors '''
125        with pytest.raises(AnsibleFailJson) as exc:
126            set_module_args({})
127            group_module()
128
129    def test_get_nonexistent_group(self):
130        ''' Test if get_unix_group returns None for non-existent group '''
131        set_module_args(self.mock_args())
132        result = self.get_group_mock_object().get_unix_group()
133        assert result is None
134
135    def test_get_existing_group(self):
136        ''' Test if get_unix_group returns details for existing group '''
137        set_module_args(self.mock_args())
138        result = self.get_group_mock_object('group').get_unix_group()
139        assert result['name'] == self.mock_group['name']
140
141    def test_get_xml(self):
142        set_module_args(self.mock_args())
143        obj = self.get_group_mock_object('group')
144        result = obj.get_unix_group()
145        assert obj.server.xml_in['query']
146        assert obj.server.xml_in['query']['unix-group-info']
147        group_info = obj.server.xml_in['query']['unix-group-info']
148        assert group_info['group-name'] == self.mock_group['name']
149        assert group_info['vserver'] == self.mock_group['vserver']
150
151    def test_create_error_missing_params(self):
152        data = self.mock_args()
153        del data['id']
154        set_module_args(data)
155        with pytest.raises(AnsibleFailJson) as exc:
156            self.get_group_mock_object('group').create_unix_group()
157        assert 'Error: Missing a required parameter for create: (id)' == exc.value.args[0]['msg']
158
159    @patch('ansible.modules.storage.netapp.na_ontap_unix_group.NetAppOntapUnixGroup.create_unix_group')
160    def test_create_called(self, create_group):
161        set_module_args(self.mock_args())
162        with pytest.raises(AnsibleExitJson) as exc:
163            self.get_group_mock_object().apply()
164        assert exc.value.args[0]['changed']
165        create_group.assert_called_with()
166
167    def test_create_xml(self):
168        '''Test create ZAPI element'''
169        set_module_args(self.mock_args())
170        create = self.get_group_mock_object()
171        with pytest.raises(AnsibleExitJson) as exc:
172            create.apply()
173        mock_key = {
174            'group-name': 'name',
175            'group-id': 'id',
176        }
177        for key in ['group-name', 'group-id']:
178            assert create.server.xml_in[key] == self.mock_group[mock_key[key]]
179
180    @patch('ansible.modules.storage.netapp.na_ontap_unix_group.NetAppOntapUnixGroup.modify_unix_group')
181    @patch('ansible.modules.storage.netapp.na_ontap_unix_group.NetAppOntapUnixGroup.delete_unix_group')
182    def test_delete_called(self, delete_group, modify_group):
183        ''' Test delete existing group '''
184        data = self.mock_args()
185        data['state'] = 'absent'
186        set_module_args(data)
187        with pytest.raises(AnsibleExitJson) as exc:
188            self.get_group_mock_object('group').apply()
189        assert exc.value.args[0]['changed']
190        delete_group.assert_called_with()
191        assert modify_group.call_count == 0
192
193    @patch('ansible.modules.storage.netapp.na_ontap_unix_group.NetAppOntapUnixGroup.get_unix_group')
194    @patch('ansible.modules.storage.netapp.na_ontap_unix_group.NetAppOntapUnixGroup.modify_unix_group')
195    def test_modify_called(self, modify_group, get_group):
196        ''' Test modify group group_id '''
197        data = self.mock_args()
198        data['id'] = 20
199        set_module_args(data)
200        get_group.return_value = {'id': 10}
201        obj = self.get_group_mock_object('group')
202        with pytest.raises(AnsibleExitJson) as exc:
203            obj.apply()
204        get_group.assert_called_with()
205        modify_group.assert_called_with({'id': 20})
206
207    def test_modify_only_id(self):
208        ''' Test modify group id '''
209        set_module_args(self.mock_args())
210        modify = self.get_group_mock_object('group')
211        modify.modify_unix_group({'id': 123})
212        print(modify.server.xml_in.to_string())
213        assert modify.server.xml_in['group-id'] == '123'
214        with pytest.raises(KeyError):
215            modify.server.xml_in['id']
216
217    def test_modify_xml(self):
218        ''' Test modify group full_name '''
219        set_module_args(self.mock_args())
220        modify = self.get_group_mock_object('group')
221        modify.modify_unix_group({'id': 25})
222        assert modify.server.xml_in['group-name'] == self.mock_group['name']
223        assert modify.server.xml_in['group-id'] == '25'
224
225    @patch('ansible.modules.storage.netapp.na_ontap_unix_group.NetAppOntapUnixGroup.create_unix_group')
226    @patch('ansible.modules.storage.netapp.na_ontap_unix_group.NetAppOntapUnixGroup.delete_unix_group')
227    @patch('ansible.modules.storage.netapp.na_ontap_unix_group.NetAppOntapUnixGroup.modify_unix_group')
228    def test_do_nothing(self, modify, delete, create):
229        ''' changed is False and none of the opetaion methods are called'''
230        data = self.mock_args()
231        data['state'] = 'absent'
232        set_module_args(data)
233        obj = self.get_group_mock_object()
234        with pytest.raises(AnsibleExitJson) as exc:
235            obj.apply()
236        create.assert_not_called()
237        delete.assert_not_called()
238        modify.assert_not_called()
239
240    def test_get_exception(self):
241        set_module_args(self.mock_args())
242        with pytest.raises(AnsibleFailJson) as exc:
243            self.get_group_mock_object('group-fail').get_unix_group()
244        assert 'Error getting UNIX group' in exc.value.args[0]['msg']
245
246    def test_create_exception(self):
247        set_module_args(self.mock_args())
248        with pytest.raises(AnsibleFailJson) as exc:
249            self.get_group_mock_object('group-fail').create_unix_group()
250        assert 'Error creating UNIX group' in exc.value.args[0]['msg']
251
252    def test_modify_exception(self):
253        set_module_args(self.mock_args())
254        with pytest.raises(AnsibleFailJson) as exc:
255            self.get_group_mock_object('group-fail').modify_unix_group({'id': '123'})
256        assert 'Error modifying UNIX group' in exc.value.args[0]['msg']
257
258    def test_delete_exception(self):
259        set_module_args(self.mock_args())
260        with pytest.raises(AnsibleFailJson) as exc:
261            self.get_group_mock_object('group-fail').delete_unix_group()
262        assert 'Error removing UNIX group' in exc.value.args[0]['msg']
263
264    @patch('ansible.modules.storage.netapp.na_ontap_unix_group.NetAppOntapUnixGroup.get_unix_group')
265    def test_add_user_exception(self, get_unix_group):
266        data = self.mock_args()
267        data['users'] = 'test_user'
268        set_module_args(data)
269        get_unix_group.side_effect = [
270            {'users': []}
271        ]
272        with pytest.raises(AnsibleFailJson) as exc:
273            self.get_group_mock_object('group-fail').modify_users_in_group()
274            print(exc.value.args[0]['msg'])
275        assert 'Error adding user' in exc.value.args[0]['msg']
276
277    @patch('ansible.modules.storage.netapp.na_ontap_unix_group.NetAppOntapUnixGroup.get_unix_group')
278    def test_delete_user_exception(self, get_unix_group):
279        data = self.mock_args()
280        data['users'] = ''
281        set_module_args(data)
282        get_unix_group.side_effect = [
283            {'users': ['test_user']}
284        ]
285        with pytest.raises(AnsibleFailJson) as exc:
286            self.get_group_mock_object('group-fail').modify_users_in_group()
287            print(exc.value.args[0]['msg'])
288        assert 'Error deleting user' in exc.value.args[0]['msg']
289