feat: complete LOW priority code quality improvements
Backend: - LOW-002: Add Query validation with max page size limits (100) - LOW-003: Replace magic strings with TaskStatus.is_done flag - LOW-004: Add 'creation' trigger type validation - Add action_executor.py with UpdateFieldAction and AutoAssignAction Frontend: - LOW-005: Replace TypeScript 'any' with 'unknown' + type guards - LOW-006: Add ConfirmModal component with A11Y support - LOW-007: Add ToastContext for user feedback notifications - LOW-009: Add Skeleton components (17 loading states replaced) - LOW-010: Setup Vitest with 21 tests for ConfirmModal and Skeleton Components updated: - App.tsx, ProtectedRoute.tsx, Spaces.tsx, Projects.tsx, Tasks.tsx - ProjectSettings.tsx, AuditPage.tsx, WorkloadPage.tsx, ProjectHealthPage.tsx - Comments.tsx, AttachmentList.tsx, TriggerList.tsx, TaskDetailModal.tsx - NotificationBell.tsx, BlockerDialog.tsx, CalendarView.tsx, WorkloadUserDetail.tsx 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -375,3 +375,119 @@ class TestTriggerAPI:
|
||||
|
||||
assert response.status_code == 400
|
||||
assert "Invalid operator" in response.json()["detail"]
|
||||
|
||||
|
||||
class TestTriggerActionRollback:
|
||||
"""Tests for trigger action rollback mechanism."""
|
||||
|
||||
def test_multi_action_rollback_on_failure(self, db, test_task, test_project, test_user, test_status):
|
||||
"""Test that when one action fails, all previous actions are rolled back.
|
||||
|
||||
Scenario:
|
||||
1. Create a trigger with 2 actions: update_field (priority) + update_field (invalid status_id)
|
||||
2. The first action should update priority to 'high'
|
||||
3. The second action should fail because of invalid status_id
|
||||
4. The first action's change should be rolled back
|
||||
"""
|
||||
# Record original priority
|
||||
original_priority = test_task.priority
|
||||
|
||||
# Create a trigger with multiple actions where the second one will fail
|
||||
trigger = Trigger(
|
||||
id=str(uuid.uuid4()),
|
||||
project_id=test_project.id,
|
||||
name="Multi-Action Trigger",
|
||||
description="Test rollback on failure",
|
||||
trigger_type="field_change",
|
||||
conditions={
|
||||
"field": "status_id",
|
||||
"operator": "changed_to",
|
||||
"value": test_status[1].id,
|
||||
},
|
||||
actions=[
|
||||
# First action: update priority (should succeed)
|
||||
{
|
||||
"type": "update_field",
|
||||
"field": "priority",
|
||||
"value": "high",
|
||||
},
|
||||
# Second action: update to invalid status (should fail)
|
||||
{
|
||||
"type": "update_field",
|
||||
"field": "status_id",
|
||||
"value": "non-existent-status-id",
|
||||
},
|
||||
],
|
||||
is_active=True,
|
||||
created_by=test_user.id,
|
||||
)
|
||||
db.add(trigger)
|
||||
db.commit()
|
||||
|
||||
old_values = {"status_id": test_status[0].id}
|
||||
new_values = {"status_id": test_status[1].id}
|
||||
|
||||
# Execute trigger - second action should fail
|
||||
logs = TriggerService.evaluate_triggers(db, test_task, old_values, new_values, test_user)
|
||||
db.commit()
|
||||
|
||||
# Verify trigger execution failed
|
||||
assert len(logs) == 1
|
||||
assert logs[0].status == "failed"
|
||||
assert logs[0].error_message is not None
|
||||
assert "not found" in logs[0].error_message.lower()
|
||||
|
||||
# Refresh task from database to get the actual state
|
||||
db.refresh(test_task)
|
||||
|
||||
# Verify that the first action's change was rolled back
|
||||
# Priority should remain unchanged (not 'high')
|
||||
assert test_task.priority == original_priority, (
|
||||
f"Expected priority to be rolled back to '{original_priority}', "
|
||||
f"but got '{test_task.priority}'"
|
||||
)
|
||||
|
||||
def test_all_actions_succeed_committed(self, db, test_task, test_project, test_user, test_status):
|
||||
"""Test that when all actions succeed, changes are committed."""
|
||||
# Create a trigger with multiple successful actions
|
||||
trigger = Trigger(
|
||||
id=str(uuid.uuid4()),
|
||||
project_id=test_project.id,
|
||||
name="Success Trigger",
|
||||
description="Test successful commit",
|
||||
trigger_type="field_change",
|
||||
conditions={
|
||||
"field": "status_id",
|
||||
"operator": "changed_to",
|
||||
"value": test_status[1].id,
|
||||
},
|
||||
actions=[
|
||||
# First action: update priority
|
||||
{
|
||||
"type": "update_field",
|
||||
"field": "priority",
|
||||
"value": "urgent",
|
||||
},
|
||||
],
|
||||
is_active=True,
|
||||
created_by=test_user.id,
|
||||
)
|
||||
db.add(trigger)
|
||||
db.commit()
|
||||
|
||||
old_values = {"status_id": test_status[0].id}
|
||||
new_values = {"status_id": test_status[1].id}
|
||||
|
||||
# Execute trigger
|
||||
logs = TriggerService.evaluate_triggers(db, test_task, old_values, new_values, test_user)
|
||||
db.commit()
|
||||
|
||||
# Verify trigger execution succeeded
|
||||
assert len(logs) == 1
|
||||
assert logs[0].status == "success"
|
||||
|
||||
# Refresh task from database
|
||||
db.refresh(test_task)
|
||||
|
||||
# Verify that the change was committed
|
||||
assert test_task.priority == "urgent"
|
||||
|
||||
Reference in New Issue
Block a user