@@ -1216,3 +1216,188 @@ def test_deploy_with_token_fails(
12161216 "The specified token is not valid. Make sure to use a valid token."
12171217 in result .output
12181218 )
1219+
1220+
1221+ @pytest .mark .respx (base_url = settings .base_api_url )
1222+ def test_deploy_with_app_id_arg (
1223+ logged_in_cli : None , tmp_path : Path , respx_mock : respx .MockRouter
1224+ ) -> None :
1225+ app_data = _get_random_app ()
1226+ app_id = app_data ["id" ]
1227+ deployment_data = _get_random_deployment (app_id = app_id )
1228+
1229+ respx_mock .get (f"/apps/{ app_id } " ).mock (return_value = Response (200 , json = app_data ))
1230+
1231+ respx_mock .post (f"/apps/{ app_id } /deployments/" ).mock (
1232+ return_value = Response (201 , json = deployment_data )
1233+ )
1234+
1235+ respx_mock .post (f"/deployments/{ deployment_data ['id' ]} /upload" ).mock (
1236+ return_value = Response (
1237+ 200 ,
1238+ json = {"url" : "http://test.com" , "fields" : {"key" : "value" }},
1239+ )
1240+ )
1241+
1242+ respx_mock .post ("http://test.com" , data = {"key" : "value" }).mock (
1243+ return_value = Response (200 )
1244+ )
1245+
1246+ respx_mock .post (f"/deployments/{ deployment_data ['id' ]} /upload-complete" ).mock (
1247+ return_value = Response (200 )
1248+ )
1249+
1250+ respx_mock .get (f"/deployments/{ deployment_data ['id' ]} /build-logs" ).mock (
1251+ return_value = Response (
1252+ 200 ,
1253+ content = build_logs_response (
1254+ {"type" : "message" , "message" : "Building..." , "id" : "1" },
1255+ {"type" : "complete" },
1256+ ),
1257+ )
1258+ )
1259+
1260+ with changing_dir (tmp_path ):
1261+ result = runner .invoke (app , ["deploy" , "--app-id" , app_id ])
1262+
1263+ assert result .exit_code == 0
1264+ assert f"Deploying to app { app_id } " in result .output
1265+
1266+
1267+ @pytest .mark .respx (base_url = settings .base_api_url )
1268+ def test_deploy_with_app_id_from_env_var (
1269+ logged_in_cli : None , tmp_path : Path , respx_mock : respx .MockRouter
1270+ ) -> None :
1271+ app_data = _get_random_app ()
1272+ app_id = app_data ["id" ]
1273+ deployment_data = _get_random_deployment (app_id = app_id )
1274+
1275+ respx_mock .get (f"/apps/{ app_id } " ).mock (return_value = Response (200 , json = app_data ))
1276+
1277+ respx_mock .post (f"/apps/{ app_id } /deployments/" ).mock (
1278+ return_value = Response (201 , json = deployment_data )
1279+ )
1280+
1281+ respx_mock .post (f"/deployments/{ deployment_data ['id' ]} /upload" ).mock (
1282+ return_value = Response (
1283+ 200 ,
1284+ json = {"url" : "http://test.com" , "fields" : {"key" : "value" }},
1285+ )
1286+ )
1287+
1288+ respx_mock .post ("http://test.com" , data = {"key" : "value" }).mock (
1289+ return_value = Response (200 )
1290+ )
1291+
1292+ respx_mock .post (f"/deployments/{ deployment_data ['id' ]} /upload-complete" ).mock (
1293+ return_value = Response (200 )
1294+ )
1295+
1296+ respx_mock .get (f"/deployments/{ deployment_data ['id' ]} /build-logs" ).mock (
1297+ return_value = Response (
1298+ 200 ,
1299+ content = build_logs_response (
1300+ {"type" : "message" , "message" : "Building..." , "id" : "1" },
1301+ {"type" : "complete" },
1302+ ),
1303+ )
1304+ )
1305+
1306+ with changing_dir (tmp_path ):
1307+ result = runner .invoke (app , ["deploy" ], env = {"FASTAPI_CLOUD_APP_ID" : app_id })
1308+
1309+ assert result .exit_code == 0
1310+ assert f"Deploying to app { app_id } " in result .output
1311+
1312+
1313+ @pytest .mark .respx (base_url = settings .base_api_url )
1314+ def test_deploy_with_app_id_matching_local_config (
1315+ logged_in_cli : None , tmp_path : Path , respx_mock : respx .MockRouter
1316+ ) -> None :
1317+ app_data = _get_random_app ()
1318+ app_id = app_data ["id" ]
1319+ team_id = "some-team-id"
1320+ deployment_data = _get_random_deployment (app_id = app_id )
1321+
1322+ config_path = tmp_path / ".fastapicloud" / "cloud.json"
1323+ config_path .parent .mkdir (parents = True , exist_ok = True )
1324+ config_path .write_text (f'{{"app_id": "{ app_id } ", "team_id": "{ team_id } "}}' )
1325+
1326+ respx_mock .get (f"/apps/{ app_id } " ).mock (return_value = Response (200 , json = app_data ))
1327+
1328+ respx_mock .post (f"/apps/{ app_id } /deployments/" ).mock (
1329+ return_value = Response (201 , json = deployment_data )
1330+ )
1331+
1332+ respx_mock .post (f"/deployments/{ deployment_data ['id' ]} /upload" ).mock (
1333+ return_value = Response (
1334+ 200 ,
1335+ json = {"url" : "http://test.com" , "fields" : {"key" : "value" }},
1336+ )
1337+ )
1338+
1339+ respx_mock .post ("http://test.com" , data = {"key" : "value" }).mock (
1340+ return_value = Response (200 )
1341+ )
1342+
1343+ respx_mock .post (f"/deployments/{ deployment_data ['id' ]} /upload-complete" ).mock (
1344+ return_value = Response (200 )
1345+ )
1346+
1347+ respx_mock .get (f"/deployments/{ deployment_data ['id' ]} /build-logs" ).mock (
1348+ return_value = Response (
1349+ 200 ,
1350+ content = build_logs_response (
1351+ {"type" : "message" , "message" : "Building..." , "id" : "1" },
1352+ {"type" : "complete" },
1353+ ),
1354+ )
1355+ )
1356+
1357+ with changing_dir (tmp_path ):
1358+ result = runner .invoke (app , ["deploy" , "--app-id" , app_id ])
1359+
1360+ assert result .exit_code == 0
1361+ # Should NOT show mismatch warning
1362+ assert "does not match" not in result .output
1363+ assert f"Deploying to app { app_id } " in result .output
1364+
1365+
1366+ @pytest .mark .respx (base_url = settings .base_api_url )
1367+ def test_deploy_with_app_id_mismatch_fails (
1368+ logged_in_cli : None , tmp_path : Path , respx_mock : respx .MockRouter
1369+ ) -> None :
1370+ local_app_data = _get_random_app ()
1371+ local_app_id = local_app_data ["id" ]
1372+ team_id = "some-team-id"
1373+
1374+ config_path = tmp_path / ".fastapicloud" / "cloud.json"
1375+ config_path .parent .mkdir (parents = True , exist_ok = True )
1376+ config_path .write_text (f'{{"app_id": "{ local_app_id } ", "team_id": "{ team_id } "}}' )
1377+
1378+ cli_app_id = "different-app-id"
1379+
1380+ with changing_dir (tmp_path ):
1381+ result = runner .invoke (app , ["deploy" , "--app-id" , cli_app_id ])
1382+
1383+ assert result .exit_code == 1
1384+ assert "does not match" in result .output
1385+ assert "fastapi cloud unlink" in result .output
1386+ assert "FASTAPI_CLOUD_APP_ID" in result .output
1387+
1388+
1389+ @pytest .mark .respx (base_url = settings .base_api_url )
1390+ def test_deploy_with_app_id_arg_app_not_found (
1391+ logged_in_cli : None , tmp_path : Path , respx_mock : respx .MockRouter
1392+ ) -> None :
1393+ app_id = "nonexistent-app-id"
1394+
1395+ respx_mock .get (f"/apps/{ app_id } " ).mock (return_value = Response (404 ))
1396+
1397+ with changing_dir (tmp_path ):
1398+ result = runner .invoke (app , ["deploy" , "--app-id" , app_id ])
1399+
1400+ assert result .exit_code == 1
1401+ assert "App not found" in result .output
1402+ # Should NOT show unlink tip when using --app-id
1403+ assert "unlink" not in result .output
0 commit comments