@@ -1161,3 +1161,363 @@ def fake_run(cmd, *args, **kwargs):
11611161 assert len (result ) == 2
11621162 assert (INFRASTRUCTURE .SIMPLE_APIM , 1 ) in result
11631163 assert (INFRASTRUCTURE .SIMPLE_APIM , 2 ) in result
1164+
1165+
1166+ # ------------------------------
1167+ # NotebookHelper._get_current_index TESTS
1168+ # ------------------------------
1169+
1170+ def test_notebookhelper_get_current_index_with_index (monkeypatch ):
1171+ """Test _get_current_index when resource group has an index."""
1172+ nb_helper = utils .NotebookHelper (
1173+ 'test-sample' , 'apim-infra-simple-apim-5' , 'eastus' ,
1174+ INFRASTRUCTURE .SIMPLE_APIM , [INFRASTRUCTURE .SIMPLE_APIM ]
1175+ )
1176+
1177+ result = nb_helper ._get_current_index ()
1178+ assert result == 5
1179+
1180+
1181+ def test_notebookhelper_get_current_index_without_index (monkeypatch ):
1182+ """Test _get_current_index when resource group has no index."""
1183+ nb_helper = utils .NotebookHelper (
1184+ 'test-sample' , 'apim-infra-simple-apim' , 'eastus' ,
1185+ INFRASTRUCTURE .SIMPLE_APIM , [INFRASTRUCTURE .SIMPLE_APIM ]
1186+ )
1187+
1188+ result = nb_helper ._get_current_index ()
1189+ assert result is None
1190+
1191+
1192+ def test_notebookhelper_get_current_index_invalid_format (monkeypatch ):
1193+ """Test _get_current_index with invalid resource group format."""
1194+ nb_helper = utils .NotebookHelper (
1195+ 'test-sample' , 'custom-rg-name' , 'eastus' ,
1196+ INFRASTRUCTURE .SIMPLE_APIM , [INFRASTRUCTURE .SIMPLE_APIM ]
1197+ )
1198+
1199+ result = nb_helper ._get_current_index ()
1200+ assert result is None
1201+
1202+
1203+ def test_notebookhelper_get_current_index_non_numeric_suffix (monkeypatch ):
1204+ """Test _get_current_index with non-numeric suffix."""
1205+ nb_helper = utils .NotebookHelper (
1206+ 'test-sample' , 'apim-infra-simple-apim-abc' , 'eastus' ,
1207+ INFRASTRUCTURE .SIMPLE_APIM , [INFRASTRUCTURE .SIMPLE_APIM ]
1208+ )
1209+
1210+ result = nb_helper ._get_current_index ()
1211+ assert result is None
1212+
1213+
1214+ # ------------------------------
1215+ # NotebookHelper._clean_up_jwt TESTS
1216+ # ------------------------------
1217+
1218+ def test_notebookhelper_clean_up_jwt_success (monkeypatch ):
1219+ """Test _clean_up_jwt with successful cleanup."""
1220+ monkeypatch .setattr (az , 'cleanup_old_jwt_signing_keys' , lambda * args : True )
1221+ monkeypatch .setattr ('console.print_warning' , lambda * args , ** kwargs : None )
1222+ monkeypatch .setattr (utils , 'generate_signing_key' , lambda : ('test-key' , 'test-key-b64' ))
1223+ monkeypatch .setattr (utils , 'print_val' , lambda * args , ** kwargs : None )
1224+ monkeypatch .setattr ('time.time' , lambda : 1234567890 )
1225+
1226+ nb_helper = utils .NotebookHelper (
1227+ 'test-sample' , 'test-rg' , 'eastus' ,
1228+ INFRASTRUCTURE .SIMPLE_APIM , [INFRASTRUCTURE .SIMPLE_APIM ], use_jwt = True
1229+ )
1230+
1231+ # Should not raise or print warning
1232+ nb_helper ._clean_up_jwt ('test-apim' )
1233+
1234+
1235+ def test_notebookhelper_clean_up_jwt_failure (monkeypatch , caplog ):
1236+ """Test _clean_up_jwt with failed cleanup."""
1237+ monkeypatch .setattr (az , 'cleanup_old_jwt_signing_keys' , lambda * args : False )
1238+ monkeypatch .setattr (utils , 'generate_signing_key' , lambda : ('test-key' , 'test-key-b64' ))
1239+ monkeypatch .setattr (utils , 'print_val' , lambda * args , ** kwargs : None )
1240+ monkeypatch .setattr ('time.time' , lambda : 1234567890 )
1241+
1242+ nb_helper = utils .NotebookHelper (
1243+ 'test-sample' , 'test-rg' , 'eastus' ,
1244+ INFRASTRUCTURE .SIMPLE_APIM , [INFRASTRUCTURE .SIMPLE_APIM ], use_jwt = True
1245+ )
1246+
1247+ with caplog .at_level (logging .WARNING ):
1248+ nb_helper ._clean_up_jwt ('test-apim' )
1249+
1250+ # Should log warning about cleanup failure
1251+ assert any ('JWT key cleanup failed' in record .message for record in caplog .records )
1252+
1253+
1254+ # ------------------------------
1255+ # get_endpoints TESTS
1256+ # ------------------------------
1257+
1258+ def test_get_endpoints_comprehensive (monkeypatch ):
1259+ """Test get_endpoints function."""
1260+ monkeypatch .setattr (az , 'get_frontdoor_url' , lambda x , y : 'https://test-afd.azurefd.net' )
1261+ monkeypatch .setattr (az , 'get_apim_url' , lambda x : 'https://test-apim.azure-api.net' )
1262+ monkeypatch .setattr (az , 'get_appgw_endpoint' , lambda x : ('appgw.contoso.com' , '1.2.3.4' ))
1263+ monkeypatch .setattr ('console.print_message' , lambda x , ** kw : None )
1264+
1265+ endpoints = utils .get_endpoints (INFRASTRUCTURE .AFD_APIM_PE , 'test-rg' )
1266+
1267+ assert endpoints .afd_endpoint_url == 'https://test-afd.azurefd.net'
1268+ assert endpoints .apim_endpoint_url == 'https://test-apim.azure-api.net'
1269+ assert endpoints .appgw_hostname == 'appgw.contoso.com'
1270+ assert endpoints .appgw_public_ip == '1.2.3.4'
1271+
1272+
1273+ def test_get_endpoints_no_frontdoor (monkeypatch ):
1274+ """Test get_endpoints when Front Door is not available."""
1275+ monkeypatch .setattr (az , 'get_frontdoor_url' , lambda x , y : None )
1276+ monkeypatch .setattr (az , 'get_apim_url' , lambda x : 'https://test-apim.azure-api.net' )
1277+ monkeypatch .setattr (az , 'get_appgw_endpoint' , lambda x : (None , None ))
1278+ monkeypatch .setattr ('console.print_message' , lambda x , ** kw : None )
1279+
1280+ endpoints = utils .get_endpoints (INFRASTRUCTURE .SIMPLE_APIM , 'test-rg' )
1281+
1282+ assert endpoints .afd_endpoint_url is None
1283+ assert endpoints .apim_endpoint_url == 'https://test-apim.azure-api.net'
1284+
1285+
1286+ # ------------------------------
1287+ # get_json TESTS
1288+ # ------------------------------
1289+
1290+ def test_get_json_valid_json_string ():
1291+ """Test get_json with valid JSON string."""
1292+ json_str = '{"key": "value", "number": 42}'
1293+ result = utils .get_json (json_str )
1294+ assert result == {'key' : 'value' , 'number' : 42 }
1295+
1296+
1297+ def test_get_json_python_dict_string ():
1298+ """Test get_json with Python dict string (single quotes)."""
1299+ dict_str = "{'key': 'value', 'number': 42}"
1300+ result = utils .get_json (dict_str )
1301+ assert result == {'key' : 'value' , 'number' : 42 }
1302+
1303+
1304+ def test_get_json_invalid_string (monkeypatch ):
1305+ """Test get_json with invalid string."""
1306+ monkeypatch .setattr ('console.print_error' , lambda * args , ** kwargs : None )
1307+
1308+ invalid_str = "not valid json or python literal"
1309+ result = utils .get_json (invalid_str )
1310+ # Should return the original string when parsing fails
1311+ assert result == invalid_str
1312+
1313+
1314+ def test_get_json_non_string ():
1315+ """Test get_json with non-string input."""
1316+ result = utils .get_json ({'already' : 'a dict' })
1317+ assert result == {'already' : 'a dict' }
1318+
1319+ result = utils .get_json ([1 , 2 , 3 ])
1320+ assert result == [1 , 2 , 3 ]
1321+
1322+
1323+ # ------------------------------
1324+ # does_infrastructure_exist TESTS
1325+ # ------------------------------
1326+
1327+ def test_does_infrastructure_exist_not_exist (monkeypatch ):
1328+ """Test does_infrastructure_exist when infrastructure doesn't exist."""
1329+ monkeypatch .setattr (az , 'does_resource_group_exist' , lambda x : False )
1330+ monkeypatch .setattr (az , 'get_infra_rg_name' , lambda x , y : 'test-rg' )
1331+ monkeypatch .setattr ('console.print_plain' , lambda * args , ** kwargs : None )
1332+
1333+ result = utils .does_infrastructure_exist (INFRASTRUCTURE .SIMPLE_APIM , 1 )
1334+ assert result is False
1335+
1336+
1337+ def test_does_infrastructure_exist_with_update_option_proceed (monkeypatch ):
1338+ """Test does_infrastructure_exist with update option - user proceeds."""
1339+ monkeypatch .setattr (az , 'does_resource_group_exist' , lambda x : True )
1340+ monkeypatch .setattr (az , 'get_infra_rg_name' , lambda x , y : 'test-rg' )
1341+ monkeypatch .setattr ('console.print_ok' , lambda * args , ** kwargs : None )
1342+ monkeypatch .setattr ('console.print_plain' , lambda * args , ** kwargs : None )
1343+ monkeypatch .setattr ('console.print_info' , lambda * args , ** kwargs : None )
1344+ monkeypatch .setattr ('builtins.input' , lambda prompt : '1' )
1345+
1346+ result = utils .does_infrastructure_exist (INFRASTRUCTURE .SIMPLE_APIM , 1 , allow_update_option = True )
1347+ assert result is False # Allow deployment to proceed
1348+
1349+
1350+ def test_does_infrastructure_exist_with_update_option_cancel (monkeypatch ):
1351+ """Test does_infrastructure_exist with update option - user cancels."""
1352+ monkeypatch .setattr (az , 'does_resource_group_exist' , lambda x : True )
1353+ monkeypatch .setattr (az , 'get_infra_rg_name' , lambda x , y : 'test-rg' )
1354+ monkeypatch .setattr ('console.print_ok' , lambda * args , ** kwargs : None )
1355+ monkeypatch .setattr ('console.print_plain' , lambda * args , ** kwargs : None )
1356+ monkeypatch .setattr ('console.print_info' , lambda * args , ** kwargs : None )
1357+ monkeypatch .setattr ('builtins.input' , lambda prompt : '2' )
1358+
1359+ result = utils .does_infrastructure_exist (INFRASTRUCTURE .SIMPLE_APIM , 1 , allow_update_option = True )
1360+ assert result is True # Block deployment
1361+
1362+
1363+ def test_does_infrastructure_exist_without_update_option (monkeypatch ):
1364+ """Test does_infrastructure_exist without update option."""
1365+ monkeypatch .setattr (az , 'does_resource_group_exist' , lambda x : True )
1366+ monkeypatch .setattr (az , 'get_infra_rg_name' , lambda x , y : 'test-rg' )
1367+ monkeypatch .setattr ('console.print_ok' , lambda * args , ** kwargs : None )
1368+ monkeypatch .setattr ('console.print_plain' , lambda * args , ** kwargs : None )
1369+
1370+ result = utils .does_infrastructure_exist (INFRASTRUCTURE .SIMPLE_APIM , 1 , allow_update_option = False )
1371+ assert result is True # Infrastructure exists, block deployment
1372+
1373+
1374+ # ------------------------------
1375+ # read_and_modify_policy_xml TESTS
1376+ # ------------------------------
1377+
1378+ def test_read_and_modify_policy_xml_with_replacements (monkeypatch ):
1379+ """Test read_and_modify_policy_xml with placeholders."""
1380+ xml_content = '<policy><key>{jwt_key}</key><value>{api_value}</value></policy>'
1381+ m = mock_open (read_data = xml_content )
1382+
1383+ real_open = builtins .open
1384+
1385+ def open_selector (file , * args , ** kwargs ):
1386+ mode = kwargs .get ('mode' , args [0 ] if args else 'r' )
1387+ file_str = str (file )
1388+ if 'test-policy.xml' in file_str and 'b' not in mode :
1389+ return m (file , * args , ** kwargs )
1390+ return real_open (file , * args , ** kwargs )
1391+
1392+ monkeypatch .setattr (builtins , 'open' , open_selector )
1393+
1394+ replacements = {
1395+ 'jwt_key' : 'JwtSigningKey123' ,
1396+ 'api_value' : 'test-api'
1397+ }
1398+
1399+ result = utils .read_and_modify_policy_xml ('/path/to/test-policy.xml' , replacements )
1400+ expected = '<policy><key>JwtSigningKey123</key><value>test-api</value></policy>'
1401+ assert result == expected
1402+
1403+
1404+ def test_read_and_modify_policy_xml_placeholder_not_found (monkeypatch , caplog ):
1405+ """Test read_and_modify_policy_xml when placeholder doesn't exist in XML."""
1406+ xml_content = '<policy><key>static</key></policy>'
1407+ m = mock_open (read_data = xml_content )
1408+
1409+ real_open = builtins .open
1410+
1411+ def open_selector (file , * args , ** kwargs ):
1412+ mode = kwargs .get ('mode' , args [0 ] if args else 'r' )
1413+ file_str = str (file )
1414+ if 'test-policy.xml' in file_str and 'b' not in mode :
1415+ return m (file , * args , ** kwargs )
1416+ return real_open (file , * args , ** kwargs )
1417+
1418+ monkeypatch .setattr (builtins , 'open' , open_selector )
1419+
1420+ replacements = {'missing_key' : 'value' }
1421+
1422+ with caplog .at_level (logging .WARNING ):
1423+ _ = utils .read_and_modify_policy_xml ('/path/to/test-policy.xml' , replacements )
1424+
1425+ # Should log warning about missing placeholder
1426+ assert any ('missing_key' in record .message for record in caplog .records )
1427+
1428+
1429+ def test_read_and_modify_policy_xml_none_replacements (monkeypatch ):
1430+ """Test read_and_modify_policy_xml with None replacements."""
1431+ xml_content = '<policy><key>{jwt_key}</key></policy>'
1432+ m = mock_open (read_data = xml_content )
1433+
1434+ real_open = builtins .open
1435+
1436+ def open_selector (file , * args , ** kwargs ):
1437+ mode = kwargs .get ('mode' , args [0 ] if args else 'r' )
1438+ file_str = str (file )
1439+ if 'test-policy.xml' in file_str and 'b' not in mode :
1440+ return m (file , * args , ** kwargs )
1441+ return real_open (file , * args , ** kwargs )
1442+
1443+ monkeypatch .setattr (builtins , 'open' , open_selector )
1444+
1445+ result = utils .read_and_modify_policy_xml ('/path/to/test-policy.xml' , None )
1446+ # Should return unmodified XML
1447+ assert result == xml_content
1448+
1449+
1450+ # ------------------------------
1451+ # determine_shared_policy_path TESTS
1452+ # ------------------------------
1453+
1454+ def test_determine_shared_policy_path (monkeypatch ):
1455+ """Test determine_shared_policy_path function."""
1456+ monkeypatch .setattr (utils , 'find_project_root' , lambda : 'c:\\ mock\\ project' )
1457+
1458+ result = utils .determine_shared_policy_path ('test-policy.xml' )
1459+ expected = 'c:\\ mock\\ project\\ shared\\ apim-policies\\ fragments\\ test-policy.xml'
1460+ assert result == expected
1461+
1462+
1463+ # ------------------------------
1464+ # InfrastructureNotebookHelper TESTS
1465+ # ------------------------------
1466+
1467+ def test_infrastructure_notebook_helper_bypass_check (monkeypatch ):
1468+ """Test InfrastructureNotebookHelper with bypass_infrastructure_check=True."""
1469+ helper = utils .InfrastructureNotebookHelper ('eastus' , INFRASTRUCTURE .SIMPLE_APIM , 1 , APIM_SKU .BASICV2 )
1470+
1471+ # Mock subprocess execution to succeed
1472+ class MockProcess :
1473+ def __init__ (self , * args , ** kwargs ):
1474+ self .returncode = 0
1475+ self .stdout = iter (['Mock deployment output\n ' ])
1476+
1477+ def wait (self ):
1478+ pass
1479+
1480+ def __enter__ (self ):
1481+ return self
1482+
1483+ def __exit__ (self , * args ):
1484+ pass
1485+
1486+ monkeypatch .setattr ('subprocess.Popen' , MockProcess )
1487+ monkeypatch .setattr (utils , 'find_project_root' , lambda : 'c:\\ mock\\ root' )
1488+ monkeypatch .setattr ('builtins.print' , lambda * args , ** kwargs : None )
1489+
1490+ # Test with bypass_infrastructure_check=True
1491+ result = helper .create_infrastructure (bypass_infrastructure_check = True )
1492+ assert result is True
1493+
1494+
1495+ def test_infrastructure_notebook_helper_allow_update_false (monkeypatch ):
1496+ """Test InfrastructureNotebookHelper with allow_update=False."""
1497+ helper = utils .InfrastructureNotebookHelper ('eastus' , INFRASTRUCTURE .SIMPLE_APIM , 1 , APIM_SKU .BASICV2 )
1498+
1499+ # Mock RG exists but allow_update=False
1500+ monkeypatch .setattr (az , 'does_resource_group_exist' , lambda x : True )
1501+
1502+ # Mock subprocess execution to succeed
1503+ class MockProcess :
1504+ def __init__ (self , * args , ** kwargs ):
1505+ self .returncode = 0
1506+ self .stdout = iter (['Mock deployment output\n ' ])
1507+
1508+ def wait (self ):
1509+ pass
1510+
1511+ def __enter__ (self ):
1512+ return self
1513+
1514+ def __exit__ (self , * args ):
1515+ pass
1516+
1517+ monkeypatch .setattr ('subprocess.Popen' , MockProcess )
1518+ monkeypatch .setattr (utils , 'find_project_root' , lambda : 'c:\\ mock\\ root' )
1519+ monkeypatch .setattr ('builtins.print' , lambda * args , ** kwargs : None )
1520+
1521+ # With allow_update=False, should still create when infrastructure doesn't exist
1522+ result = helper .create_infrastructure (allow_update = False , bypass_infrastructure_check = True )
1523+ assert result is True
0 commit comments