kaxil commented on code in PR #63355:
URL: https://github.com/apache/airflow/pull/63355#discussion_r3416871687


##########
airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_task_instances.py:
##########
@@ -1971,6 +1971,62 @@ def test_ti_update_state_not_running(self, client, 
session, create_task_instance
         session.refresh(ti)
         assert ti.state == State.SUCCESS
 
+    def test_ti_update_state_same_state_is_idempotent(self, client, session, 
create_task_instance):
+        """Test that setting a TI to its current state is treated as an 
idempotent no-op."""
+        ti = create_task_instance(
+            task_id="test_ti_update_state_same_state_is_idempotent",
+            state=State.SUCCESS,
+            session=session,
+            start_date=DEFAULT_START_DATE,
+        )
+        ti.end_date = DEFAULT_END_DATE
+        session.commit()
+
+        response = client.patch(
+            f"/execution/task-instances/{ti.id}/state",
+            json={
+                "state": "success",
+                "end_date": timezone.parse("2024-10-31T13:00:00Z").isoformat(),
+            },
+        )
+        assert response.status_code == 200
+        assert response.content == b""
+
+        session.refresh(ti)
+        assert ti.state == State.SUCCESS
+        assert ti.end_date == DEFAULT_END_DATE

Review Comment:
   This sends a different `end_date` (13:00) than the TI's stored value 
(`DEFAULT_END_DATE`, 12:00) and then asserts the stored value wins (line 1997), 
so it codifies "silently drop a conflicting payload field" as the contract. But 
the duplicate-SUCCESS source this actually fixes (supervisor replay / httpx 
retry) re-sends the byte-identical payload, so this documents a case the bug 
can't produce. Sending the same `end_date` here would document the 
idempotent-replay path you're fixing -- and would still pass.



##########
airflow-core/src/airflow/api_fastapi/execution_api/routes/task_instances.py:
##########
@@ -335,16 +335,12 @@ def ti_run(
 @ti_id_router.patch(
     "/{task_instance_id}/state",
     status_code=status.HTTP_204_NO_CONTENT,
-    responses=create_openapi_http_exception_doc(
-        [
-            (status.HTTP_404_NOT_FOUND, "Task Instance not found"),
-            (
-                status.HTTP_409_CONFLICT,
-                "The TI is already in the requested state",
-            ),
-            (HTTP_422_UNPROCESSABLE_CONTENT, "Invalid payload for the state 
transition"),
-        ]
-    ),
+    responses={
+        status.HTTP_200_OK: {"description": "The TI was already in the 
requested state"},
+        status.HTTP_404_NOT_FOUND: {"description": "Task Instance not found"},
+        status.HTTP_409_CONFLICT: {"description": "The TI is not in a valid 
state for this transition"},
+        HTTP_422_UNPROCESSABLE_CONTENT: {"description": "Invalid payload for 
the state transition"},
+    },

Review Comment:
   This is now the only route in the file that hand-writes `responses=` instead 
of going through `create_openapi_http_exception_doc(...)` (the helper is used 
at lines 118, 777, 840, 963, 997, 1039, 1307). The helper attaches `model: 
HTTPExceptionResponse` to each error code; this literal dict carries only 
`description`, so 404/409/422 lose their response schema in the generated 
OpenAPI spec (and the codegen'd execution-API client). Since the helper only 
emits error models, you can keep it for the error codes and merge the 200 in:
   
   ```python
   responses={
       status.HTTP_200_OK: {"description": "The TI was already in the requested 
state"},
       **create_openapi_http_exception_doc(
           [
               (status.HTTP_404_NOT_FOUND, "Task Instance not found"),
               (status.HTTP_409_CONFLICT, "The TI is not in a valid state for 
this transition"),
               (HTTP_422_UNPROCESSABLE_CONTENT, "Invalid payload for the state 
transition"),
           ]
       ),
   },
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to