@@ -1079,3 +1079,188 @@ def test_cancel_upload_swallows_exceptions(
10791079
10801080 assert upload_cancelled_route .called
10811081 assert "HTTPStatusError" not in result .output
1082+
1083+
1084+ @pytest .mark .respx (base_url = settings .base_api_url )
1085+ def test_deploy_with_app_id_arg (
1086+ logged_in_cli : None , tmp_path : Path , respx_mock : respx .MockRouter
1087+ ) -> None :
1088+ app_data = _get_random_app ()
1089+ app_id = app_data ["id" ]
1090+ deployment_data = _get_random_deployment (app_id = app_id )
1091+
1092+ respx_mock .get (f"/apps/{ app_id } " ).mock (return_value = Response (200 , json = app_data ))
1093+
1094+ respx_mock .post (f"/apps/{ app_id } /deployments/" ).mock (
1095+ return_value = Response (201 , json = deployment_data )
1096+ )
1097+
1098+ respx_mock .post (f"/deployments/{ deployment_data ['id' ]} /upload" ).mock (
1099+ return_value = Response (
1100+ 200 ,
1101+ json = {"url" : "http://test.com" , "fields" : {"key" : "value" }},
1102+ )
1103+ )
1104+
1105+ respx_mock .post ("http://test.com" , data = {"key" : "value" }).mock (
1106+ return_value = Response (200 )
1107+ )
1108+
1109+ respx_mock .post (f"/deployments/{ deployment_data ['id' ]} /upload-complete" ).mock (
1110+ return_value = Response (200 )
1111+ )
1112+
1113+ respx_mock .get (f"/deployments/{ deployment_data ['id' ]} /build-logs" ).mock (
1114+ return_value = Response (
1115+ 200 ,
1116+ content = build_logs_response (
1117+ {"type" : "message" , "message" : "Building..." , "id" : "1" },
1118+ {"type" : "complete" },
1119+ ),
1120+ )
1121+ )
1122+
1123+ with changing_dir (tmp_path ):
1124+ result = runner .invoke (app , ["deploy" , "--app-id" , app_id ])
1125+
1126+ assert result .exit_code == 0
1127+ assert f"Deploying to app { app_id } " in result .output
1128+
1129+
1130+ @pytest .mark .respx (base_url = settings .base_api_url )
1131+ def test_deploy_with_app_id_from_env_var (
1132+ logged_in_cli : None , tmp_path : Path , respx_mock : respx .MockRouter
1133+ ) -> None :
1134+ app_data = _get_random_app ()
1135+ app_id = app_data ["id" ]
1136+ deployment_data = _get_random_deployment (app_id = app_id )
1137+
1138+ respx_mock .get (f"/apps/{ app_id } " ).mock (return_value = Response (200 , json = app_data ))
1139+
1140+ respx_mock .post (f"/apps/{ app_id } /deployments/" ).mock (
1141+ return_value = Response (201 , json = deployment_data )
1142+ )
1143+
1144+ respx_mock .post (f"/deployments/{ deployment_data ['id' ]} /upload" ).mock (
1145+ return_value = Response (
1146+ 200 ,
1147+ json = {"url" : "http://test.com" , "fields" : {"key" : "value" }},
1148+ )
1149+ )
1150+
1151+ respx_mock .post ("http://test.com" , data = {"key" : "value" }).mock (
1152+ return_value = Response (200 )
1153+ )
1154+
1155+ respx_mock .post (f"/deployments/{ deployment_data ['id' ]} /upload-complete" ).mock (
1156+ return_value = Response (200 )
1157+ )
1158+
1159+ respx_mock .get (f"/deployments/{ deployment_data ['id' ]} /build-logs" ).mock (
1160+ return_value = Response (
1161+ 200 ,
1162+ content = build_logs_response (
1163+ {"type" : "message" , "message" : "Building..." , "id" : "1" },
1164+ {"type" : "complete" },
1165+ ),
1166+ )
1167+ )
1168+
1169+ with changing_dir (tmp_path ):
1170+ result = runner .invoke (app , ["deploy" ], env = {"FASTAPI_CLOUD_APP_ID" : app_id })
1171+
1172+ assert result .exit_code == 0
1173+ assert f"Deploying to app { app_id } " in result .output
1174+
1175+
1176+ @pytest .mark .respx (base_url = settings .base_api_url )
1177+ def test_deploy_with_app_id_matching_local_config (
1178+ logged_in_cli : None , tmp_path : Path , respx_mock : respx .MockRouter
1179+ ) -> None :
1180+ app_data = _get_random_app ()
1181+ app_id = app_data ["id" ]
1182+ team_id = "some-team-id"
1183+ deployment_data = _get_random_deployment (app_id = app_id )
1184+
1185+ config_path = tmp_path / ".fastapicloud" / "cloud.json"
1186+ config_path .parent .mkdir (parents = True , exist_ok = True )
1187+ config_path .write_text (f'{{"app_id": "{ app_id } ", "team_id": "{ team_id } "}}' )
1188+
1189+ respx_mock .get (f"/apps/{ app_id } " ).mock (return_value = Response (200 , json = app_data ))
1190+
1191+ respx_mock .post (f"/apps/{ app_id } /deployments/" ).mock (
1192+ return_value = Response (201 , json = deployment_data )
1193+ )
1194+
1195+ respx_mock .post (f"/deployments/{ deployment_data ['id' ]} /upload" ).mock (
1196+ return_value = Response (
1197+ 200 ,
1198+ json = {"url" : "http://test.com" , "fields" : {"key" : "value" }},
1199+ )
1200+ )
1201+
1202+ respx_mock .post ("http://test.com" , data = {"key" : "value" }).mock (
1203+ return_value = Response (200 )
1204+ )
1205+
1206+ respx_mock .post (f"/deployments/{ deployment_data ['id' ]} /upload-complete" ).mock (
1207+ return_value = Response (200 )
1208+ )
1209+
1210+ respx_mock .get (f"/deployments/{ deployment_data ['id' ]} /build-logs" ).mock (
1211+ return_value = Response (
1212+ 200 ,
1213+ content = build_logs_response (
1214+ {"type" : "message" , "message" : "Building..." , "id" : "1" },
1215+ {"type" : "complete" },
1216+ ),
1217+ )
1218+ )
1219+
1220+ with changing_dir (tmp_path ):
1221+ result = runner .invoke (app , ["deploy" , "--app-id" , app_id ])
1222+
1223+ assert result .exit_code == 0
1224+ # Should NOT show mismatch warning
1225+ assert "does not match" not in result .output
1226+ assert f"Deploying to app { app_id } " in result .output
1227+
1228+
1229+ @pytest .mark .respx (base_url = settings .base_api_url )
1230+ def test_deploy_with_app_id_mismatch_fails (
1231+ logged_in_cli : None , tmp_path : Path , respx_mock : respx .MockRouter
1232+ ) -> None :
1233+ local_app_data = _get_random_app ()
1234+ local_app_id = local_app_data ["id" ]
1235+ team_id = "some-team-id"
1236+
1237+ config_path = tmp_path / ".fastapicloud" / "cloud.json"
1238+ config_path .parent .mkdir (parents = True , exist_ok = True )
1239+ config_path .write_text (f'{{"app_id": "{ local_app_id } ", "team_id": "{ team_id } "}}' )
1240+
1241+ cli_app_id = "different-app-id"
1242+
1243+ with changing_dir (tmp_path ):
1244+ result = runner .invoke (app , ["deploy" , "--app-id" , cli_app_id ])
1245+
1246+ assert result .exit_code == 1
1247+ assert "does not match" in result .output
1248+ assert "fastapi cloud unlink" in result .output
1249+ assert "FASTAPI_CLOUD_APP_ID" in result .output
1250+
1251+
1252+ @pytest .mark .respx (base_url = settings .base_api_url )
1253+ def test_deploy_with_app_id_arg_app_not_found (
1254+ logged_in_cli : None , tmp_path : Path , respx_mock : respx .MockRouter
1255+ ) -> None :
1256+ app_id = "nonexistent-app-id"
1257+
1258+ respx_mock .get (f"/apps/{ app_id } " ).mock (return_value = Response (404 ))
1259+
1260+ with changing_dir (tmp_path ):
1261+ result = runner .invoke (app , ["deploy" , "--app-id" , app_id ])
1262+
1263+ assert result .exit_code == 1
1264+ assert "App not found" in result .output
1265+ # Should NOT show unlink tip when using --app-id
1266+ assert "unlink" not in result .output
0 commit comments