@@ -2496,6 +2496,107 @@ def test_app_rollback(servicer, mock_dir, set_env_client):
24962496 run_cli_command (["app" , "rollback" , "my_app" , "2" ], expected_exit_code = 2 )
24972497
24982498
2499+ def _record_future_deployment (servicer , app_id : str , version : int ) -> None :
2500+ """Add a deployment to an App's history that is newer than the currently deployed version."""
2501+ latest = servicer .app_deployment_history [app_id ][- 1 ]
2502+ servicer .app_deployment_history [app_id ].append ({** latest , "version" : version , "tag" : f"deploy{ version } " })
2503+
2504+
2505+ def test_app_rollback_relative_to_live_version (servicer , mock_dir , set_env_client ):
2506+ with mock_dir ({"myapp.py" : dummy_app_file , "other_module.py" : dummy_other_module_file }):
2507+ for _ in range (3 ):
2508+ run_cli_command (["deploy" , "myapp.py" , "--name" , "my_app" ])
2509+ app_id = servicer .deployed_apps [("main" , "my_app" )]
2510+ _record_future_deployment (servicer , app_id , version = 5 )
2511+
2512+ # Relative versions resolve against the live version (v3), not the newest staged row (v5),
2513+ # but the new deployment still lands above everything in history.
2514+ run_cli_command (["app" , "rollback" , "my_app" ])
2515+ assert servicer .app_deployment_history [app_id ][- 1 ]["rollback_version" ] == 2
2516+ assert servicer .app_deployment_history [app_id ][- 1 ]["version" ] == 6
2517+
2518+
2519+ @pytest .mark .parametrize ("version" , ["v5" , "5" ])
2520+ def test_app_promote (servicer , mock_dir , set_env_client , version ):
2521+ with mock_dir ({"myapp.py" : dummy_app_file , "other_module.py" : dummy_other_module_file }):
2522+ run_cli_command (["deploy" , "myapp.py" , "--name" , "my_app" ])
2523+ app_id = servicer .deployed_apps [("main" , "my_app" )]
2524+ _record_future_deployment (servicer , app_id , version = 5 )
2525+
2526+ with servicer .intercept () as ctx :
2527+ res = run_cli_command (["app" , "promote" , "my_app" , version ])
2528+
2529+ (request ,) = ctx .get_requests ("AppPromote" )
2530+ assert request .app_id == app_id
2531+ assert request .version == 5
2532+
2533+ assert "Promoted App to v5" in res .stdout
2534+ assert "http://test.modal.com/foo/bar" in res .stdout
2535+
2536+ # The promotion is recorded as a new deployment above every version in history
2537+ assert servicer .app_deployment_history [app_id ][- 1 ]["version" ] == 6
2538+ assert servicer .app_deployment_history [app_id ][- 1 ]["rollback_version" ] == 5
2539+
2540+
2541+ @pytest .mark .parametrize ("version" , ["v0" , "0" , "v" , "latest" , "-1" , "v5.1" , "5x" ])
2542+ def test_app_promote_invalid_version (servicer , mock_dir , set_env_client , version ):
2543+ with mock_dir ({"myapp.py" : dummy_app_file , "other_module.py" : dummy_other_module_file }):
2544+ run_cli_command (["deploy" , "myapp.py" , "--name" , "my_app" ])
2545+
2546+ with servicer .intercept () as ctx :
2547+ run_cli_command (["app" , "promote" , "my_app" , version ], expected_exit_code = 2 )
2548+
2549+ # Version specifiers are validated before we hit the server
2550+ assert not ctx .get_requests ("AppPromote" )
2551+
2552+
2553+ def test_app_promote_requires_version (servicer , mock_dir , set_env_client ):
2554+ with mock_dir ({"myapp.py" : dummy_app_file , "other_module.py" : dummy_other_module_file }):
2555+ run_cli_command (["deploy" , "myapp.py" , "--name" , "my_app" ])
2556+
2557+ run_cli_command (["app" , "promote" , "my_app" ], expected_exit_code = 2 )
2558+
2559+
2560+ def test_app_promote_not_deployed (servicer , mock_dir , set_env_client ):
2561+ with mock_dir ({"myapp.py" : dummy_app_file , "other_module.py" : dummy_other_module_file }):
2562+ run_cli_command (["deploy" , "myapp.py" , "--name" , "my_app" ])
2563+ app_id = servicer .deployed_apps [("main" , "my_app" )]
2564+ _record_future_deployment (servicer , app_id , version = 5 )
2565+ servicer .app_state_history [app_id ].append (api_pb2 .APP_STATE_STOPPED )
2566+
2567+ with servicer .intercept () as ctx :
2568+ run_cli_command (
2569+ ["app" , "promote" , "my_app" , "v5" ], expected_exit_code = 1 , expected_error = "App .* is not deployed"
2570+ )
2571+
2572+ assert not ctx .get_requests ("AppPromote" )
2573+
2574+
2575+ def test_app_promote_version_not_newer (servicer , mock_dir , set_env_client ):
2576+ with mock_dir ({"myapp.py" : dummy_app_file , "other_module.py" : dummy_other_module_file }):
2577+ for _ in range (2 ):
2578+ run_cli_command (["deploy" , "myapp.py" , "--name" , "my_app" ])
2579+
2580+ # v1 and v2 exist in the App history, but the App already serves v2
2581+ for version in ["v1" , "v2" ]:
2582+ run_cli_command (
2583+ ["app" , "promote" , "my_app" , version ],
2584+ expected_exit_code = 1 ,
2585+ expected_error = "must be newer than the current App version" ,
2586+ )
2587+
2588+
2589+ def test_app_promote_version_not_in_history (servicer , mock_dir , set_env_client ):
2590+ with mock_dir ({"myapp.py" : dummy_app_file , "other_module.py" : dummy_other_module_file }):
2591+ run_cli_command (["deploy" , "myapp.py" , "--name" , "my_app" ])
2592+
2593+ run_cli_command (
2594+ ["app" , "promote" , "my_app" , "v100" ],
2595+ expected_exit_code = 1 ,
2596+ expected_error = "not found in App history" ,
2597+ )
2598+
2599+
24992600def test_dict_create_list_delete (servicer , server_url_env , set_env_client ):
25002601 run_cli_command (["dict" , "create" , "foo-dict" ])
25012602 run_cli_command (["dict" , "create" , "bar-dict" ])
@@ -3246,6 +3347,8 @@ async def task_list(servicer, stream):
32463347 ctx .set_responder ("TaskList" , task_list )
32473348
32483349 with mock_dir ({"myapp.py" : dummy_app_file , "other_module.py" : dummy_other_module_file }):
3350+ # Deploy twice so that there is an earlier version for `rollback` to target
3351+ run_cli_command (["deploy" , "myapp.py" ])
32493352 run_cli_command (["deploy" , "myapp.py" ])
32503353
32513354 res = run_cli_command (["app" , cmd , "my_app" , "--strategy" , "recreate" ])
@@ -3259,6 +3362,8 @@ async def task_list(servicer, stream):
32593362@pytest .mark .parametrize ("cmd" , ["rollover" , "rollback" ])
32603363def test_rolling_strategy (cmd , servicer , mock_dir , set_env_client ):
32613364 with mock_dir ({"myapp.py" : dummy_app_file , "other_module.py" : dummy_other_module_file }):
3365+ # Deploy twice so that there is an earlier version for `rollback` to target
3366+ run_cli_command (["deploy" , "myapp.py" ])
32623367 run_cli_command (["deploy" , "myapp.py" ])
32633368
32643369 res = run_cli_command (["app" , cmd , "my_app" ])
0 commit comments