Skip to content

Commit 1bce992

Browse files
Add tests
1 parent 36b31fe commit 1bce992

File tree

3 files changed

+1114
-2
lines changed

3 files changed

+1114
-2
lines changed

tests/python/test_apimrequests.py

Lines changed: 362 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import pytest
22
import requests
3+
import time
34
from unittest.mock import patch, MagicMock
45
from 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
89
default_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

Comments
 (0)