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_user \ 17 import NetAppOntapUnixUser as user_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 == 'user': 66 xml = self.build_user_info(self.params) 67 elif self.kind == 'user-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_user_info(data): 74 ''' build xml data for vserser-info ''' 75 xml = netapp_utils.zapi.NaElement('xml') 76 attributes = \ 77 {'attributes-list': {'unix-user-info': {'user-id': data['id'], 78 'group-id': data['group_id'], 'full-name': data['full_name']}}, 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_user = { 95 'name': 'test', 96 'id': '11', 97 'group_id': '12', 98 'vserver': 'something', 99 'full_name': 'Test User' 100 } 101 102 def mock_args(self): 103 return { 104 'name': self.mock_user['name'], 105 'group_id': self.mock_user['group_id'], 106 'id': self.mock_user['id'], 107 'vserver': self.mock_user['vserver'], 108 'full_name': self.mock_user['full_name'], 109 'hostname': 'test', 110 'username': 'test_user', 111 'password': 'test_pass!' 112 } 113 114 def get_user_mock_object(self, kind=None, data=None): 115 """ 116 Helper method to return an na_ontap_unix_user object 117 :param kind: passes this param to MockONTAPConnection() 118 :return: na_ontap_unix_user object 119 """ 120 obj = user_module() 121 obj.autosupport_log = Mock(return_value=None) 122 if data is None: 123 data = self.mock_user 124 obj.server = MockONTAPConnection(kind=kind, data=data) 125 return obj 126 127 def test_module_fail_when_required_args_missing(self): 128 ''' required arguments are reported as errors ''' 129 with pytest.raises(AnsibleFailJson) as exc: 130 set_module_args({}) 131 user_module() 132 133 def test_get_nonexistent_user(self): 134 ''' Test if get_unix_user returns None for non-existent user ''' 135 set_module_args(self.mock_args()) 136 result = self.get_user_mock_object().get_unix_user() 137 assert result is None 138 139 def test_get_existing_user(self): 140 ''' Test if get_unix_user returns details for existing user ''' 141 set_module_args(self.mock_args()) 142 result = self.get_user_mock_object('user').get_unix_user() 143 assert result['full_name'] == self.mock_user['full_name'] 144 145 def test_get_xml(self): 146 set_module_args(self.mock_args()) 147 obj = self.get_user_mock_object('user') 148 result = obj.get_unix_user() 149 assert obj.server.xml_in['query'] 150 assert obj.server.xml_in['query']['unix-user-info'] 151 user_info = obj.server.xml_in['query']['unix-user-info'] 152 assert user_info['user-name'] == self.mock_user['name'] 153 assert user_info['vserver'] == self.mock_user['vserver'] 154 155 def test_create_error_missing_params(self): 156 data = self.mock_args() 157 del data['group_id'] 158 set_module_args(data) 159 with pytest.raises(AnsibleFailJson) as exc: 160 self.get_user_mock_object('user').create_unix_user() 161 assert 'Error: Missing one or more required parameters for create: (group_id, id)' == exc.value.args[0]['msg'] 162 163 @patch('ansible.modules.storage.netapp.na_ontap_unix_user.NetAppOntapUnixUser.create_unix_user') 164 def test_create_called(self, create_user): 165 set_module_args(self.mock_args()) 166 with pytest.raises(AnsibleExitJson) as exc: 167 self.get_user_mock_object().apply() 168 assert exc.value.args[0]['changed'] 169 create_user.assert_called_with() 170 171 def test_create_xml(self): 172 '''Test create ZAPI element''' 173 set_module_args(self.mock_args()) 174 create = self.get_user_mock_object() 175 with pytest.raises(AnsibleExitJson) as exc: 176 create.apply() 177 mock_key = { 178 'user-name': 'name', 179 'group-id': 'group_id', 180 'user-id': 'id', 181 'full-name': 'full_name' 182 } 183 for key in ['user-name', 'user-id', 'group-id', 'full-name']: 184 assert create.server.xml_in[key] == self.mock_user[mock_key[key]] 185 186 def test_create_wihtout_full_name(self): 187 '''Test create ZAPI element''' 188 data = self.mock_args() 189 del data['full_name'] 190 set_module_args(data) 191 create = self.get_user_mock_object() 192 with pytest.raises(AnsibleExitJson) as exc: 193 create.apply() 194 with pytest.raises(KeyError): 195 create.server.xml_in['full-name'] 196 197 @patch('ansible.modules.storage.netapp.na_ontap_unix_user.NetAppOntapUnixUser.modify_unix_user') 198 @patch('ansible.modules.storage.netapp.na_ontap_unix_user.NetAppOntapUnixUser.delete_unix_user') 199 def test_delete_called(self, delete_user, modify_user): 200 ''' Test delete existing user ''' 201 data = self.mock_args() 202 data['state'] = 'absent' 203 set_module_args(data) 204 with pytest.raises(AnsibleExitJson) as exc: 205 self.get_user_mock_object('user').apply() 206 assert exc.value.args[0]['changed'] 207 delete_user.assert_called_with() 208 assert modify_user.call_count == 0 209 210 @patch('ansible.modules.storage.netapp.na_ontap_unix_user.NetAppOntapUnixUser.get_unix_user') 211 @patch('ansible.modules.storage.netapp.na_ontap_unix_user.NetAppOntapUnixUser.modify_unix_user') 212 def test_modify_called(self, modify_user, get_user): 213 ''' Test modify user group_id ''' 214 data = self.mock_args() 215 data['group_id'] = 20 216 set_module_args(data) 217 get_user.return_value = {'group_id': 10} 218 obj = self.get_user_mock_object('user') 219 with pytest.raises(AnsibleExitJson) as exc: 220 obj.apply() 221 get_user.assert_called_with() 222 modify_user.assert_called_with({'group_id': 20}) 223 224 def test_modify_only_id(self): 225 ''' Test modify user id ''' 226 set_module_args(self.mock_args()) 227 modify = self.get_user_mock_object('user') 228 modify.modify_unix_user({'id': 123}) 229 assert modify.server.xml_in['user-id'] == '123' 230 with pytest.raises(KeyError): 231 modify.server.xml_in['group-id'] 232 with pytest.raises(KeyError): 233 modify.server.xml_in['full-name'] 234 235 def test_modify_xml(self): 236 ''' Test modify user full_name ''' 237 set_module_args(self.mock_args()) 238 modify = self.get_user_mock_object('user') 239 modify.modify_unix_user({'full_name': 'New Name', 240 'group_id': '25'}) 241 assert modify.server.xml_in['user-name'] == self.mock_user['name'] 242 assert modify.server.xml_in['full-name'] == 'New Name' 243 assert modify.server.xml_in['group-id'] == '25' 244 245 @patch('ansible.modules.storage.netapp.na_ontap_unix_user.NetAppOntapUnixUser.create_unix_user') 246 @patch('ansible.modules.storage.netapp.na_ontap_unix_user.NetAppOntapUnixUser.delete_unix_user') 247 @patch('ansible.modules.storage.netapp.na_ontap_unix_user.NetAppOntapUnixUser.modify_unix_user') 248 def test_do_nothing(self, modify, delete, create): 249 ''' changed is False and none of the opetaion methods are called''' 250 data = self.mock_args() 251 data['state'] = 'absent' 252 set_module_args(data) 253 obj = self.get_user_mock_object() 254 with pytest.raises(AnsibleExitJson) as exc: 255 obj.apply() 256 create.assert_not_called() 257 delete.assert_not_called() 258 modify.assert_not_called() 259 260 def test_get_exception(self): 261 set_module_args(self.mock_args()) 262 with pytest.raises(AnsibleFailJson) as exc: 263 self.get_user_mock_object('user-fail').get_unix_user() 264 assert 'Error getting UNIX user' in exc.value.args[0]['msg'] 265 266 def test_create_exception(self): 267 set_module_args(self.mock_args()) 268 with pytest.raises(AnsibleFailJson) as exc: 269 self.get_user_mock_object('user-fail').create_unix_user() 270 assert 'Error creating UNIX user' in exc.value.args[0]['msg'] 271 272 def test_modify_exception(self): 273 set_module_args(self.mock_args()) 274 with pytest.raises(AnsibleFailJson) as exc: 275 self.get_user_mock_object('user-fail').modify_unix_user({'id': '123'}) 276 assert 'Error modifying UNIX user' in exc.value.args[0]['msg'] 277 278 def test_delete_exception(self): 279 set_module_args(self.mock_args()) 280 with pytest.raises(AnsibleFailJson) as exc: 281 self.get_user_mock_object('user-fail').delete_unix_user() 282 assert 'Error removing UNIX user' in exc.value.args[0]['msg'] 283