This is an automated email from the ASF dual-hosted git repository.
vincbeck pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new d9d0d2cc07a Add tests for the AWS Batch executor boto schemas (#72609)
d9d0d2cc07a is described below
commit d9d0d2cc07a85052b23349038e38194806d31570
Author: JiHo Kim <[email protected]>
AuthorDate: Wed Sep 9 23:41:45 2026 +0900
Add tests for the AWS Batch executor boto schemas (#72609)
The three marshmallow schemas in boto_schema.py sit on the
OVERLOOKED_TESTS allowlist and, unlike most entries there, are not
covered indirectly either - nothing under providers/*/tests references
them. They parse live AWS Batch API responses, so the behaviour worth
pinning down is the data_key renaming from the API's camelCase, the
unknown=EXCLUDE policy that tolerates fields AWS adds over time, and
the post_load hook that returns a BatchJob rather than a plain dict.
Removes the module's entry from OVERLOOKED_TESTS in the same commit, as
that list fails once a listed test file exists.
related: #35442
---
.../tests/unit/always/test_project_structure.py | 1 -
.../amazon/aws/executors/batch/test_boto_schema.py | 124 +++++++++++++++++++++
2 files changed, 124 insertions(+), 1 deletion(-)
diff --git a/airflow-core/tests/unit/always/test_project_structure.py
b/airflow-core/tests/unit/always/test_project_structure.py
index 28eac70d585..2efbc1d7470 100644
--- a/airflow-core/tests/unit/always/test_project_structure.py
+++ b/airflow-core/tests/unit/always/test_project_structure.py
@@ -64,7 +64,6 @@ class TestProjectStructure:
"providers/amazon/tests/unit/amazon/aws/auth_manager/datamodels/test_login.py",
"providers/amazon/tests/unit/amazon/aws/auth_manager/security_manager/test_aws_security_manager_override.py",
"providers/amazon/tests/unit/amazon/aws/executors/batch/test_batch_executor_config.py",
-
"providers/amazon/tests/unit/amazon/aws/executors/batch/test_boto_schema.py",
"providers/amazon/tests/unit/amazon/aws/executors/ecs/test_ecs_executor_config.py",
"providers/amazon/tests/unit/amazon/aws/executors/aws_lambda/test_utils.py",
"providers/amazon/tests/unit/amazon/aws/executors/aws_lambda/docker/test_app.py",
diff --git
a/providers/amazon/tests/unit/amazon/aws/executors/batch/test_boto_schema.py
b/providers/amazon/tests/unit/amazon/aws/executors/batch/test_boto_schema.py
new file mode 100644
index 00000000000..823fb5f88dc
--- /dev/null
+++ b/providers/amazon/tests/unit/amazon/aws/executors/batch/test_boto_schema.py
@@ -0,0 +1,124 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+import pytest
+from marshmallow import ValidationError
+
+from airflow.providers.amazon.aws.executors.batch.boto_schema import (
+ BatchDescribeJobsResponseSchema,
+ BatchJobDetailSchema,
+ BatchSubmitJobResponseSchema,
+)
+from airflow.providers.amazon.aws.executors.batch.utils import BatchJob
+
+
+class TestBatchSubmitJobResponseSchema:
+ def test_load_maps_camel_case_job_id_to_snake_case(self):
+ loaded = BatchSubmitJobResponseSchema().load({"jobId": "job-1"})
+
+ assert loaded == {"job_id": "job-1"}
+
+ def test_load_ignores_fields_the_submit_job_api_adds(self):
+ loaded = BatchSubmitJobResponseSchema().load(
+ {"jobId": "job-1", "jobName": "airflow-task", "jobArn":
"arn:aws:batch:job-1"}
+ )
+
+ assert loaded == {"job_id": "job-1"}
+
+ def test_load_rejects_response_without_job_id(self):
+ with pytest.raises(ValidationError) as excinfo:
+ BatchSubmitJobResponseSchema().load({"jobName": "airflow-task"})
+
+ assert "jobId" in excinfo.value.messages
+
+ def test_load_rejects_snake_case_job_id(self):
+ with pytest.raises(ValidationError) as excinfo:
+ BatchSubmitJobResponseSchema().load({"job_id": "job-1"})
+
+ assert "jobId" in excinfo.value.messages
+
+
+class TestBatchJobDetailSchema:
+ def test_load_returns_batch_job_instead_of_dict(self):
+ job = BatchJobDetailSchema().load({"jobId": "job-1", "status":
"RUNNING", "statusReason": "started"})
+
+ assert isinstance(job, BatchJob)
+ assert job.job_id == "job-1"
+ assert job.status == "RUNNING"
+ assert job.status_reason == "started"
+
+ def test_load_without_status_reason_leaves_it_unset(self):
+ job = BatchJobDetailSchema().load({"jobId": "job-1", "status":
"SUCCEEDED"})
+
+ assert job.status_reason is None
+
+ def test_load_ignores_fields_the_describe_jobs_api_adds(self):
+ job = BatchJobDetailSchema().load(
+ {"jobId": "job-1", "status": "FAILED", "jobQueue": "queue-1",
"startedAt": 1700000000}
+ )
+
+ assert isinstance(job, BatchJob)
+ assert job.job_id == "job-1"
+
+ @pytest.mark.parametrize(
+ ("payload", "missing_key"),
+ [
+ pytest.param({"status": "RUNNING"}, "jobId", id="without-job-id"),
+ pytest.param({"jobId": "job-1"}, "status", id="without-status"),
+ ],
+ )
+ def test_load_rejects_detail_missing_a_required_field(self, payload,
missing_key):
+ with pytest.raises(ValidationError) as excinfo:
+ BatchJobDetailSchema().load(payload)
+
+ assert missing_key in excinfo.value.messages
+
+
+class TestBatchDescribeJobsResponseSchema:
+ def test_load_returns_a_batch_job_per_entry(self):
+ loaded = BatchDescribeJobsResponseSchema().load(
+ {
+ "jobs": [
+ {"jobId": "job-1", "status": "RUNNING"},
+ {"jobId": "job-2", "status": "FAILED", "statusReason":
"exit code 1"},
+ ]
+ }
+ )
+
+ assert [(job.job_id, job.status, job.status_reason) for job in
loaded["jobs"]] == [
+ ("job-1", "RUNNING", None),
+ ("job-2", "FAILED", "exit code 1"),
+ ]
+ assert all(isinstance(job, BatchJob) for job in loaded["jobs"])
+
+ def test_load_accepts_an_empty_job_list(self):
+ loaded = BatchDescribeJobsResponseSchema().load({"jobs": []})
+
+ assert loaded == {"jobs": []}
+
+ def test_load_rejects_response_without_jobs(self):
+ with pytest.raises(ValidationError) as excinfo:
+ BatchDescribeJobsResponseSchema().load({})
+
+ assert "jobs" in excinfo.value.messages
+
+ def test_load_propagates_validation_error_from_a_nested_job(self):
+ with pytest.raises(ValidationError) as excinfo:
+ BatchDescribeJobsResponseSchema().load({"jobs": [{"jobId":
"job-1"}]})
+
+ assert "status" in excinfo.value.messages["jobs"][0]