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