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_igroup_initiator \ 17 import NetAppOntapIgroupInitiator as initiator # 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.data = 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 == 'initiator': 66 xml = self.build_igroup_initiator() 67 elif self.kind == 'initiator_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_igroup_initiator(): 74 ''' build xml data for initiator ''' 75 xml = netapp_utils.zapi.NaElement('xml') 76 attributes = { 77 'num-records': 1, 78 'attributes-list': { 79 'initiator-group-info': { 80 'initiators': [ 81 {'initiator-info': { 82 'initiator-name': 'init1' 83 }}, 84 {'initiator-info': { 85 'initiator-name': 'init2' 86 }} 87 ] 88 } 89 } 90 } 91 xml.translate_struct(attributes) 92 return xml 93 94 95class TestMyModule(unittest.TestCase): 96 ''' a group of related Unit Tests ''' 97 98 def setUp(self): 99 self.mock_module_helper = patch.multiple(basic.AnsibleModule, 100 exit_json=exit_json, 101 fail_json=fail_json) 102 self.mock_module_helper.start() 103 self.addCleanup(self.mock_module_helper.stop) 104 self.server = MockONTAPConnection() 105 106 def mock_args(self): 107 return { 108 'vserver': 'vserver', 109 'name': 'init1', 110 'initiator_group': 'test', 111 'hostname': 'hostname', 112 'username': 'username', 113 'password': 'password' 114 } 115 116 def get_initiator_mock_object(self, kind=None): 117 """ 118 Helper method to return an na_ontap_initiator object 119 :param kind: passes this param to MockONTAPConnection() 120 :return: na_ontap_initiator object 121 """ 122 obj = initiator() 123 obj.autosupport_log = Mock(return_value=None) 124 if kind is None: 125 obj.server = MockONTAPConnection() 126 else: 127 obj.server = MockONTAPConnection(kind=kind) 128 return obj 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 initiator() 135 136 def test_get_nonexistent_initiator(self): 137 ''' Test if get_initiators returns None for non-existent initiator ''' 138 data = self.mock_args() 139 data['name'] = 'idontexist' 140 set_module_args(data) 141 result = self.get_initiator_mock_object('initiator').get_initiators() 142 assert data['name'] not in result 143 144 def test_get_nonexistent_igroup(self): 145 ''' Test if get_initiators returns None for non-existent igroup ''' 146 data = self.mock_args() 147 data['name'] = 'idontexist' 148 set_module_args(data) 149 result = self.get_initiator_mock_object().get_initiators() 150 assert result == [] 151 152 def test_get_existing_initiator(self): 153 ''' Test if get_initiator returns None for existing initiator ''' 154 data = self.mock_args() 155 set_module_args(data) 156 result = self.get_initiator_mock_object(kind='initiator').get_initiators() 157 assert data['name'] in result 158 assert result == ['init1', 'init2'] # from build_igroup_initiators() 159 160 def test_successful_add(self): 161 ''' Test successful add''' 162 data = self.mock_args() 163 data['name'] = 'iamnew' 164 set_module_args(data) 165 obj = self.get_initiator_mock_object('initiator') 166 with pytest.raises(AnsibleExitJson) as exc: 167 current = obj.get_initiators() 168 obj.apply() 169 assert data['name'] not in current 170 assert exc.value.args[0]['changed'] 171 172 def test_successful_add_idempotency(self): 173 ''' Test successful add idempotency ''' 174 data = self.mock_args() 175 set_module_args(data) 176 obj = self.get_initiator_mock_object('initiator') 177 with pytest.raises(AnsibleExitJson) as exc: 178 current_list = obj.get_initiators() 179 obj.apply() 180 assert data['name'] in current_list 181 assert not exc.value.args[0]['changed'] 182 183 def test_successful_remove(self): 184 ''' Test successful remove ''' 185 data = self.mock_args() 186 data['state'] = 'absent' 187 set_module_args(data) 188 obj = self.get_initiator_mock_object('initiator') 189 with pytest.raises(AnsibleExitJson) as exc: 190 current_list = obj.get_initiators() 191 obj.apply() 192 assert data['name'] in current_list 193 assert exc.value.args[0]['changed'] 194 195 def test_successful_remove_idempotency(self): 196 ''' Test successful remove idempotency''' 197 data = self.mock_args() 198 data['state'] = 'absent' 199 data['name'] = 'alreadyremoved' 200 set_module_args(data) 201 obj = self.get_initiator_mock_object('initiator') 202 with pytest.raises(AnsibleExitJson) as exc: 203 current_list = obj.get_initiators() 204 obj.apply() 205 assert data['name'] not in current_list 206 assert not exc.value.args[0]['changed'] 207 208 def test_if_all_methods_catch_exception(self): 209 data = self.mock_args() 210 set_module_args(data) 211 my_obj = self.get_initiator_mock_object('initiator_fail') 212 with pytest.raises(AnsibleFailJson) as exc: 213 my_obj.get_initiators() 214 assert 'Error fetching igroup info ' in exc.value.args[0]['msg'] 215 with pytest.raises(AnsibleFailJson) as exc: 216 my_obj.modify_initiator(data['name'], 'igroup-add') 217 assert 'Error modifying igroup initiator ' in exc.value.args[0]['msg'] 218