diff --git a/airflow-ctl/src/airflowctl/api/operations.py b/airflow-ctl/src/airflowctl/api/operations.py index 9c492022221bb..6b38225250559 100644 --- a/airflow-ctl/src/airflowctl/api/operations.py +++ b/airflow-ctl/src/airflowctl/api/operations.py @@ -193,6 +193,9 @@ def __init_subclass__(cls, **kwargs): setattr(cls, attr, _check_flag_and_exit_if_server_response_error(value)) def execute_list(self, *, path, data_model, offset=0, limit=50, params=None): + if limit <= 0: + raise ValueError(f"limit must be a positive integer, got {limit}") + shared_params = {"limit": limit, **(params or {})} def safe_validate(content: bytes) -> BaseModel: diff --git a/airflow-ctl/tests/airflow_ctl/api/test_operations.py b/airflow-ctl/tests/airflow_ctl/api/test_operations.py index 8254409863261..44c5ab3231b69 100644 --- a/airflow-ctl/tests/airflow_ctl/api/test_operations.py +++ b/airflow-ctl/tests/airflow_ctl/api/test_operations.py @@ -226,6 +226,16 @@ def test_execute_list_sends_limit_on_subsequent_pages(self): for call in mock_client.get.call_args_list: assert call.kwargs["params"]["limit"] == 2 + @pytest.mark.parametrize("limit", [0, -1]) + def test_execute_list_rejects_non_positive_limit(self, limit): + mock_client = Mock() + base_operation = BaseOperations(client=mock_client) + + with pytest.raises(ValueError, match="limit must be a positive integer"): + base_operation.execute_list(path="hello", data_model=HelloCollectionResponse, limit=limit) + + mock_client.get.assert_not_called() + class TestAssetsOperations: asset_id: int = 1