fix(tests): repair test_create_list_delete_task_flow's two defects
Same dependency_overrides fix as the other test_*.py commits for get_task_executor (patch() cannot reach a route's already-registered Depends() reference — see the conftest.py commit for the full explanation and the empirical check that established it). Once the mock actually intercepted the DB layer, a second, independent defect surfaced: KeyError: 0. mock_cursor.fetchone.side_effect held 3 values sized for "create, list, delete" (one fetchone() each), but list_tasks uses fetchall(), not fetchone(), and delete_task has grown a second fetchone() call since this test was written —c34db66added the 409-on-history-loss check, which does `row = cur.fetchone()` for the task id and then a separate `cur.fetchone()[0]` for its execution count. A real create->list-> delete flow through this endpoint set needs 1 (create) + 2 (delete) = 3 fetchone() values in that order, not one per named step. Re-sequenced so delete_task sees a real id and a zero execution count, reaching the intended 200 rather than an unhandled KeyError. Source unchanged; delete_task's behavior is deliberate (c34db66) and this integration test had not been updated to track it.
This commit is contained in:
+39
-33
@@ -27,49 +27,55 @@ class TestDatabaseIntegration:
|
||||
class TestEndToEndTaskFlow:
|
||||
"""End-to-end tests for task lifecycle (mocked database)."""
|
||||
|
||||
def test_create_list_delete_task_flow(self, client: TestClient, auth_headers: dict, sample_task_data: dict):
|
||||
def test_create_list_delete_task_flow(self, client: TestClient, auth_headers: dict, sample_task_data: dict, override_task_executor):
|
||||
"""Test complete task lifecycle: create → list → delete."""
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
# This simulates the full flow with mocked database
|
||||
with patch('src.main.get_task_executor') as mock_executor:
|
||||
mock_conn = MagicMock()
|
||||
mock_cursor = MagicMock()
|
||||
mock_conn = MagicMock()
|
||||
mock_cursor = MagicMock()
|
||||
|
||||
# Mock create
|
||||
created_task = {**sample_task_data, "id": 99}
|
||||
mock_cursor.fetchone.side_effect = [
|
||||
created_task, # Create task
|
||||
created_task, # List tasks (as dict)
|
||||
("test_task",) # Delete task
|
||||
]
|
||||
mock_cursor.fetchall.return_value = [created_task]
|
||||
# Mock create. list_tasks (GET /tasks) uses fetchall, not fetchone, so
|
||||
# it does not consume a slot here — the previous list of 3 values was
|
||||
# sized for a delete that has since grown a second lookup (T-97's
|
||||
# 409-on-history-loss check, c34db66): delete_task now does
|
||||
# `row = cur.fetchone()` for the task id, then a separate
|
||||
# `cur.fetchone()[0]` for its execution count, so a real
|
||||
# create->list->delete flow needs 1 (create) + 2 (delete) = 3
|
||||
# fetchone() calls in that order, not create+list+delete.
|
||||
created_task = {**sample_task_data, "id": 99}
|
||||
mock_cursor.fetchone.side_effect = [
|
||||
created_task, # create_task: INSERT ... RETURNING (dict row)
|
||||
(99,), # delete_task: SELECT id FROM scheduled_tasks
|
||||
(0,), # delete_task: SELECT COUNT(*) FROM task_executions — none, so it proceeds
|
||||
]
|
||||
mock_cursor.fetchall.return_value = [created_task]
|
||||
|
||||
mock_conn.cursor.return_value.__enter__ = MagicMock(return_value=mock_cursor)
|
||||
mock_conn.cursor.return_value.__exit__ = MagicMock(return_value=None)
|
||||
mock_executor.return_value.get_db_connection.return_value.__enter__ = MagicMock(return_value=mock_conn)
|
||||
mock_executor.return_value.get_db_connection.return_value.__exit__ = MagicMock(return_value=None)
|
||||
mock_conn.cursor.return_value.__enter__ = MagicMock(return_value=mock_cursor)
|
||||
mock_conn.cursor.return_value.__exit__ = MagicMock(return_value=None)
|
||||
override_task_executor.get_db_connection.return_value.__enter__ = MagicMock(return_value=mock_conn)
|
||||
override_task_executor.get_db_connection.return_value.__exit__ = MagicMock(return_value=None)
|
||||
|
||||
# Create
|
||||
create_response = client.post(
|
||||
"/tasks",
|
||||
headers=auth_headers,
|
||||
json=sample_task_data
|
||||
)
|
||||
# Create
|
||||
create_response = client.post(
|
||||
"/tasks",
|
||||
headers=auth_headers,
|
||||
json=sample_task_data
|
||||
)
|
||||
|
||||
# List
|
||||
list_response = client.get("/tasks", headers=auth_headers)
|
||||
# List
|
||||
list_response = client.get("/tasks", headers=auth_headers)
|
||||
|
||||
# Delete
|
||||
delete_response = client.delete(
|
||||
"/tasks/test_task",
|
||||
headers=auth_headers
|
||||
)
|
||||
# Delete
|
||||
delete_response = client.delete(
|
||||
"/tasks/test_task",
|
||||
headers=auth_headers
|
||||
)
|
||||
|
||||
# Verify the flow worked
|
||||
assert create_response.status_code in [200, 500] # May fail on DB issues
|
||||
assert list_response.status_code in [200, 500]
|
||||
assert delete_response.status_code in [200, 404, 500]
|
||||
# Verify the flow worked
|
||||
assert create_response.status_code in [200, 500] # May fail on DB issues
|
||||
assert list_response.status_code in [200, 500]
|
||||
assert delete_response.status_code in [200, 404, 500]
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
|
||||
Reference in New Issue
Block a user