1# (c) 2019, 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 for Ansible module: na_ontap_security_certificates """ 5 6from __future__ import (absolute_import, division, print_function) 7__metaclass__ = type 8import json 9import pytest 10import sys 11 12from ansible.module_utils import basic 13from ansible.module_utils._text import to_bytes 14from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch 15import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils 16 17from ansible_collections.netapp.ontap.plugins.modules.na_ontap_security_certificates \ 18 import NetAppOntapSecurityCertificates as my_module # module under test 19 20 21if not netapp_utils.HAS_REQUESTS and sys.version_info < (2, 7): 22 pytestmark = pytest.mark.skip('Skipping Unit Tests on 2.6 as requests is not be available') 23 24 25# REST API canned responses when mocking send_request 26SRR = { 27 # common responses 28 'is_rest': (200, {}, None), 29 'is_zapi': (400, {}, "Unreachable"), 30 'empty_good': (200, {}, None), 31 'end_of_sequence': (500, None, "Unexpected call to send_request"), 32 'generic_error': (400, None, "Expected error"), 33 # module specific responses 34 'empty_records': (200, {'records': []}, None), 35 'get_uuid': (200, {'records': [{'uuid': 'ansible'}]}, None), 36 'error_unexpected_name': (200, None, {'message': 'Unexpected argument "name".'}) 37} 38 39NAME_ERROR = "Error calling API: security/certificates - ONTAP 9.6 and 9.7 do not support 'name'. Use 'common_name' and 'type' as a work-around." 40TYPE_ERROR = "Error calling API: security/certificates - When using 'common_name', 'type' is required." 41EXPECTED_ERROR = "Error calling API: security/certificates - Expected error" 42 43 44def set_module_args(args): 45 """prepare arguments so that they will be picked up during module creation""" 46 args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) 47 basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access 48 49 50class AnsibleExitJson(Exception): 51 """Exception class to be raised by module.exit_json and caught by the test case""" 52 53 54class AnsibleFailJson(Exception): 55 """Exception class to be raised by module.fail_json and caught by the test case""" 56 57 58def exit_json(*args, **kwargs): # pylint: disable=unused-argument 59 """function to patch over exit_json; package return data into an exception""" 60 if 'changed' not in kwargs: 61 kwargs['changed'] = False 62 raise AnsibleExitJson(kwargs) 63 64 65def fail_json(*args, **kwargs): # pylint: disable=unused-argument 66 """function to patch over fail_json; package return data into an exception""" 67 kwargs['failed'] = True 68 raise AnsibleFailJson(kwargs) 69 70 71def set_default_args(): 72 return dict({ 73 'hostname': 'hostname', 74 'username': 'username', 75 'password': 'password', 76 'name': 'name_for_certificate' 77 }) 78 79 80@patch('ansible.module_utils.basic.AnsibleModule.fail_json') 81def test_module_fail_when_required_args_missing(mock_fail): 82 ''' required arguments are reported as errors ''' 83 mock_fail.side_effect = fail_json 84 set_module_args({}) 85 with pytest.raises(AnsibleFailJson) as exc: 86 my_module() 87 print('Info: %s' % exc.value.args[0]['msg']) 88 89 90@patch('ansible.module_utils.basic.AnsibleModule.fail_json') 91@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') 92def test_ensure_get_certificate_called(mock_request, mock_fail): 93 mock_fail.side_effect = fail_json 94 mock_request.side_effect = [ 95 SRR['is_rest'], 96 SRR['get_uuid'], 97 SRR['end_of_sequence'] 98 ] 99 set_module_args(set_default_args()) 100 my_obj = my_module() 101 assert my_obj.get_certificate() is not None 102 103 104@patch('ansible.module_utils.basic.AnsibleModule.fail_json') 105@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') 106def test_rest_error(mock_request, mock_fail): 107 mock_fail.side_effect = fail_json 108 mock_request.side_effect = [ 109 SRR['is_rest'], 110 SRR['generic_error'], 111 SRR['end_of_sequence'] 112 ] 113 set_module_args(set_default_args()) 114 my_obj = my_module() 115 with pytest.raises(AnsibleFailJson) as exc: 116 my_obj.apply() 117 assert exc.value.args[0]['msg'] == EXPECTED_ERROR 118 119 120@patch('ansible.module_utils.basic.AnsibleModule.exit_json') 121@patch('ansible.module_utils.basic.AnsibleModule.fail_json') 122@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') 123def test_rest_create_failed(mock_request, mock_fail, mock_exit): 124 mock_exit.side_effect = exit_json 125 mock_fail.side_effect = fail_json 126 mock_request.side_effect = [ 127 SRR['is_rest'], 128 SRR['empty_records'], # get certificate -> not found 129 SRR['empty_good'], 130 SRR['end_of_sequence'] 131 ] 132 data = { 133 'type': 'client_ca', 134 'vserver': 'abc', 135 } 136 data.update(set_default_args()) 137 set_module_args(data) 138 my_obj = my_module() 139 with pytest.raises(AnsibleFailJson) as exc: 140 my_obj.apply() 141 msg = 'Error creating or installing certificate: one or more of the following options are missing:' 142 assert exc.value.args[0]['msg'].startswith(msg) 143 144 145@patch('ansible.module_utils.basic.AnsibleModule.exit_json') 146@patch('ansible.module_utils.basic.AnsibleModule.fail_json') 147@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') 148def test_rest_successful_create(mock_request, mock_fail, mock_exit): 149 mock_exit.side_effect = exit_json 150 mock_fail.side_effect = fail_json 151 mock_request.side_effect = [ 152 SRR['is_rest'], 153 SRR['empty_records'], # get certificate -> not found 154 SRR['empty_good'], 155 SRR['end_of_sequence'] 156 ] 157 data = { 158 'type': 'client_ca', 159 'vserver': 'abc', 160 'common_name': 'cname' 161 } 162 data.update(set_default_args()) 163 set_module_args(data) 164 my_obj = my_module() 165 with pytest.raises(AnsibleExitJson) as exc: 166 my_obj.apply() 167 assert exc.value.args[0]['changed'] 168 169 170@patch('ansible.module_utils.basic.AnsibleModule.exit_json') 171@patch('ansible.module_utils.basic.AnsibleModule.fail_json') 172@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') 173def test_rest_idempotent_create(mock_request, mock_fail, mock_exit): 174 mock_exit.side_effect = exit_json 175 mock_fail.side_effect = fail_json 176 mock_request.side_effect = [ 177 SRR['is_rest'], 178 SRR['get_uuid'], # get certificate -> found 179 SRR['end_of_sequence'] 180 ] 181 data = { 182 'type': 'client_ca', 183 'vserver': 'abc', 184 } 185 data.update(set_default_args()) 186 set_module_args(data) 187 my_obj = my_module() 188 with pytest.raises(AnsibleExitJson) as exc: 189 my_obj.apply() 190 assert not exc.value.args[0]['changed'] 191 192 193@patch('ansible.module_utils.basic.AnsibleModule.exit_json') 194@patch('ansible.module_utils.basic.AnsibleModule.fail_json') 195@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') 196def test_rest_successful_delete(mock_request, mock_fail, mock_exit): 197 mock_exit.side_effect = exit_json 198 mock_fail.side_effect = fail_json 199 mock_request.side_effect = [ 200 SRR['is_rest'], 201 SRR['get_uuid'], # get certificate -> found 202 SRR['empty_good'], 203 SRR['end_of_sequence'] 204 ] 205 data = { 206 'state': 'absent', 207 } 208 data.update(set_default_args()) 209 set_module_args(data) 210 my_obj = my_module() 211 with pytest.raises(AnsibleExitJson) as exc: 212 my_obj.apply() 213 assert exc.value.args[0]['changed'] 214 215 216@patch('ansible.module_utils.basic.AnsibleModule.exit_json') 217@patch('ansible.module_utils.basic.AnsibleModule.fail_json') 218@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') 219def test_rest_idempotent_delete(mock_request, mock_fail, mock_exit): 220 mock_exit.side_effect = exit_json 221 mock_fail.side_effect = fail_json 222 mock_request.side_effect = [ 223 SRR['is_rest'], 224 SRR['empty_records'], # get certificate -> not found 225 SRR['empty_good'], 226 SRR['end_of_sequence'] 227 ] 228 data = { 229 'state': 'absent', 230 } 231 data.update(set_default_args()) 232 set_module_args(data) 233 my_obj = my_module() 234 with pytest.raises(AnsibleExitJson) as exc: 235 my_obj.apply() 236 assert not exc.value.args[0]['changed'] 237 238 239@patch('ansible.module_utils.basic.AnsibleModule.exit_json') 240@patch('ansible.module_utils.basic.AnsibleModule.fail_json') 241@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') 242def test_rest_successful_sign(mock_request, mock_fail, mock_exit): 243 mock_exit.side_effect = exit_json 244 mock_fail.side_effect = fail_json 245 mock_request.side_effect = [ 246 SRR['is_rest'], 247 SRR['get_uuid'], # get certificate -> found 248 SRR['empty_good'], 249 SRR['end_of_sequence'] 250 ] 251 data = { 252 'vserver': 'abc', 253 'signing_request': 'CSR' 254 } 255 data.update(set_default_args()) 256 set_module_args(data) 257 my_obj = my_module() 258 with pytest.raises(AnsibleExitJson) as exc: 259 my_obj.apply() 260 assert exc.value.args[0]['changed'] 261 262 263@patch('ansible.module_utils.basic.AnsibleModule.exit_json') 264@patch('ansible.module_utils.basic.AnsibleModule.fail_json') 265@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') 266def test_rest_failed_sign_missing_ca(mock_request, mock_fail, mock_exit): 267 mock_exit.side_effect = exit_json 268 mock_fail.side_effect = fail_json 269 mock_request.side_effect = [ 270 SRR['is_rest'], 271 SRR['empty_records'], # get certificate -> not found 272 SRR['empty_good'], 273 SRR['end_of_sequence'] 274 ] 275 data = { 276 'vserver': 'abc', 277 'signing_request': 'CSR' 278 } 279 data.update(set_default_args()) 280 set_module_args(data) 281 my_obj = my_module() 282 with pytest.raises(AnsibleFailJson) as exc: 283 my_obj.apply() 284 msg = "signing certificate with name '%s' not found on svm: %s" % (data['name'], data['vserver']) 285 assert exc.value.args[0]['msg'] == msg 286 287 288@patch('ansible.module_utils.basic.AnsibleModule.exit_json') 289@patch('ansible.module_utils.basic.AnsibleModule.fail_json') 290@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') 291def test_rest_failed_sign_absent(mock_request, mock_fail, mock_exit): 292 mock_exit.side_effect = exit_json 293 mock_fail.side_effect = fail_json 294 mock_request.side_effect = [ 295 SRR['is_rest'], 296 SRR['get_uuid'], # get certificate -> found 297 SRR['end_of_sequence'] 298 ] 299 data = { 300 'vserver': 'abc', 301 'signing_request': 'CSR', 302 'state': 'absent' 303 } 304 data.update(set_default_args()) 305 set_module_args(data) 306 my_obj = my_module() 307 with pytest.raises(AnsibleFailJson) as exc: 308 my_obj.apply() 309 msg = "'signing_request' is not supported with 'state' set to 'absent'" 310 assert exc.value.args[0]['msg'] == msg 311 312 313@patch('ansible.module_utils.basic.AnsibleModule.exit_json') 314@patch('ansible.module_utils.basic.AnsibleModule.fail_json') 315@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') 316def test_rest_failed_on_name(mock_request, mock_fail, mock_exit): 317 mock_exit.side_effect = exit_json 318 mock_fail.side_effect = fail_json 319 mock_request.side_effect = [ 320 SRR['is_rest'], 321 SRR['error_unexpected_name'], # get certificate -> error 322 SRR['end_of_sequence'] 323 ] 324 data = { 325 'vserver': 'abc', 326 'signing_request': 'CSR', 327 'state': 'absent', 328 'ignore_name_if_not_supported': False, 329 'common_name': 'common_name', 330 'type': 'root_ca' 331 } 332 data.update(set_default_args()) 333 set_module_args(data) 334 my_obj = my_module() 335 with pytest.raises(AnsibleFailJson) as exc: 336 my_obj.apply() 337 assert exc.value.args[0]['msg'] == NAME_ERROR 338 339 340@patch('ansible.module_utils.basic.AnsibleModule.exit_json') 341@patch('ansible.module_utils.basic.AnsibleModule.fail_json') 342@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') 343def test_rest_cannot_ignore_name_error_no_common_name(mock_request, mock_fail, mock_exit): 344 mock_exit.side_effect = exit_json 345 mock_fail.side_effect = fail_json 346 mock_request.side_effect = [ 347 SRR['is_rest'], 348 SRR['error_unexpected_name'], # get certificate -> error 349 SRR['end_of_sequence'] 350 ] 351 data = { 352 'vserver': 'abc', 353 'signing_request': 'CSR', 354 'state': 'absent', 355 } 356 data.update(set_default_args()) 357 set_module_args(data) 358 my_obj = my_module() 359 with pytest.raises(AnsibleFailJson) as exc: 360 my_obj.apply() 361 assert exc.value.args[0]['msg'] == NAME_ERROR 362 363 364@patch('ansible.module_utils.basic.AnsibleModule.exit_json') 365@patch('ansible.module_utils.basic.AnsibleModule.fail_json') 366@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') 367def test_rest_cannot_ignore_name_error_no_type(mock_request, mock_fail, mock_exit): 368 mock_exit.side_effect = exit_json 369 mock_fail.side_effect = fail_json 370 mock_request.side_effect = [ 371 SRR['is_rest'], 372 SRR['error_unexpected_name'], # get certificate -> error 373 SRR['end_of_sequence'] 374 ] 375 data = { 376 'vserver': 'abc', 377 'signing_request': 'CSR', 378 'state': 'absent', 379 'common_name': 'common_name' 380 } 381 data.update(set_default_args()) 382 set_module_args(data) 383 my_obj = my_module() 384 with pytest.raises(AnsibleFailJson) as exc: 385 my_obj.apply() 386 assert exc.value.args[0]['msg'] == TYPE_ERROR 387 388 389@patch('ansible.module_utils.basic.AnsibleModule.exit_json') 390@patch('ansible.module_utils.basic.AnsibleModule.fail_json') 391@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') 392def test_rest_ignore_name_error(mock_request, mock_fail, mock_exit): 393 mock_exit.side_effect = exit_json 394 mock_fail.side_effect = fail_json 395 mock_request.side_effect = [ 396 SRR['is_rest'], 397 SRR['error_unexpected_name'], # get certificate -> error 398 SRR['get_uuid'], # get certificate -> found 399 SRR['end_of_sequence'] 400 ] 401 data = { 402 'vserver': 'abc', 403 'signing_request': 'CSR', 404 'state': 'absent', 405 'common_name': 'common_name', 406 'type': 'root_ca' 407 } 408 data.update(set_default_args()) 409 set_module_args(data) 410 my_obj = my_module() 411 with pytest.raises(AnsibleFailJson) as exc: 412 my_obj.apply() 413 msg = "'signing_request' is not supported with 'state' set to 'absent'" 414 assert exc.value.args[0]['msg'] == msg 415 416 417@patch('ansible.module_utils.basic.AnsibleModule.exit_json') 418@patch('ansible.module_utils.basic.AnsibleModule.fail_json') 419@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') 420def test_rest_successful_create_name_error(mock_request, mock_fail, mock_exit): 421 mock_exit.side_effect = exit_json 422 mock_fail.side_effect = fail_json 423 mock_request.side_effect = [ 424 SRR['is_rest'], 425 SRR['error_unexpected_name'], # get certificate -> error 426 SRR['empty_records'], # get certificate -> not found 427 SRR['empty_good'], 428 SRR['end_of_sequence'] 429 ] 430 data = { 431 'common_name': 'cname', 432 'type': 'client_ca', 433 'vserver': 'abc', 434 } 435 data.update(set_default_args()) 436 set_module_args(data) 437 my_obj = my_module() 438 with pytest.raises(AnsibleExitJson) as exc: 439 my_obj.apply() 440 assert exc.value.args[0]['changed'] 441 print(mock_request.mock_calls) 442