fix: Comprehensive validation and bug fixes
Infrastructure: - Add Redis and Celery workers to all docker-compose files - Fix celery migration race condition in entrypoint.sh - Add healthchecks and resource limits to dev compose - Update .env.template with Redis/Celery variables Backend Models & Schemas: - Rename Sprint.completed_points to velocity (per requirements) - Add AgentInstance.name as required field - Rename Issue external tracker fields for consistency - Add IssueSource and TrackerType enums - Add Project.default_tracker_type field Backend Fixes: - Add Celery retry configuration with exponential backoff - Remove unused sequence counter from EventBus - Add mypy overrides for test dependencies - Fix test file using wrong schema (UserUpdate -> dict) Frontend Fixes: - Fix memory leak in useProjectEvents (proper cleanup) - Fix race condition with stale closure in reconnection - Sync TokenWithUser type with regenerated API client - Fix expires_in null handling in useAuth - Clean up unused imports in prototype pages - Add ESLint relaxed rules for prototype files CI/CD: - Add E2E testing stage with Testcontainers - Add security scanning with Trivy and pip-audit - Add dependency caching for faster builds Tests: - Update all tests to use renamed fields (velocity, name, etc.) - Fix 14 schema test failures - All 1500 tests pass with 91% coverage 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -22,15 +22,11 @@ from app.models.syndarix import (
|
||||
ProjectStatus,
|
||||
Sprint,
|
||||
SprintStatus,
|
||||
SyncStatus,
|
||||
)
|
||||
from app.models.user import User
|
||||
from app.schemas.syndarix import (
|
||||
AgentInstanceCreate,
|
||||
AgentTypeCreate,
|
||||
IssueCreate,
|
||||
ProjectCreate,
|
||||
SprintCreate,
|
||||
)
|
||||
|
||||
|
||||
@@ -77,7 +73,7 @@ def sprint_create_data():
|
||||
"end_date": today + timedelta(days=14),
|
||||
"status": SprintStatus.PLANNED,
|
||||
"planned_points": 21,
|
||||
"completed_points": 0,
|
||||
"velocity": 0,
|
||||
}
|
||||
|
||||
|
||||
@@ -171,6 +167,7 @@ async def test_agent_instance_crud(async_test_db, test_project_crud, test_agent_
|
||||
id=uuid.uuid4(),
|
||||
agent_type_id=test_agent_type_crud.id,
|
||||
project_id=test_project_crud.id,
|
||||
name="TestAgent",
|
||||
status=AgentStatus.IDLE,
|
||||
current_task=None,
|
||||
short_term_memory={},
|
||||
|
||||
@@ -25,6 +25,7 @@ class TestAgentInstanceCreate:
|
||||
instance_data = AgentInstanceCreate(
|
||||
agent_type_id=test_agent_type_crud.id,
|
||||
project_id=test_project_crud.id,
|
||||
name="TestBot",
|
||||
status=AgentStatus.IDLE,
|
||||
current_task=None,
|
||||
short_term_memory={"context": "initial"},
|
||||
@@ -48,6 +49,7 @@ class TestAgentInstanceCreate:
|
||||
instance_data = AgentInstanceCreate(
|
||||
agent_type_id=test_agent_type_crud.id,
|
||||
project_id=test_project_crud.id,
|
||||
name="MinimalBot",
|
||||
)
|
||||
result = await agent_instance_crud.create(session, obj_in=instance_data)
|
||||
|
||||
@@ -179,6 +181,7 @@ class TestAgentInstanceTerminate:
|
||||
instance_data = AgentInstanceCreate(
|
||||
agent_type_id=test_agent_type_crud.id,
|
||||
project_id=test_project_crud.id,
|
||||
name="TerminateBot",
|
||||
status=AgentStatus.WORKING,
|
||||
)
|
||||
created = await agent_instance_crud.create(session, obj_in=instance_data)
|
||||
@@ -236,6 +239,7 @@ class TestAgentInstanceMetrics:
|
||||
instance_data = AgentInstanceCreate(
|
||||
agent_type_id=test_agent_type_crud.id,
|
||||
project_id=test_project_crud.id,
|
||||
name="MetricsBot",
|
||||
)
|
||||
created = await agent_instance_crud.create(session, obj_in=instance_data)
|
||||
instance_id = created.id
|
||||
@@ -309,6 +313,7 @@ class TestAgentInstanceByProject:
|
||||
idle_instance = AgentInstanceCreate(
|
||||
agent_type_id=test_agent_type_crud.id,
|
||||
project_id=test_project_crud.id,
|
||||
name="IdleBot",
|
||||
status=AgentStatus.IDLE,
|
||||
)
|
||||
await agent_instance_crud.create(session, obj_in=idle_instance)
|
||||
@@ -316,12 +321,13 @@ class TestAgentInstanceByProject:
|
||||
working_instance = AgentInstanceCreate(
|
||||
agent_type_id=test_agent_type_crud.id,
|
||||
project_id=test_project_crud.id,
|
||||
name="WorkerBot",
|
||||
status=AgentStatus.WORKING,
|
||||
)
|
||||
await agent_instance_crud.create(session, obj_in=working_instance)
|
||||
|
||||
async with AsyncTestingSessionLocal() as session:
|
||||
instances, total = await agent_instance_crud.get_by_project(
|
||||
instances, _total = await agent_instance_crud.get_by_project(
|
||||
session,
|
||||
project_id=test_project_crud.id,
|
||||
status=AgentStatus.WORKING,
|
||||
@@ -362,6 +368,7 @@ class TestBulkTerminate:
|
||||
instance_data = AgentInstanceCreate(
|
||||
agent_type_id=test_agent_type_crud.id,
|
||||
project_id=test_project_crud.id,
|
||||
name=f"BulkBot-{i}",
|
||||
status=AgentStatus.WORKING if i < 2 else AgentStatus.IDLE,
|
||||
)
|
||||
await agent_instance_crud.create(session, obj_in=instance_data)
|
||||
|
||||
@@ -280,7 +280,7 @@ class TestAgentTypeFilters:
|
||||
await agent_type_crud.create(session, obj_in=agent_type_data)
|
||||
|
||||
async with AsyncTestingSessionLocal() as session:
|
||||
page1, total = await agent_type_crud.get_multi_with_filters(
|
||||
page1, _total = await agent_type_crud.get_multi_with_filters(
|
||||
session,
|
||||
skip=0,
|
||||
limit=2,
|
||||
|
||||
@@ -50,16 +50,16 @@ class TestIssueCreate:
|
||||
issue_data = IssueCreate(
|
||||
project_id=test_project_crud.id,
|
||||
title="External Issue",
|
||||
external_tracker="gitea",
|
||||
external_id="gitea-123",
|
||||
external_url="https://gitea.example.com/issues/123",
|
||||
external_number=123,
|
||||
external_tracker_type="gitea",
|
||||
external_issue_id="gitea-123",
|
||||
remote_url="https://gitea.example.com/issues/123",
|
||||
external_issue_number=123,
|
||||
)
|
||||
result = await issue_crud.create(session, obj_in=issue_data)
|
||||
|
||||
assert result.external_tracker == "gitea"
|
||||
assert result.external_id == "gitea-123"
|
||||
assert result.external_number == 123
|
||||
assert result.external_tracker_type == "gitea"
|
||||
assert result.external_issue_id == "gitea-123"
|
||||
assert result.external_issue_number == 123
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_issue_minimal(self, async_test_db, test_project_crud):
|
||||
@@ -433,8 +433,8 @@ class TestIssueSyncStatus:
|
||||
issue_data = IssueCreate(
|
||||
project_id=test_project_crud.id,
|
||||
title="Sync Status Issue",
|
||||
external_tracker="gitea",
|
||||
external_id="gitea-456",
|
||||
external_tracker_type="gitea",
|
||||
external_issue_id="gitea-456",
|
||||
)
|
||||
created = await issue_crud.create(session, obj_in=issue_data)
|
||||
issue_id = created.id
|
||||
@@ -463,8 +463,8 @@ class TestIssueSyncStatus:
|
||||
issue_data = IssueCreate(
|
||||
project_id=test_project_crud.id,
|
||||
title="Pending Sync Issue",
|
||||
external_tracker="gitea",
|
||||
external_id="gitea-789",
|
||||
external_tracker_type="gitea",
|
||||
external_issue_id="gitea-789",
|
||||
)
|
||||
created = await issue_crud.create(session, obj_in=issue_data)
|
||||
|
||||
@@ -494,20 +494,20 @@ class TestIssueExternalTracker:
|
||||
issue_data = IssueCreate(
|
||||
project_id=test_project_crud.id,
|
||||
title="External ID Issue",
|
||||
external_tracker="github",
|
||||
external_id="github-unique-123",
|
||||
external_tracker_type="github",
|
||||
external_issue_id="github-unique-123",
|
||||
)
|
||||
await issue_crud.create(session, obj_in=issue_data)
|
||||
|
||||
async with AsyncTestingSessionLocal() as session:
|
||||
result = await issue_crud.get_by_external_id(
|
||||
session,
|
||||
external_tracker="github",
|
||||
external_id="github-unique-123",
|
||||
external_tracker_type="github",
|
||||
external_issue_id="github-unique-123",
|
||||
)
|
||||
|
||||
assert result is not None
|
||||
assert result.external_id == "github-unique-123"
|
||||
assert result.external_issue_id == "github-unique-123"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_by_external_id_not_found(self, async_test_db):
|
||||
@@ -517,8 +517,8 @@ class TestIssueExternalTracker:
|
||||
async with AsyncTestingSessionLocal() as session:
|
||||
result = await issue_crud.get_by_external_id(
|
||||
session,
|
||||
external_tracker="gitea",
|
||||
external_id="non-existent",
|
||||
external_tracker_type="gitea",
|
||||
external_issue_id="non-existent",
|
||||
)
|
||||
assert result is None
|
||||
|
||||
|
||||
@@ -242,7 +242,7 @@ class TestProjectFilters:
|
||||
|
||||
# Filter by ACTIVE status
|
||||
async with AsyncTestingSessionLocal() as session:
|
||||
projects, total = await project_crud.get_multi_with_filters(
|
||||
projects, _total = await project_crud.get_multi_with_filters(
|
||||
session,
|
||||
status=ProjectStatus.ACTIVE,
|
||||
)
|
||||
@@ -319,7 +319,7 @@ class TestProjectFilters:
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
async with AsyncTestingSessionLocal() as session:
|
||||
for i, name in enumerate(["Charlie", "Alice", "Bob"]):
|
||||
for _i, name in enumerate(["Charlie", "Alice", "Bob"]):
|
||||
project_data = ProjectCreate(
|
||||
name=name,
|
||||
slug=f"sort-project-{name.lower()}",
|
||||
|
||||
@@ -482,7 +482,7 @@ class TestSprintVelocity:
|
||||
end_date=today - timedelta(days=14 * (i - 1)),
|
||||
status=SprintStatus.COMPLETED,
|
||||
planned_points=20,
|
||||
completed_points=15 + i,
|
||||
velocity=15 + i,
|
||||
)
|
||||
await sprint_crud.create(session, obj_in=sprint_data)
|
||||
|
||||
@@ -498,8 +498,8 @@ class TestSprintVelocity:
|
||||
assert "sprint_number" in data
|
||||
assert "sprint_name" in data
|
||||
assert "planned_points" in data
|
||||
assert "completed_points" in data
|
||||
assert "velocity" in data
|
||||
assert "velocity_ratio" in data
|
||||
|
||||
|
||||
class TestSprintWithIssueCounts:
|
||||
|
||||
Reference in New Issue
Block a user