diff --git a/sqlmesh/core/context.py b/sqlmesh/core/context.py index 5902977331..d9cc1dba20 100644 --- a/sqlmesh/core/context.py +++ b/sqlmesh/core/context.py @@ -1856,6 +1856,8 @@ def invalidate_environment(self, name: str, sync: bool = False) -> None: be deleted asynchronously by the janitor process. """ name = Environment.sanitize_name(name) + if self.state_sync.get_environment(name) is None: + raise SQLMeshError(f"Environment '{name}' does not exist.") self.state_sync.invalidate_environment(name) if sync: self._cleanup_environments(name=name) diff --git a/tests/core/test_context.py b/tests/core/test_context.py index 365d31d3fd..83b75c4d26 100644 --- a/tests/core/test_context.py +++ b/tests/core/test_context.py @@ -1611,6 +1611,20 @@ def test_invalidate_environment_no_sync_skips_cleanup(sushi_context, mocker: Moc state_sync_mock.delete_expired_environments.assert_not_called() +def test_invalidate_environment_nonexistent_raises(sushi_context, mocker: MockerFixture) -> None: + """Invalidating an environment that does not exist should error instead of + reporting success, so a mistyped name is caught rather than silently accepted.""" + state_sync_mock = mocker.patch.object( + type(sushi_context), "state_sync", new_callable=mocker.PropertyMock + ).return_value + state_sync_mock.get_environment.return_value = None + + with pytest.raises(SQLMeshError, match="Environment 'doesnotexist' does not exist"): + sushi_context.invalidate_environment("doesnotexist") + + state_sync_mock.invalidate_environment.assert_not_called() + + @pytest.mark.slow def test_plan_default_end(sushi_context_pre_scheduling: Context): prod_plan_builder = sushi_context_pre_scheduling.plan_builder("prod")