Skip to content

Commit 6200b58

Browse files
Updated Python Tests (#113)
1 parent 36716c2 commit 6200b58

17 files changed

+5208
-631
lines changed

shared/python/apimrequests.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -218,11 +218,13 @@ def _multiRequest(self, method: HTTP_VERB, path: str, runs: int, headers: list[a
218218
'response_time': response_time
219219
})
220220

221-
if sleepMs is not None:
222-
if sleepMs > 0:
223-
time.sleep(sleepMs / 1000)
224-
else:
225-
time.sleep(SLEEP_TIME_BETWEEN_REQUESTS_MS / 1000) # default sleep time
221+
# Sleep only between requests (not after the final run)
222+
if i < runs - 1:
223+
if sleepMs is not None:
224+
if sleepMs > 0:
225+
time.sleep(sleepMs / 1000)
226+
else:
227+
time.sleep(SLEEP_TIME_BETWEEN_REQUESTS_MS / 1000) # default sleep time
226228
finally:
227229
session.close()
228230

shared/python/infrastructures.py

Lines changed: 213 additions & 387 deletions
Large diffs are not rendered by default.

shared/python/logging_config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
try:
2222
from dotenv import load_dotenv # type: ignore[import-not-found]
23-
except Exception: # pragma: no cover
23+
except Exception:
2424
load_dotenv = None
2525

2626

shared/python/show_soft_deleted_resources.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@
99
from datetime import datetime
1010
from pathlib import Path
1111

12+
# APIM Samples imports
13+
import azure_resources as az
14+
1215
# Configure UTF-8 encoding for console output
1316
if sys.stdout.encoding != 'utf-8':
1417
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
1518

16-
# APIM Samples imports
17-
import azure_resources as az
18-
1919

2020
def _get_suggested_purge_command() -> str:
2121
"""Return a purge command that works from the current working directory."""

shared/python/utils.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import azure_resources as az
2121
from apimtypes import APIM_SKU, HTTP_VERB, INFRASTRUCTURE, Endpoints, Output, get_project_root
2222
from console import print_error, print_info, print_message, print_ok, print_plain, print_warning, print_val
23-
from logging_config import get_configured_level_name
23+
import logging_config
2424

2525
# Configure warning filter to suppress IPython exit warnings
2626
warnings.filterwarnings(
@@ -48,7 +48,7 @@ def get_deployment_failure_message(deployment_name: str) -> str:
4848
base_message = f"Deployment '{deployment_name}' failed. View deployment details in Azure Portal."
4949

5050
# Only suggest enabling DEBUG logging if it's not already enabled
51-
current_level = get_configured_level_name()
51+
current_level = logging_config.get_configured_level_name()
5252
if current_level != 'DEBUG':
5353
return f"{base_message} Enable DEBUG logging in workspace root .env file, then rerun to see details."
5454

@@ -644,9 +644,8 @@ def find_project_root() -> str:
644644

645645
while current_dir != os.path.dirname(current_dir): # Stop at filesystem root
646646
if any(os.path.exists(os.path.join(current_dir, marker)) for marker in marker_files):
647-
# Additional check: verify this looks like our project by checking for samples directory
648-
if os.path.exists(os.path.join(current_dir, 'samples')):
649-
return current_dir
647+
# Return as soon as marker files are found; do not require 'samples' directory
648+
return current_dir
650649
current_dir = os.path.dirname(current_dir)
651650

652651
# If we can't find the project root, raise an error

start.ps1

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ function Invoke-Cmd {
5555
Write-Host ""
5656
Write-Host "Command failed: $_" -ForegroundColor Red
5757
Write-Host ""
58-
Invoke-Pause-Menu
5958
return $false
6059
}
6160
finally {
@@ -66,7 +65,6 @@ function Invoke-Cmd {
6665
Write-Host ""
6766
Write-Host "Command exited with code $exitCode" -ForegroundColor Yellow
6867
Write-Host ""
69-
Invoke-Pause-Menu
7068
return $false
7169
}
7270

@@ -101,11 +99,6 @@ except Exception as exc: # pylint: disable=broad-except
10199
}
102100
}
103101

104-
function Invoke-Pause-Menu {
105-
Write-Host ""
106-
Write-Host "=========================="
107-
Read-Host "Press ENTER to return to the menu" | Out-Null
108-
}
109102

110103
while ($true) {
111104
Write-Host ""
@@ -130,26 +123,25 @@ while ($true) {
130123

131124
switch ($choice) {
132125
'1' {
133-
if (Invoke-Cmd (Get-Python) "$RepoRoot/setup/local_setup.py" "--complete-setup") { Invoke-Pause-Menu }
126+
Invoke-Cmd (Get-Python) "$RepoRoot/setup/local_setup.py" "--complete-setup"
134127
}
135128
'2' {
136-
if (Invoke-Cmd (Get-Python) "$RepoRoot/setup/verify_local_setup.py") { Invoke-Pause-Menu }
129+
Invoke-Cmd (Get-Python) "$RepoRoot/setup/verify_local_setup.py"
137130
}
138131
'3' {
139132
Show-AccountInfo
140-
Invoke-Pause-Menu
141133
}
142134
'4' {
143-
if (Invoke-Cmd (Get-Python) "$RepoRoot/shared/python/show_soft_deleted_resources.py") { Invoke-Pause-Menu }
135+
Invoke-Cmd (Get-Python) "$RepoRoot/shared/python/show_soft_deleted_resources.py"
144136
}
145137
'5' {
146-
if (Invoke-Cmd "$RepoRoot/tests/python/run_pylint.ps1") { Invoke-Pause-Menu }
138+
Invoke-Cmd "$RepoRoot/tests/python/run_pylint.ps1"
147139
}
148140
'6' {
149-
if (Invoke-Cmd "$RepoRoot/tests/python/run_tests.ps1") { Invoke-Pause-Menu }
141+
Invoke-Cmd "$RepoRoot/tests/python/run_tests.ps1"
150142
}
151143
'7' {
152-
if (Invoke-Cmd "$RepoRoot/tests/python/check_python.ps1") { Invoke-Pause-Menu }
144+
Invoke-Cmd "$RepoRoot/tests/python/check_python.ps1"
153145
}
154146
'0' {
155147
Write-Host ""

start.sh

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ run_cmd() {
3737
echo ""
3838
echo "Command exited with code $status"
3939
echo ""
40-
pause_prompt
4140
return $status
4241
fi
4342
}
@@ -65,10 +64,6 @@ PY
6564
)
6665
}
6766

68-
pause_prompt() {
69-
echo
70-
read -rp "Press ENTER to return to the menu..." _
71-
}
7267

7368
while true; do
7469
echo ""
@@ -93,30 +88,24 @@ while true; do
9388
case "$choice" in
9489
1)
9590
run_cmd "$(find_python)" "${REPO_ROOT}/setup/local_setup.py" --complete-setup
96-
pause_prompt
9791
;;
9892
2)
9993
run_cmd "$(find_python)" "${REPO_ROOT}/setup/verify_local_setup.py"
100-
pause_prompt
10194
;;
10295
3)
103-
if show_account; then pause_prompt; fi
96+
show_account
10497
;;
10598
4)
10699
run_cmd "$(find_python)" "${REPO_ROOT}/shared/python/show_soft_deleted_resources.py"
107-
pause_prompt
108100
;;
109101
5)
110102
run_cmd "${REPO_ROOT}/tests/python/run_pylint.sh"
111-
pause_prompt
112103
;;
113104
6)
114105
run_cmd "${REPO_ROOT}/tests/python/run_tests.sh"
115-
pause_prompt
116106
;;
117107
7)
118108
run_cmd "${REPO_ROOT}/tests/python/check_python.sh"
119-
pause_prompt
120109
;;
121110
0)
122111
echo ""

tests/python/test_apimrequests.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,16 @@ def test_headers_property_is_dict_reference():
209209
h['X-Ref'] = 'ref'
210210
assert apim.headers['X-Ref'] == 'ref'
211211

212+
213+
def test_subscription_key_setter_updates_and_clears_header():
214+
apim = ApimRequests(DEFAULT_URL, DEFAULT_KEY)
215+
216+
apim.subscriptionKey = 'new-key'
217+
assert apim.headers[SUBSCRIPTION_KEY_PARAMETER_NAME] == 'new-key'
218+
219+
apim.subscriptionKey = None
220+
assert SUBSCRIPTION_KEY_PARAMETER_NAME not in apim.headers
221+
212222
# ------------------------------
213223
# ADDITIONAL COVERAGE TESTS FOR APIMREQUESTS
214224
# ------------------------------
@@ -432,6 +442,49 @@ def test_multi_request_non_json_response(mock_print_info, mock_session_class, ap
432442
assert result[0]['response'] == 'Plain text response'
433443

434444

445+
@pytest.mark.unit
446+
@patch('apimrequests.time.sleep')
447+
@patch('apimrequests.requests.Session')
448+
def test_multi_request_sleep_zero(mock_session_class, mock_sleep, apim):
449+
"""Test _multiRequest respects sleepMs=0 without sleeping."""
450+
mock_session = MagicMock()
451+
mock_session_class.return_value = mock_session
452+
453+
mock_response = MagicMock()
454+
mock_response.status_code = 200
455+
mock_response.headers = {'Content-Type': 'application/json'}
456+
mock_response.json.return_value = {'ok': True}
457+
mock_response.text = '{"ok": true}'
458+
mock_session.request.return_value = mock_response
459+
460+
with patch.object(apim, '_print_response_code'):
461+
result = apim._multiRequest(HTTP_VERB.GET, '/sleep', 1, sleepMs=0)
462+
463+
assert result[0]['status_code'] == 200
464+
mock_sleep.assert_not_called()
465+
466+
467+
@pytest.mark.unit
468+
@patch('apimrequests.time.sleep')
469+
@patch('apimrequests.requests.Session')
470+
def test_multi_request_sleep_positive(mock_session_class, mock_sleep, apim):
471+
"""Test _multiRequest sleeps when sleepMs is positive."""
472+
mock_session = MagicMock()
473+
mock_session_class.return_value = mock_session
474+
475+
mock_response = MagicMock()
476+
mock_response.status_code = 200
477+
mock_response.headers = {'Content-Type': 'application/json'}
478+
mock_response.json.return_value = {'ok': True}
479+
mock_response.text = '{"ok": true}'
480+
mock_session.request.return_value = mock_response
481+
482+
with patch.object(apim, '_print_response_code'):
483+
apim._multiRequest(HTTP_VERB.GET, '/sleep', 2, sleepMs = 150)
484+
485+
mock_sleep.assert_called_once_with(0.15)
486+
487+
435488
@pytest.mark.unit
436489
@patch('apimrequests.print_val')
437490
def test_print_response_non_200_status(mock_print_val, apim):
@@ -449,6 +502,60 @@ def test_print_response_non_200_status(mock_print_val, apim):
449502
mock_print_val.assert_any_call('Response body', '{"error": "not found"}', True)
450503

451504

505+
@pytest.mark.unit
506+
@patch('apimrequests.print_val')
507+
def test_print_response_200_invalid_json(mock_print_val, apim):
508+
"""Test _print_response handles invalid JSON body for 200 responses."""
509+
mock_response = MagicMock()
510+
mock_response.status_code = 200
511+
mock_response.reason = 'OK'
512+
mock_response.headers = {'Content-Type': 'application/json'}
513+
mock_response.text = 'not valid json'
514+
515+
with patch.object(apim, '_print_response_code'):
516+
apim._print_response(mock_response)
517+
518+
mock_print_val.assert_any_call('Response body', 'not valid json', True)
519+
520+
521+
@pytest.mark.unit
522+
@patch('apimrequests.print_val')
523+
def test_print_response_200_valid_json(mock_print_val, apim):
524+
"""Test _print_response prints formatted JSON when parse succeeds."""
525+
mock_response = MagicMock()
526+
mock_response.status_code = 200
527+
mock_response.reason = 'OK'
528+
mock_response.headers = {'Content-Type': 'application/json'}
529+
mock_response.text = '{"alpha": 1}'
530+
531+
with patch.object(apim, '_print_response_code'):
532+
apim._print_response(mock_response)
533+
534+
mock_print_val.assert_any_call('Response body', '{\n "alpha": 1\n}', True)
535+
536+
537+
@pytest.mark.unit
538+
@patch('apimrequests.print_val')
539+
def test_print_response_code_success_and_error(mock_print_val, apim):
540+
"""Test _print_response_code color formatting for success and error codes."""
541+
class DummyResponse:
542+
status_code = 200
543+
reason = 'OK'
544+
545+
apim._print_response_code(DummyResponse())
546+
547+
class ErrorResponse:
548+
status_code = 500
549+
reason = 'Server Error'
550+
551+
apim._print_response_code(ErrorResponse())
552+
553+
messages = [record.args[1] for record in mock_print_val.call_args_list]
554+
555+
assert any('200 - OK' in msg for msg in messages)
556+
assert any('500 - Server Error' in msg for msg in messages)
557+
558+
452559
@pytest.mark.unit
453560
@patch('apimrequests.requests.get')
454561
@patch('apimrequests.print_info')

0 commit comments

Comments
 (0)