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 tests ONTAP Ansible module: na_ontap_user '''
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_user \
17    import NetAppOntapUser as my_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, parm1=None, parm2=None):
56        ''' save arguments '''
57        self.type = kind
58        self.parm1 = parm1
59        self.parm2 = parm2
60        self.xml_in = None
61        self.xml_out = None
62
63    def invoke_successfully(self, xml, enable_tunneling):  # pylint: disable=unused-argument
64        ''' mock invoke_successfully returning xml data '''
65        self.xml_in = xml
66        if self.type == 'user':
67            xml = self.build_user_info(self.parm1, self.parm2)
68        elif self.type == 'user_fail':
69            raise netapp_utils.zapi.NaApiError(code='TEST', message="This exception is from the unit test")
70        self.xml_out = xml
71        return xml
72
73    @staticmethod
74    def set_vserver(vserver):
75        '''mock set vserver'''
76        pass
77
78    @staticmethod
79    def build_user_info(locked, role_name):
80        ''' build xml data for user-info '''
81        xml = netapp_utils.zapi.NaElement('xml')
82        data = {'num-records': 1,
83                'attributes-list': {'security-login-account-info': {'is-locked': locked, 'role-name': role_name}}}
84
85        xml.translate_struct(data)
86        print(xml.to_string())
87        return xml
88
89
90class TestMyModule(unittest.TestCase):
91    ''' a group of related Unit Tests '''
92
93    def setUp(self):
94        self.mock_module_helper = patch.multiple(basic.AnsibleModule,
95                                                 exit_json=exit_json,
96                                                 fail_json=fail_json)
97        self.mock_module_helper.start()
98        self.addCleanup(self.mock_module_helper.stop)
99        self.server = MockONTAPConnection()
100        self.onbox = False
101
102    def set_default_args(self):
103        if self.onbox:
104            hostname = '10.10.10.10'
105            username = 'username'
106            password = 'password'
107            user_name = 'test'
108            vserver = 'ansible_test'
109            application = 'console'
110            authentication_method = 'password'
111        else:
112            hostname = 'hostname'
113            username = 'username'
114            password = 'password'
115            user_name = 'name'
116            vserver = 'vserver'
117            application = 'console'
118            authentication_method = 'password'
119
120        return dict({
121            'hostname': hostname,
122            'username': username,
123            'password': password,
124            'name': user_name,
125            'vserver': vserver,
126            'applications': application,
127            'authentication_method': authentication_method
128        })
129
130    def test_module_fail_when_required_args_missing(self):
131        ''' required arguments are reported as errors '''
132        with pytest.raises(AnsibleFailJson) as exc:
133            set_module_args({})
134            my_module()
135        print('Info: %s' % exc.value.args[0]['msg'])
136
137    def test_ensure_user_get_called(self):
138        ''' a more interesting test '''
139        module_args = {}
140        module_args.update(self.set_default_args())
141        module_args.update({'role_name': 'test'})
142        set_module_args(module_args)
143        my_obj = my_module()
144        my_obj.server = self.server
145        user_info = my_obj.get_user()
146        print('Info: test_user_get: %s' % repr(user_info))
147        assert user_info is None
148
149    def test_ensure_user_apply_called(self):
150        ''' creating user and checking idempotency '''
151        module_args = {}
152        module_args.update(self.set_default_args())
153        module_args.update({'name': 'create'})
154        module_args.update({'role_name': 'test'})
155        set_module_args(module_args)
156        my_obj = my_module()
157        if not self.onbox:
158            my_obj.server = self.server
159        with pytest.raises(AnsibleExitJson) as exc:
160            my_obj.apply()
161        print('Info: test_user_apply: %s' % repr(exc.value))
162        assert exc.value.args[0]['changed']
163        if not self.onbox:
164            my_obj.server = MockONTAPConnection('user', 'false')
165        with pytest.raises(AnsibleExitJson) as exc:
166            my_obj.apply()
167        print('Info: test_user_apply: %s' % repr(exc.value))
168        assert exc.value.args[0]['changed']
169
170    def test_ensure_user_apply_for_delete_called(self):
171        ''' deleting user and checking idempotency '''
172        module_args = {}
173        module_args.update(self.set_default_args())
174        module_args.update({'name': 'create'})
175        module_args.update({'role_name': 'test'})
176        set_module_args(module_args)
177        my_obj = my_module()
178        if not self.onbox:
179            my_obj.server = MockONTAPConnection('user', 'false', 'test')
180        with pytest.raises(AnsibleExitJson) as exc:
181            my_obj.apply()
182        print('Info: test_user_apply: %s' % repr(exc.value))
183        assert not exc.value.args[0]['changed']
184        module_args.update({'state': 'absent'})
185        set_module_args(module_args)
186        my_obj = my_module()
187        if not self.onbox:
188            my_obj.server = MockONTAPConnection('user', 'false', 'test')
189        with pytest.raises(AnsibleExitJson) as exc:
190            my_obj.apply()
191        print('Info: test_user_delete: %s' % repr(exc.value))
192        assert exc.value.args[0]['changed']
193
194    def test_ensure_user_lock_called(self):
195        ''' changing user_lock to True and checking idempotency'''
196        module_args = {}
197        module_args.update(self.set_default_args())
198        module_args.update({'name': 'create'})
199        module_args.update({'role_name': 'test'})
200        module_args.update({'lock_user': 'false'})
201        set_module_args(module_args)
202        my_obj = my_module()
203        if not self.onbox:
204            my_obj.server = MockONTAPConnection('user', 'false', 'test')
205        with pytest.raises(AnsibleExitJson) as exc:
206            my_obj.apply()
207        print('Info: test_user_apply: %s' % repr(exc.value))
208        assert not exc.value.args[0]['changed']
209        module_args.update({'lock_user': 'true'})
210        set_module_args(module_args)
211        my_obj = my_module()
212        if not self.onbox:
213            my_obj.server = MockONTAPConnection('user', 'false')
214        with pytest.raises(AnsibleExitJson) as exc:
215            my_obj.apply()
216        print('Info: test_user_lock: %s' % repr(exc.value))
217        assert exc.value.args[0]['changed']
218
219    def test_ensure_user_unlock_called(self):
220        ''' changing user_lock to False and checking idempotency'''
221        module_args = {}
222        module_args.update(self.set_default_args())
223        module_args.update({'name': 'create'})
224        module_args.update({'role_name': 'test'})
225        module_args.update({'lock_user': 'false'})
226        set_module_args(module_args)
227        my_obj = my_module()
228        if not self.onbox:
229            my_obj.server = MockONTAPConnection('user', 'false', 'test')
230        with pytest.raises(AnsibleExitJson) as exc:
231            my_obj.apply()
232        print('Info: test_user_apply: %s' % repr(exc.value))
233        assert not exc.value.args[0]['changed']
234        module_args.update({'lock_user': 'false'})
235        set_module_args(module_args)
236        my_obj = my_module()
237        if not self.onbox:
238            my_obj.server = MockONTAPConnection('user', 'true', 'test')
239        with pytest.raises(AnsibleExitJson) as exc:
240            my_obj.apply()
241        print('Info: test_user_unlock: %s' % repr(exc.value))
242        assert exc.value.args[0]['changed']
243
244    def test_ensure_user_set_password_called(self):
245        ''' set password '''
246        module_args = {}
247        module_args.update(self.set_default_args())
248        module_args.update({'name': 'create'})
249        module_args.update({'role_name': 'test'})
250        module_args.update({'set_password': '123456'})
251        set_module_args(module_args)
252        my_obj = my_module()
253        if not self.onbox:
254            my_obj.server = MockONTAPConnection('user', 'true')
255        with pytest.raises(AnsibleExitJson) as exc:
256            my_obj.apply()
257        print('Info: test_user_apply: %s' % repr(exc.value))
258        assert exc.value.args[0]['changed']
259
260    def test_ensure_user_role_update_called(self):
261        ''' set password '''
262        module_args = {}
263        module_args.update(self.set_default_args())
264        module_args.update({'name': 'create'})
265        module_args.update({'role_name': 'test123'})
266        module_args.update({'set_password': '123456'})
267        set_module_args(module_args)
268        my_obj = my_module()
269        if not self.onbox:
270            my_obj.server = MockONTAPConnection('user', 'true')
271        with pytest.raises(AnsibleExitJson) as exc:
272            my_obj.apply()
273        print('Info: test_user_apply: %s' % repr(exc.value))
274        assert exc.value.args[0]['changed']
275
276    def test_ensure_user_role_update_additional_application_called(self):
277        ''' set password '''
278        module_args = {}
279        module_args.update(self.set_default_args())
280        module_args.update({'name': 'create'})
281        module_args.update({'role_name': 'test123'})
282        module_args.update({'application': 'http'})
283        module_args.update({'set_password': '123456'})
284        set_module_args(module_args)
285        my_obj = my_module()
286        if not self.onbox:
287            my_obj.server = MockONTAPConnection('user', 'true')
288        with pytest.raises(AnsibleExitJson) as exc:
289            my_obj.apply()
290        print('Info: test_user_apply: %s' % repr(exc.value))
291        assert exc.value.args[0]['changed']
292
293    def test_if_all_methods_catch_exception(self):
294        data = self.set_default_args()
295        data.update({'role_name': 'test'})
296        set_module_args(data)
297        my_obj = my_module()
298        if not self.onbox:
299            my_obj.server = MockONTAPConnection('user_fail')
300        with pytest.raises(AnsibleFailJson) as exc:
301            my_obj.get_user()
302        assert 'Error getting user ' in exc.value.args[0]['msg']
303        with pytest.raises(AnsibleFailJson) as exc:
304            my_obj.create_user(data['applications'])
305        assert 'Error creating user ' in exc.value.args[0]['msg']
306        with pytest.raises(AnsibleFailJson) as exc:
307            my_obj.lock_given_user()
308        assert 'Error locking user ' in exc.value.args[0]['msg']
309        with pytest.raises(AnsibleFailJson) as exc:
310            my_obj.unlock_given_user()
311        assert 'Error unlocking user ' in exc.value.args[0]['msg']
312        with pytest.raises(AnsibleFailJson) as exc:
313            my_obj.delete_user(data['applications'])
314        assert 'Error removing user ' in exc.value.args[0]['msg']
315        with pytest.raises(AnsibleFailJson) as exc:
316            my_obj.change_password()
317        assert 'Error setting password for user ' in exc.value.args[0]['msg']
318        with pytest.raises(AnsibleFailJson) as exc:
319            my_obj.modify_user(data['applications'])
320        assert 'Error modifying user ' in exc.value.args[0]['msg']
321