Skip to content

Commit b025ce2

Browse files
More test coverage
1 parent 700abbd commit b025ce2

File tree

5 files changed

+156
-6
lines changed

5 files changed

+156
-6
lines changed

.github/python.instructions.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,17 @@ applyTo: '**/*.py'
4848
2. Specific type/constant imports (e.g., `from apimtypes import INFRASTRUCTURE`)
4949
3. Specific function imports (e.g., `from console import print_error`)
5050

51-
## Linting (pylint)
51+
## Code Quality Checklist
5252

53-
- Respect the repository pylint configuration at `tests/python/.pylintrc`.
54-
- When changing Python code, run pylint and ensure changes do not worsen the pylint rating unexpectedly.
55-
- Prefer fixing root causes (e.g., import structure, error handling) over suppressions.
53+
Before completing any Python code changes, verify:
54+
55+
- [ ] All pylint warnings and errors are resolved (`pylint --rcfile=tests/python/.pylintrc <file>`)
56+
- [ ] Code follows PEP 8 and the style guidelines in this file
57+
- [ ] Import statements for modules within this repo are placed last in the imports and are grouped with the `# APIM Samples imports` header
58+
- [ ] Type hints are present where appropriate
59+
- [ ] No unnecessary comments; docstrings are present for functions and classes
60+
- [ ] Edge cases and error handling are implemented
61+
- [ ] Prefer fixing root causes (e.g., import structure, error handling) over suppressions.
5662

5763
## Testing
5864

coverage.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

tests/python/run_tests.ps1

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,15 @@ $env:PYTHONUNBUFFERED = "1"
1919
Push-Location $RepoRoot
2020
try {
2121
$env:COVERAGE_FILE = (Join-Path $RepoRoot "tests/python/.coverage")
22-
pytest -v --color=yes --cov=shared/python --cov-config=tests/python/.coveragerc --cov-report=html:tests/python/htmlcov tests/python/
22+
pytest -v --color=yes --cov=shared/python --cov-config=tests/python/.coveragerc --cov-report=html:tests/python/htmlcov --cov-report=json tests/python/
23+
24+
# Generate coverage.json for VS Code visualization
25+
Write-Host "`nGenerating coverage.json for VS Code..." -ForegroundColor Cyan
26+
coverage json
27+
28+
# Display coverage summary
29+
Write-Host "`nCoverage Summary:" -ForegroundColor Green
30+
coverage report --skip-covered
2331
}
2432
finally {
2533
Pop-Location

tests/python/run_tests.sh

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,14 @@ REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
1717
cd "${REPO_ROOT}"
1818

1919
export COVERAGE_FILE="tests/python/.coverage"
20-
pytest -v --color=yes --cov=shared/python --cov-config=tests/python/.coveragerc --cov-report=html:tests/python/htmlcov tests/python/
20+
pytest -v --color=yes --cov=shared/python --cov-config=tests/python/.coveragerc --cov-report=html:tests/python/htmlcov --cov-report=json tests/python/
21+
22+
# Generate coverage.json for VS Code visualization
23+
echo ""
24+
echo "Generating coverage.json for VS Code..."
25+
coverage json
26+
27+
# Display coverage summary
28+
echo ""
29+
echo "Coverage Summary:"
30+
coverage report --skip-covered

tests/python/test_azure_resources.py

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1101,3 +1101,128 @@ def test_extract_az_cli_error_message_skips_warnings():
11011101
output = 'WARNING: This is deprecated\nERROR: Real error here'
11021102
result = az._extract_az_cli_error_message(output)
11031103
assert result == 'Real error here'
1104+
1105+
1106+
def test_extract_az_cli_error_message_with_ansi_codes():
1107+
"""Test _extract_az_cli_error_message strips ANSI codes."""
1108+
output = '\x1b[31mERROR: Resource failed\x1b[0m'
1109+
result = az._extract_az_cli_error_message(output)
1110+
assert result == 'Resource failed'
1111+
1112+
1113+
def test_extract_az_cli_error_message_finds_first_non_empty_line():
1114+
"""Test _extract_az_cli_error_message returns first meaningful line."""
1115+
output = '\n\n\nSome error occurred\nMore details'
1116+
result = az._extract_az_cli_error_message(output)
1117+
assert result == 'Some error occurred'
1118+
1119+
1120+
def test_looks_like_json_with_valid_json():
1121+
"""Test _looks_like_json identifies JSON strings."""
1122+
assert az._looks_like_json('{"key": "value"}') is True
1123+
assert az._looks_like_json('[1, 2, 3]') is True
1124+
1125+
1126+
def test_looks_like_json_with_non_json():
1127+
"""Test _looks_like_json rejects non-JSON strings."""
1128+
assert az._looks_like_json('plain text') is False
1129+
assert az._looks_like_json('') is False
1130+
1131+
1132+
def test_strip_ansi_removes_codes():
1133+
"""Test _strip_ansi removes ANSI escape codes."""
1134+
text = '\x1b[31mRed text\x1b[0m normal'
1135+
result = az._strip_ansi(text)
1136+
assert '\x1b' not in result
1137+
assert 'Red text' in result
1138+
assert 'normal' in result
1139+
1140+
1141+
def test_is_az_command_recognizes_az_commands():
1142+
"""Test _is_az_command identifies az CLI commands."""
1143+
assert az._is_az_command('az group list') is True
1144+
assert az._is_az_command(' az account show ') is True
1145+
assert az._is_az_command('az') is True
1146+
1147+
1148+
def test_is_az_command_rejects_non_az_commands():
1149+
"""Test _is_az_command rejects non-az commands."""
1150+
assert az._is_az_command('echo hello') is False
1151+
assert az._is_az_command('python script.py') is False
1152+
assert az._is_az_command('azurecli') is False
1153+
1154+
1155+
def test_run_with_exception_in_subprocess():
1156+
"""Test run() handles subprocess exceptions gracefully."""
1157+
with patch('azure_resources.subprocess.run') as mock_subprocess:
1158+
mock_subprocess.side_effect = Exception('Subprocess failed')
1159+
1160+
result = az.run('az group list')
1161+
1162+
assert result.success is False
1163+
assert 'Subprocess failed' in result.text
1164+
1165+
1166+
def test_run_with_stderr_only():
1167+
"""Test run() handles commands that only output to stderr."""
1168+
with patch('azure_resources.subprocess.run') as mock_subprocess:
1169+
mock_process = Mock()
1170+
mock_process.returncode = 0
1171+
mock_process.stdout = ''
1172+
mock_process.stderr = 'Some warning message'
1173+
mock_subprocess.return_value = mock_process
1174+
1175+
result = az.run('az group list')
1176+
1177+
assert result.success is True
1178+
1179+
1180+
def test_run_with_az_debug_flag_already_present():
1181+
"""Test run() doesn't duplicate --debug flag."""
1182+
with patch('azure_resources.is_debug_enabled', return_value=True):
1183+
with patch('azure_resources.subprocess.run') as mock_subprocess:
1184+
mock_process = Mock()
1185+
mock_process.returncode = 0
1186+
mock_process.stdout = '[]'
1187+
mock_process.stderr = ''
1188+
mock_subprocess.return_value = mock_process
1189+
1190+
az.run('az group list --debug')
1191+
1192+
# Check that --debug appears only once in the command
1193+
called_command = mock_subprocess.call_args[0][0]
1194+
assert called_command.count('--debug') == 1
1195+
1196+
1197+
def test_run_with_json_output_success():
1198+
"""Test run() with successful JSON output."""
1199+
with patch('azure_resources.subprocess.run') as mock_subprocess:
1200+
mock_process = Mock()
1201+
mock_process.returncode = 0
1202+
mock_process.stdout = '{"result": "success"}'
1203+
mock_process.stderr = ''
1204+
mock_subprocess.return_value = mock_process
1205+
1206+
result = az.run('az group show --name test-rg')
1207+
1208+
assert result.success is True
1209+
assert '{"result": "success"}' in result.text
1210+
1211+
1212+
def test_run_with_complex_shell_expression():
1213+
"""Test run() handles complex shell expressions with operators."""
1214+
with patch('azure_resources.is_debug_enabled', return_value=True):
1215+
with patch('azure_resources.subprocess.run') as mock_subprocess:
1216+
mock_process = Mock()
1217+
mock_process.returncode = 0
1218+
mock_process.stdout = 'output'
1219+
mock_process.stderr = ''
1220+
mock_subprocess.return_value = mock_process
1221+
1222+
az.run('az group list || echo "failed"')
1223+
1224+
# --debug should be inserted before the ||
1225+
called_command = mock_subprocess.call_args[0][0]
1226+
debug_pos = called_command.find('--debug')
1227+
pipe_pos = called_command.find('||')
1228+
assert debug_pos < pipe_pos

0 commit comments

Comments
 (0)