@@ -6128,3 +6128,167 @@ async def test_deploying_directory_as_prefect_file(self):
6128
6128
"Is a directory: '.'. Skipping."
6129
6129
],
6130
6130
)
6131
+
6132
+
6133
+ class TestDeployDryRun :
6134
+ @pytest .fixture
6135
+ def project_dir_with_single_deployment (self , project_dir : str ):
6136
+ prefect_yaml = {
6137
+ "name" : "test-project" ,
6138
+ "deployments" : [
6139
+ {
6140
+ "name" : "test-deployment" ,
6141
+ "entrypoint" : "flows.py:hello_world" ,
6142
+ "work_pool" : {"name" : "test-pool" },
6143
+ }
6144
+ ],
6145
+ }
6146
+ with open (Path (project_dir ) / "prefect.yaml" , "w" ) as f :
6147
+ yaml .dump (prefect_yaml , f )
6148
+
6149
+ flows_file = Path (project_dir ) / "flows.py"
6150
+ flows_file .write_text ("""
6151
+ from prefect import flow
6152
+
6153
+ @flow
6154
+ def hello_world():
6155
+ print("Hello, world!")
6156
+ """ )
6157
+ return project_dir
6158
+
6159
+ @pytest .mark .usefixtures ("project_dir_with_single_deployment" )
6160
+ async def test_dry_run_no_api_calls (self ):
6161
+ """Test that dry run mode doesn't make API calls."""
6162
+ with mock .patch ("prefect.cli.deploy.get_client" ) as mock_get_client :
6163
+ mock_client = mock .AsyncMock ()
6164
+ mock_get_client .return_value = mock_client
6165
+
6166
+ # Mock the work pool to exist for validation
6167
+ mock_work_pool = mock .Mock ()
6168
+ mock_work_pool .type = "process"
6169
+ mock_work_pool .is_push_pool = False
6170
+ mock_work_pool .is_managed_pool = False
6171
+ mock_work_pool .base_job_template = {"variables" : {"properties" : {}}}
6172
+ mock_client .read_work_pool .return_value = mock_work_pool
6173
+
6174
+ await run_sync_in_worker_thread (
6175
+ invoke_and_assert ,
6176
+ command = "deploy --all --dry-run" ,
6177
+ expected_code = 0 ,
6178
+ expected_output_contains = [
6179
+ "DRY RUN: Would create/update deployment" ,
6180
+ "DRY RUN COMPLETE" ,
6181
+ ],
6182
+ )
6183
+
6184
+ # Verify no deployment creation API calls were made
6185
+ mock_client .create_deployment .assert_not_called ()
6186
+ mock_client .update_deployment .assert_not_called ()
6187
+ mock_client .create_automation .assert_not_called ()
6188
+ mock_client .apply_slas_for_deployment .assert_not_called ()
6189
+
6190
+ @pytest .mark .usefixtures ("project_dir_with_single_deployment" )
6191
+ async def test_dry_run_non_interactive (self ):
6192
+ """Test that dry run mode is non-interactive."""
6193
+ from prefect .cli .deploy import _is_interactive_mode
6194
+
6195
+ assert _is_interactive_mode (dry_run = True ) is False
6196
+
6197
+ with mock .patch ("prefect.cli.deploy.is_interactive" , return_value = True ):
6198
+ assert _is_interactive_mode (dry_run = False ) is True
6199
+ assert _is_interactive_mode (dry_run = True ) is False
6200
+
6201
+ @pytest .fixture
6202
+ def project_dir_with_multi_deployments (self , project_dir : str ):
6203
+ prefect_yaml = {
6204
+ "name" : "test-project" ,
6205
+ "build" : [
6206
+ {
6207
+ "prefect_docker.deployments.steps.build_docker_image" : {
6208
+ "image_name" : "test" ,
6209
+ "tag" : "latest" ,
6210
+ }
6211
+ }
6212
+ ],
6213
+ "deployments" : [
6214
+ {
6215
+ "name" : "test-deployment-1" ,
6216
+ "entrypoint" : "flows.py:hello_world" ,
6217
+ "work_pool" : {"name" : "test-pool" },
6218
+ },
6219
+ {
6220
+ "name" : "test-deployment-2" ,
6221
+ "entrypoint" : "flows.py:hello_world" ,
6222
+ "work_pool" : {"name" : "test-pool" },
6223
+ },
6224
+ ],
6225
+ }
6226
+ with open (Path (project_dir ) / "prefect.yaml" , "w" ) as f :
6227
+ yaml .dump (prefect_yaml , f )
6228
+
6229
+ flows_file = Path (project_dir ) / "flows.py"
6230
+ flows_file .write_text ("""
6231
+ from prefect import flow
6232
+
6233
+ @flow
6234
+ def hello_world():
6235
+ print("Hello, world!")
6236
+ """ )
6237
+ return project_dir
6238
+
6239
+ @pytest .mark .usefixtures ("project_dir_with_multi_deployments" )
6240
+ async def test_dry_run_with_build_steps (self ):
6241
+ """Test dry run with build steps doesn't execute them."""
6242
+ with mock .patch ("prefect.cli.deploy.get_client" ) as mock_get_client :
6243
+ mock_client = mock .AsyncMock ()
6244
+ mock_get_client .return_value = mock_client
6245
+
6246
+ mock_work_pool = mock .Mock ()
6247
+ mock_work_pool .type = "process"
6248
+ mock_work_pool .is_push_pool = False
6249
+ mock_work_pool .is_managed_pool = False
6250
+ mock_work_pool .base_job_template = {"variables" : {"properties" : {}}}
6251
+ mock_client .read_work_pool .return_value = mock_work_pool
6252
+
6253
+ with mock .patch (
6254
+ "prefect.deployments.steps.core.run_steps"
6255
+ ) as mock_run_steps :
6256
+ await run_sync_in_worker_thread (
6257
+ invoke_and_assert ,
6258
+ command = "deploy --all --dry-run" ,
6259
+ expected_code = 0 ,
6260
+ expected_output_contains = [
6261
+ "DRY RUN MODE" ,
6262
+ "Would run 1 build step(s)" ,
6263
+ "DRY RUN COMPLETE" ,
6264
+ ],
6265
+ )
6266
+
6267
+ # Verify build steps were not executed
6268
+ mock_run_steps .assert_not_called ()
6269
+
6270
+ @pytest .mark .usefixtures ("project_dir_with_single_deployment" )
6271
+ async def test_dry_run_missing_entrypoint_fails (self ):
6272
+ """Test that dry run still validates required fields."""
6273
+ prefect_yaml = {
6274
+ "name" : "test-project" ,
6275
+ "deployments" : [
6276
+ {
6277
+ "name" : "test-deployment" ,
6278
+ # Missing entrypoint should fail validation
6279
+ "work_pool" : {"name" : "test-pool" },
6280
+ }
6281
+ ],
6282
+ }
6283
+
6284
+ with open ("prefect.yaml" , "w" ) as f :
6285
+ yaml .dump (prefect_yaml , f )
6286
+
6287
+ await run_sync_in_worker_thread (
6288
+ invoke_and_assert ,
6289
+ command = "deploy --all --dry-run" ,
6290
+ expected_code = 1 ,
6291
+ expected_output_contains = [
6292
+ "An entrypoint must be provided" ,
6293
+ ],
6294
+ )
0 commit comments