11import pytest
22import requests
3+ import time
34from unittest .mock import patch , MagicMock
45from apimrequests import ApimRequests
5- from apimtypes import SUBSCRIPTION_KEY_PARAMETER_NAME
6+ from apimtypes import SUBSCRIPTION_KEY_PARAMETER_NAME , HTTP_VERB
67
78# Sample values for tests
89default_url = 'https://example.com/apim/'
@@ -310,4 +311,363 @@ def test_apim_requests_without_subscription_key():
310311 assert apim .url == default_url
311312 assert apim .apimSubscriptionKey is None
312313 assert SUBSCRIPTION_KEY_PARAMETER_NAME not in apim .headers
313- assert apim .headers ['Accept' ] == 'application/json'
314+ assert apim .headers ['Accept' ] == 'application/json'
315+
316+
317+ @pytest .mark .unit
318+ def test_headers_setter (apim ):
319+ """Test the headers setter property."""
320+ new_headers = {'Authorization' : 'Bearer token' , 'Custom' : 'value' }
321+ apim .headers = new_headers
322+ assert apim .headers == new_headers
323+
324+
325+ @pytest .mark .unit
326+ @patch ('apimrequests.requests.request' )
327+ @patch ('apimrequests.utils.print_message' )
328+ @patch ('apimrequests.utils.print_info' )
329+ def test_request_with_message (mock_print_info , mock_print_message , mock_request , apim ):
330+ """Test _request method with message parameter."""
331+ mock_response = MagicMock ()
332+ mock_response .status_code = 200
333+ mock_response .headers = {'Content-Type' : 'application/json' }
334+ mock_response .json .return_value = {'result' : 'ok' }
335+ mock_response .text = '{"result": "ok"}'
336+ mock_request .return_value = mock_response
337+
338+ with patch .object (apim , '_print_response' ):
339+ apim ._request (HTTP_VERB .GET , '/test' , msg = 'Test message' )
340+
341+ mock_print_message .assert_called_once_with ('Test message' , blank_above = True )
342+
343+
344+ @pytest .mark .unit
345+ @patch ('apimrequests.requests.request' )
346+ @patch ('apimrequests.utils.print_info' )
347+ def test_request_path_without_leading_slash (mock_print_info , mock_request , apim ):
348+ """Test _request method with path without leading slash."""
349+ mock_response = MagicMock ()
350+ mock_response .status_code = 200
351+ mock_response .headers = {'Content-Type' : 'application/json' }
352+ mock_response .json .return_value = {'result' : 'ok' }
353+ mock_response .text = '{"result": "ok"}'
354+ mock_request .return_value = mock_response
355+
356+ with patch .object (apim , '_print_response' ):
357+ apim ._request (HTTP_VERB .GET , 'test' )
358+
359+ # Should call with the corrected URL
360+ expected_url = default_url + '/test'
361+ mock_request .assert_called_once ()
362+ args , kwargs = mock_request .call_args
363+ assert args [1 ] == expected_url
364+
365+
366+ @pytest .mark .unit
367+ @patch ('apimrequests.requests.Session' )
368+ @patch ('apimrequests.utils.print_message' )
369+ @patch ('apimrequests.utils.print_info' )
370+ def test_multi_request_with_message (mock_print_info , mock_print_message , mock_session_class , apim ):
371+ """Test _multiRequest method with message parameter."""
372+ mock_session = MagicMock ()
373+ mock_session_class .return_value = mock_session
374+
375+ mock_response = MagicMock ()
376+ mock_response .status_code = 200
377+ mock_response .headers = {'Content-Type' : 'application/json' }
378+ mock_response .json .return_value = {'result' : 'ok' }
379+ mock_response .text = '{"result": "ok"}'
380+ mock_session .request .return_value = mock_response
381+
382+ with patch .object (apim , '_print_response_code' ):
383+ result = apim ._multiRequest (HTTP_VERB .GET , '/test' , 1 , msg = 'Multi-request message' )
384+
385+ mock_print_message .assert_called_once_with ('Multi-request message' , blank_above = True )
386+ assert len (result ) == 1
387+
388+
389+ @pytest .mark .unit
390+ @patch ('apimrequests.requests.Session' )
391+ @patch ('apimrequests.utils.print_info' )
392+ def test_multi_request_path_without_leading_slash (mock_print_info , mock_session_class , apim ):
393+ """Test _multiRequest method with path without leading slash."""
394+ mock_session = MagicMock ()
395+ mock_session_class .return_value = mock_session
396+
397+ mock_response = MagicMock ()
398+ mock_response .status_code = 200
399+ mock_response .headers = {'Content-Type' : 'application/json' }
400+ mock_response .json .return_value = {'result' : 'ok' }
401+ mock_response .text = '{"result": "ok"}'
402+ mock_session .request .return_value = mock_response
403+
404+ with patch .object (apim , '_print_response_code' ):
405+ apim ._multiRequest (HTTP_VERB .GET , 'test' , 1 )
406+
407+ # Should call with the corrected URL
408+ expected_url = default_url + '/test'
409+ mock_session .request .assert_called_once ()
410+ args , kwargs = mock_session .request .call_args
411+ assert args [1 ] == expected_url
412+
413+
414+ @pytest .mark .unit
415+ @patch ('apimrequests.requests.Session' )
416+ @patch ('apimrequests.utils.print_info' )
417+ def test_multi_request_non_json_response (mock_print_info , mock_session_class , apim ):
418+ """Test _multiRequest method with non-JSON response."""
419+ mock_session = MagicMock ()
420+ mock_session_class .return_value = mock_session
421+
422+ mock_response = MagicMock ()
423+ mock_response .status_code = 200
424+ mock_response .headers = {'Content-Type' : 'text/plain' }
425+ mock_response .text = 'Plain text response'
426+ mock_session .request .return_value = mock_response
427+
428+ with patch .object (apim , '_print_response_code' ):
429+ result = apim ._multiRequest (HTTP_VERB .GET , '/test' , 1 )
430+
431+ assert len (result ) == 1
432+ assert result [0 ]['response' ] == 'Plain text response'
433+
434+
435+ @pytest .mark .unit
436+ @patch ('apimrequests.utils.print_val' )
437+ def test_print_response_non_200_status (mock_print_val , apim ):
438+ """Test _print_response method with non-200 status code."""
439+ mock_response = MagicMock ()
440+ mock_response .status_code = 404
441+ mock_response .reason = 'Not Found'
442+ mock_response .headers = {'Content-Type' : 'application/json' }
443+ mock_response .text = '{"error": "not found"}'
444+
445+ with patch .object (apim , '_print_response_code' ):
446+ apim ._print_response (mock_response )
447+
448+ # Should print response body directly for non-200 status
449+ mock_print_val .assert_any_call ('Response body' , '{"error": "not found"}' , True )
450+
451+
452+ @pytest .mark .unit
453+ @patch ('apimrequests.requests.get' )
454+ @patch ('apimrequests.utils.print_info' )
455+ @patch ('apimrequests.utils.print_ok' )
456+ @patch ('apimrequests.time.sleep' )
457+ def test_poll_async_operation_success (mock_sleep , mock_print_ok , mock_print_info , mock_get , apim ):
458+ """Test _poll_async_operation method with successful completion."""
459+ mock_response = MagicMock ()
460+ mock_response .status_code = 200
461+ mock_get .return_value = mock_response
462+
463+ result = apim ._poll_async_operation ('http://example.com/operation/123' )
464+
465+ assert result == mock_response
466+ mock_print_ok .assert_called_once_with ('Async operation completed successfully!' )
467+
468+
469+ @pytest .mark .unit
470+ @patch ('apimrequests.requests.get' )
471+ @patch ('apimrequests.utils.print_info' )
472+ @patch ('apimrequests.utils.print_error' )
473+ @patch ('apimrequests.time.sleep' )
474+ def test_poll_async_operation_in_progress_then_success (mock_sleep , mock_print_error , mock_print_info , mock_get , apim ):
475+ """Test _poll_async_operation method with in-progress then success."""
476+ # First call returns 202 (in progress), second call returns 200 (complete)
477+ responses = [
478+ MagicMock (status_code = 202 ),
479+ MagicMock (status_code = 200 )
480+ ]
481+ mock_get .side_effect = responses
482+
483+ result = apim ._poll_async_operation ('http://example.com/operation/123' , poll_interval = 1 )
484+
485+ assert result == responses [1 ] # Should return the final success response
486+ assert mock_get .call_count == 2
487+ mock_sleep .assert_called_once_with (1 )
488+
489+
490+ @pytest .mark .unit
491+ @patch ('apimrequests.requests.get' )
492+ @patch ('apimrequests.utils.print_error' )
493+ def test_poll_async_operation_unexpected_status (mock_print_error , mock_get , apim ):
494+ """Test _poll_async_operation method with unexpected status code."""
495+ mock_response = MagicMock ()
496+ mock_response .status_code = 500
497+ mock_get .return_value = mock_response
498+
499+ result = apim ._poll_async_operation ('http://example.com/operation/123' )
500+
501+ assert result == mock_response # Should return the error response
502+ mock_print_error .assert_called_with ('Unexpected status code during polling: 500' )
503+
504+
505+ @pytest .mark .unit
506+ @patch ('apimrequests.requests.get' )
507+ @patch ('apimrequests.utils.print_error' )
508+ def test_poll_async_operation_request_exception (mock_print_error , mock_get , apim ):
509+ """Test _poll_async_operation method with request exception."""
510+ mock_get .side_effect = requests .exceptions .RequestException ('Connection error' )
511+
512+ result = apim ._poll_async_operation ('http://example.com/operation/123' )
513+
514+ assert result is None
515+ mock_print_error .assert_called_with ('Error polling operation: Connection error' )
516+
517+
518+ @pytest .mark .unit
519+ @patch ('apimrequests.requests.get' )
520+ @patch ('apimrequests.utils.print_error' )
521+ @patch ('apimrequests.time.time' )
522+ @patch ('apimrequests.time.sleep' )
523+ def test_poll_async_operation_timeout (mock_sleep , mock_time , mock_print_error , mock_get , apim ):
524+ """Test _poll_async_operation method with timeout."""
525+ # Mock time to simulate timeout
526+ mock_time .side_effect = [0 , 30 , 61 ] # start, first check, timeout check
527+
528+ mock_response = MagicMock ()
529+ mock_response .status_code = 202
530+ mock_get .return_value = mock_response
531+
532+ result = apim ._poll_async_operation ('http://example.com/operation/123' , timeout = 60 )
533+
534+ assert result is None
535+ mock_print_error .assert_called_with ('Async operation timeout reached after 60 seconds' )
536+
537+
538+ @pytest .mark .unit
539+ @patch ('apimrequests.requests.request' )
540+ @patch ('apimrequests.utils.print_message' )
541+ @patch ('apimrequests.utils.print_info' )
542+ def test_single_post_async_success_with_location (mock_print_info , mock_print_message , mock_request , apim ):
543+ """Test singlePostAsync method with successful async operation."""
544+ # Mock initial 202 response with Location header
545+ initial_response = MagicMock ()
546+ initial_response .status_code = 202
547+ initial_response .headers = {'Location' : 'http://example.com/operation/123' }
548+
549+ # Mock final 200 response
550+ final_response = MagicMock ()
551+ final_response .status_code = 200
552+ final_response .headers = {'Content-Type' : 'application/json' }
553+ final_response .json .return_value = {'result' : 'completed' }
554+ final_response .text = '{"result": "completed"}'
555+
556+ mock_request .return_value = initial_response
557+
558+ with patch .object (apim , '_poll_async_operation' , return_value = final_response ) as mock_poll :
559+ with patch .object (apim , '_print_response' ) as mock_print_response :
560+ result = apim .singlePostAsync ('/test' , data = {'test' : 'data' }, msg = 'Async test' )
561+
562+ mock_print_message .assert_called_once_with ('Async test' , blank_above = True )
563+ mock_poll .assert_called_once ()
564+ mock_print_response .assert_called_once_with (final_response )
565+ assert result == '{\n "result": "completed"\n }'
566+
567+
568+ @pytest .mark .unit
569+ @patch ('apimrequests.requests.request' )
570+ @patch ('apimrequests.utils.print_info' )
571+ @patch ('apimrequests.utils.print_error' )
572+ def test_single_post_async_no_location_header (mock_print_error , mock_print_info , mock_request , apim ):
573+ """Test singlePostAsync method with 202 response but no Location header."""
574+ mock_response = MagicMock ()
575+ mock_response .status_code = 202
576+ mock_response .headers = {} # No Location header
577+ mock_request .return_value = mock_response
578+
579+ with patch .object (apim , '_print_response' ) as mock_print_response :
580+ result = apim .singlePostAsync ('/test' )
581+
582+ mock_print_error .assert_called_once_with ('No Location header found in 202 response' )
583+ mock_print_response .assert_called_once_with (mock_response )
584+ assert result is None
585+
586+
587+ @pytest .mark .unit
588+ @patch ('apimrequests.requests.request' )
589+ @patch ('apimrequests.utils.print_info' )
590+ def test_single_post_async_non_async_response (mock_print_info , mock_request , apim ):
591+ """Test singlePostAsync method with non-async (immediate) response."""
592+ mock_response = MagicMock ()
593+ mock_response .status_code = 200
594+ mock_response .headers = {'Content-Type' : 'application/json' }
595+ mock_response .json .return_value = {'result' : 'immediate' }
596+ mock_response .text = '{"result": "immediate"}'
597+ mock_request .return_value = mock_response
598+
599+ with patch .object (apim , '_print_response' ) as mock_print_response :
600+ result = apim .singlePostAsync ('/test' )
601+
602+ mock_print_response .assert_called_once_with (mock_response )
603+ assert result == '{\n "result": "immediate"\n }'
604+
605+
606+ @pytest .mark .unit
607+ @patch ('apimrequests.requests.request' )
608+ @patch ('apimrequests.utils.print_error' )
609+ def test_single_post_async_request_exception (mock_print_error , mock_request , apim ):
610+ """Test singlePostAsync method with request exception."""
611+ mock_request .side_effect = requests .exceptions .RequestException ('Connection error' )
612+
613+ result = apim .singlePostAsync ('/test' )
614+
615+ assert result is None
616+ mock_print_error .assert_called_once_with ('Error making request: Connection error' )
617+
618+
619+ @pytest .mark .unit
620+ @patch ('apimrequests.requests.request' )
621+ @patch ('apimrequests.utils.print_error' )
622+ def test_single_post_async_failed_polling (mock_print_error , mock_request , apim ):
623+ """Test singlePostAsync method with failed async operation polling."""
624+ initial_response = MagicMock ()
625+ initial_response .status_code = 202
626+ initial_response .headers = {'Location' : 'http://example.com/operation/123' }
627+ mock_request .return_value = initial_response
628+
629+ with patch .object (apim , '_poll_async_operation' , return_value = None ) as mock_poll :
630+ result = apim .singlePostAsync ('/test' )
631+
632+ mock_poll .assert_called_once ()
633+ mock_print_error .assert_called_once_with ('Async operation failed or timed out' )
634+ assert result is None
635+
636+
637+ @pytest .mark .unit
638+ @patch ('apimrequests.requests.request' )
639+ @patch ('apimrequests.utils.print_info' )
640+ def test_single_post_async_path_without_leading_slash (mock_print_info , mock_request , apim ):
641+ """Test singlePostAsync method with path without leading slash."""
642+ mock_response = MagicMock ()
643+ mock_response .status_code = 200
644+ mock_response .headers = {'Content-Type' : 'application/json' }
645+ mock_response .json .return_value = {'result' : 'ok' }
646+ mock_response .text = '{"result": "ok"}'
647+ mock_request .return_value = mock_response
648+
649+ with patch .object (apim , '_print_response' ):
650+ apim .singlePostAsync ('test' )
651+
652+ # Should call with the corrected URL
653+ expected_url = default_url + '/test'
654+ mock_request .assert_called_once ()
655+ args , kwargs = mock_request .call_args
656+ assert args [1 ] == expected_url
657+
658+
659+ @pytest .mark .unit
660+ @patch ('apimrequests.requests.request' )
661+ @patch ('apimrequests.utils.print_info' )
662+ def test_single_post_async_non_json_response (mock_print_info , mock_request , apim ):
663+ """Test singlePostAsync method with non-JSON response."""
664+ mock_response = MagicMock ()
665+ mock_response .status_code = 200
666+ mock_response .headers = {'Content-Type' : 'text/plain' }
667+ mock_response .text = 'Plain text result'
668+ mock_request .return_value = mock_response
669+
670+ with patch .object (apim , '_print_response' ):
671+ result = apim .singlePostAsync ('/test' )
672+
673+ assert result == 'Plain text result'
0 commit comments