1''' unit tests ONTAP Ansible module: na_ontap_cifs_local_group_member '''
2from __future__ import (absolute_import, division, print_function)
3__metaclass__ = type
4import json
5import pytest
6
7from ansible.module_utils import basic
8from ansible.module_utils._text import to_bytes
9from ansible_collections.netapp.ontap.tests.unit.compat import unittest
10from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch
11import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils
12
13from ansible_collections.netapp.ontap.plugins.modules.na_ontap_cifs_local_group_member \
14    import NetAppOntapCifsLocalGroupMember as group_member_module  # module under test
15
16
17if not netapp_utils.has_netapp_lib():
18    pytestmark = pytest.mark.skip('skipping as missing required netapp_lib')
19
20# REST API canned responses when mocking send_request
21SRR = {
22    # common responses
23    'is_rest': (200, {}, None),
24    'is_zapi': (400, {}, "Unreachable"),
25    'empty_good': (200, {}, None),
26    'end_of_sequence': (500, None, "Ooops, the UT needs one more SRR response"),
27    'generic_error': (400, None, "Expected error"),
28    # module specific responses
29    'group_member_record': (200, {
30        "records": [{
31            'vserver': 'ansible',
32            'group_name': 'BUILTIN\\Guests',
33            'member': 'test'
34        }]
35    }, None)
36}
37
38
39def set_module_args(args):
40    """prepare arguments so that they will be picked up during module creation"""
41    args = json.dumps({'ANSIBLE_MODULE_ARGS': args})
42    basic._ANSIBLE_ARGS = to_bytes(args)  # pylint: disable=protected-access
43
44
45class AnsibleExitJson(Exception):
46    """Exception class to be raised by module.exit_json and caught by the test case"""
47
48
49class AnsibleFailJson(Exception):
50    """Exception class to be raised by module.fail_json and caught by the test case"""
51
52
53def exit_json(*args, **kwargs):  # pylint: disable=unused-argument
54    """function to patch over exit_json; package return data into an exception"""
55    if 'changed' not in kwargs:
56        kwargs['changed'] = False
57    raise AnsibleExitJson(kwargs)
58
59
60def fail_json(*args, **kwargs):  # pylint: disable=unused-argument
61    """function to patch over fail_json; package return data into an exception"""
62    kwargs['failed'] = True
63    raise AnsibleFailJson(kwargs)
64
65
66class MockONTAPConnection(object):
67    ''' mock server connection to ONTAP host '''
68
69    def __init__(self, kind=None):
70        ''' save arguments '''
71        self.type = kind
72        self.xml_in = None
73        self.xml_out = None
74
75    def invoke_successfully(self, xml, enable_tunneling):  # pylint: disable=unused-argument
76        ''' mock invoke_successfully returning xml data '''
77        self.xml_in = xml
78        if self.type == 'group_member':
79            xml = self.build_group_member_info()
80        elif self.type == 'group_member_fail':
81            raise netapp_utils.zapi.NaApiError(code='TEST', message="This exception is from the unit test")
82        self.xml_out = xml
83        return xml
84
85    @staticmethod
86    def build_group_member_info():
87        ''' build xml data for cifs-local-group-members '''
88        xml = netapp_utils.zapi.NaElement('xml')
89        data = {'attributes-list': {'cifs-local-group-members': {'group-name': 'BUILTIN\\GUESTS', 'member': 'test', 'vserver': 'ansible'}}}
90        xml.translate_struct(data)
91        return xml
92
93
94class TestMyModule(unittest.TestCase):
95    ''' a group of related Unit Tests '''
96
97    def setUp(self):
98        self.mock_module_helper = patch.multiple(basic.AnsibleModule,
99                                                 exit_json=exit_json,
100                                                 fail_json=fail_json)
101        self.mock_module_helper.start()
102        self.addCleanup(self.mock_module_helper.stop)
103        self.server = MockONTAPConnection()
104        self.onbox = False
105
106    def set_default_args(self, use_rest=None):
107        if self.onbox:
108            hostname = '10.10.10.10'
109            username = 'username'
110            password = 'password'
111            vserver = 'ansible'
112            group = 'BUILTIN\\Guests'
113            member = 'test'
114
115        else:
116            hostname = '10.10.10.10'
117            username = 'username'
118            password = 'password'
119            vserver = 'ansible'
120            group = 'BUILTIN\\Guests'
121            member = 'test'
122
123        args = dict({
124            'state': 'present',
125            'hostname': hostname,
126            'username': username,
127            'password': password,
128            'vserver': vserver,
129            'group': group,
130            'member': member
131        })
132
133        if use_rest is not None:
134            args['use_rest'] = use_rest
135
136        return args
137
138    @staticmethod
139    def get_group_member_mock_object(cx_type='zapi', kind=None):
140        group_member_obj = group_member_module()
141        if cx_type == 'zapi':
142            if kind is None:
143                group_member_obj.server = MockONTAPConnection()
144            else:
145                group_member_obj.server = MockONTAPConnection(kind=kind)
146        return group_member_obj
147
148    def test_module_fail_when_required_args_missing(self):
149        ''' required arguments are reported as errors '''
150        with pytest.raises(AnsibleFailJson) as exc:
151            set_module_args({})
152            group_member_module()
153        print('Info: %s' % exc.value.args[0]['msg'])
154
155    def test_ensure_get_called(self):
156        ''' test get_cifs_local_group_member for non-existent config'''
157        set_module_args(self.set_default_args(use_rest='Never'))
158        print('starting')
159        my_obj = group_member_module()
160        print('use_rest:', my_obj.use_rest)
161        my_obj.server = self.server
162        assert my_obj.get_cifs_local_group_member is not None
163
164    def test_ensure_get_called_existing(self):
165        ''' test get_cifs_local_group_member for existing config'''
166        set_module_args(self.set_default_args(use_rest='Never'))
167        my_obj = group_member_module()
168        my_obj.server = MockONTAPConnection(kind='group_member')
169        assert my_obj.get_cifs_local_group_member()
170
171    @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_cifs_local_group_member.NetAppOntapCifsLocalGroupMember.add_cifs_local_group_member')
172    def test_successful_create(self, add_cifs_local_group_member):
173        ''' adding member to local-group and testing idempotency '''
174        set_module_args(self.set_default_args(use_rest='Never'))
175        my_obj = group_member_module()
176        if not self.onbox:
177            my_obj.server = self.server
178        with pytest.raises(AnsibleExitJson) as exc:
179            my_obj.apply()
180        assert exc.value.args[0]['changed']
181        add_cifs_local_group_member.assert_called_with()
182        # to reset na_helper from remembering the previous 'changed' value
183        set_module_args(self.set_default_args(use_rest='Never'))
184        my_obj = group_member_module()
185        if not self.onbox:
186            my_obj.server = MockONTAPConnection('group_member')
187        with pytest.raises(AnsibleExitJson) as exc:
188            my_obj.apply()
189        assert not exc.value.args[0]['changed']
190
191    @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_cifs_local_group_member.NetAppOntapCifsLocalGroupMember.remove_cifs_local_group_member')
192    def test_successful_delete(self, remove_cifs_local_group_member):
193        ''' removing member from local-group and testing idempotency '''
194        data = self.set_default_args(use_rest='Never')
195        data['state'] = 'absent'
196        set_module_args(data)
197        my_obj = group_member_module()
198        if not self.onbox:
199            my_obj.server = MockONTAPConnection('group_member')
200        with pytest.raises(AnsibleExitJson) as exc:
201            my_obj.apply()
202        assert exc.value.args[0]['changed']
203        # remove_cifs_local_group_member.assert_called_with()
204        # to reset na_helper from remembering the previous 'changed' value
205        my_obj = group_member_module()
206        if not self.onbox:
207            my_obj.server = self.server
208        with pytest.raises(AnsibleExitJson) as exc:
209            my_obj.apply()
210        assert not exc.value.args[0]['changed']
211
212    def test_if_all_methods_catch_exception(self):
213        data = self.set_default_args(use_rest='Never')
214        set_module_args(data)
215        my_obj = group_member_module()
216        if not self.onbox:
217            my_obj.server = MockONTAPConnection('group_member_fail')
218        with pytest.raises(AnsibleFailJson) as exc:
219            my_obj.add_cifs_local_group_member()
220        assert 'Error adding member ' in exc.value.args[0]['msg']
221        with pytest.raises(AnsibleFailJson) as exc:
222            my_obj.remove_cifs_local_group_member()
223        assert 'Error removing member ' in exc.value.args[0]['msg']
224
225    @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request')
226    def test_rest_error(self, mock_request):
227        data = self.set_default_args()
228        set_module_args(data)
229        mock_request.side_effect = [
230            SRR['is_rest'],
231            SRR['generic_error'],
232            SRR['end_of_sequence']
233        ]
234        with pytest.raises(AnsibleFailJson) as exc:
235            self.get_group_member_mock_object(cx_type='rest').apply()
236        assert exc.value.args[0]['msg'] == SRR['generic_error'][2]
237
238    @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request')
239    def test_successful_create_rest(self, mock_request):
240        data = self.set_default_args()
241        set_module_args(data)
242        mock_request.side_effect = [
243            SRR['is_rest'],
244            SRR['empty_good'],  # get
245            SRR['empty_good'],  # post
246            SRR['end_of_sequence']
247        ]
248        with pytest.raises(AnsibleExitJson) as exc:
249            self.get_group_member_mock_object(cx_type='rest').apply()
250        assert exc.value.args[0]['changed']
251
252    @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request')
253    def test_idempotent_create_rest(self, mock_request):
254        data = self.set_default_args()
255        set_module_args(data)
256        mock_request.side_effect = [
257            SRR['is_rest'],
258            SRR['group_member_record'],  # get
259            SRR['end_of_sequence']
260        ]
261        with pytest.raises(AnsibleExitJson) as exc:
262            self.get_group_member_mock_object(cx_type='rest').apply()
263        assert not exc.value.args[0]['changed']
264
265    @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request')
266    def test_successful_delete_rest(self, mock_request):
267        data = self.set_default_args()
268        data['state'] = 'absent'
269        set_module_args(data)
270        mock_request.side_effect = [
271            SRR['is_rest'],
272            SRR['group_member_record'],  # get
273            SRR['empty_good'],  # delete
274            SRR['end_of_sequence']
275        ]
276        with pytest.raises(AnsibleExitJson) as exc:
277            self.get_group_member_mock_object(cx_type='rest').apply()
278        assert exc.value.args[0]['changed']
279
280    @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request')
281    def test_idempotent_delete_rest(self, mock_request):
282        data = self.set_default_args()
283        data['state'] = 'absent'
284        set_module_args(data)
285        mock_request.side_effect = [
286            SRR['is_rest'],
287            SRR['empty_good'],  # get
288            SRR['end_of_sequence']
289        ]
290        with pytest.raises(AnsibleExitJson) as exc:
291            self.get_group_member_mock_object(cx_type='rest').apply()
292        assert not exc.value.args[0]['changed']
293