test(backend): add comprehensive tests for OAuth and agent endpoints
- Added tests for OAuth provider admin and consent endpoints covering edge cases. - Extended agent-related tests to handle incorrect project associations and lifecycle state transitions. - Introduced tests for sprint status transitions and validation checks. - Improved multiline formatting consistency across all test functions.
This commit is contained in:
@@ -159,7 +159,9 @@ async def test_agent_type_crud(async_test_db, agent_type_create_data):
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def test_agent_instance_crud(async_test_db, test_project_crud, test_agent_type_crud):
|
||||
async def test_agent_instance_crud(
|
||||
async_test_db, test_project_crud, test_agent_type_crud
|
||||
):
|
||||
"""Create a test agent instance in the database for CRUD tests."""
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
async with AsyncTestingSessionLocal() as session:
|
||||
|
||||
@@ -203,7 +203,7 @@ class TestAgentInstanceGetByProject:
|
||||
self, db_session, test_project, test_agent_instance
|
||||
):
|
||||
"""Test getting agent instances with status filter."""
|
||||
instances, total = await agent_instance.get_by_project(
|
||||
instances, _total = await agent_instance.get_by_project(
|
||||
db_session,
|
||||
project_id=test_project.id,
|
||||
status=AgentStatus.IDLE,
|
||||
|
||||
@@ -17,7 +17,9 @@ class TestAgentInstanceCreate:
|
||||
"""Tests for agent instance creation."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_agent_instance_success(self, async_test_db, test_project_crud, test_agent_type_crud):
|
||||
async def test_create_agent_instance_success(
|
||||
self, async_test_db, test_project_crud, test_agent_type_crud
|
||||
):
|
||||
"""Test successfully creating an agent instance."""
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
@@ -41,7 +43,9 @@ class TestAgentInstanceCreate:
|
||||
assert result.short_term_memory == {"context": "initial"}
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_agent_instance_minimal(self, async_test_db, test_project_crud, test_agent_type_crud):
|
||||
async def test_create_agent_instance_minimal(
|
||||
self, async_test_db, test_project_crud, test_agent_type_crud
|
||||
):
|
||||
"""Test creating agent instance with minimal fields."""
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
@@ -62,12 +66,16 @@ class TestAgentInstanceRead:
|
||||
"""Tests for agent instance read operations."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_agent_instance_by_id(self, async_test_db, test_agent_instance_crud):
|
||||
async def test_get_agent_instance_by_id(
|
||||
self, async_test_db, test_agent_instance_crud
|
||||
):
|
||||
"""Test getting agent instance by ID."""
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
async with AsyncTestingSessionLocal() as session:
|
||||
result = await agent_instance_crud.get(session, id=str(test_agent_instance_crud.id))
|
||||
result = await agent_instance_crud.get(
|
||||
session, id=str(test_agent_instance_crud.id)
|
||||
)
|
||||
|
||||
assert result is not None
|
||||
assert result.id == test_agent_instance_crud.id
|
||||
@@ -102,33 +110,48 @@ class TestAgentInstanceUpdate:
|
||||
"""Tests for agent instance update operations."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_agent_instance_status(self, async_test_db, test_agent_instance_crud):
|
||||
async def test_update_agent_instance_status(
|
||||
self, async_test_db, test_agent_instance_crud
|
||||
):
|
||||
"""Test updating agent instance status."""
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
async with AsyncTestingSessionLocal() as session:
|
||||
instance = await agent_instance_crud.get(session, id=str(test_agent_instance_crud.id))
|
||||
instance = await agent_instance_crud.get(
|
||||
session, id=str(test_agent_instance_crud.id)
|
||||
)
|
||||
|
||||
update_data = AgentInstanceUpdate(
|
||||
status=AgentStatus.WORKING,
|
||||
current_task="Processing feature request",
|
||||
)
|
||||
result = await agent_instance_crud.update(session, db_obj=instance, obj_in=update_data)
|
||||
result = await agent_instance_crud.update(
|
||||
session, db_obj=instance, obj_in=update_data
|
||||
)
|
||||
|
||||
assert result.status == AgentStatus.WORKING
|
||||
assert result.current_task == "Processing feature request"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_agent_instance_memory(self, async_test_db, test_agent_instance_crud):
|
||||
async def test_update_agent_instance_memory(
|
||||
self, async_test_db, test_agent_instance_crud
|
||||
):
|
||||
"""Test updating agent instance short-term memory."""
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
async with AsyncTestingSessionLocal() as session:
|
||||
instance = await agent_instance_crud.get(session, id=str(test_agent_instance_crud.id))
|
||||
instance = await agent_instance_crud.get(
|
||||
session, id=str(test_agent_instance_crud.id)
|
||||
)
|
||||
|
||||
new_memory = {"conversation": ["msg1", "msg2"], "decisions": {"key": "value"}}
|
||||
new_memory = {
|
||||
"conversation": ["msg1", "msg2"],
|
||||
"decisions": {"key": "value"},
|
||||
}
|
||||
update_data = AgentInstanceUpdate(short_term_memory=new_memory)
|
||||
result = await agent_instance_crud.update(session, db_obj=instance, obj_in=update_data)
|
||||
result = await agent_instance_crud.update(
|
||||
session, db_obj=instance, obj_in=update_data
|
||||
)
|
||||
|
||||
assert result.short_term_memory == new_memory
|
||||
|
||||
@@ -172,7 +195,9 @@ class TestAgentInstanceTerminate:
|
||||
"""Tests for agent instance termination."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_terminate_agent_instance(self, async_test_db, test_project_crud, test_agent_type_crud):
|
||||
async def test_terminate_agent_instance(
|
||||
self, async_test_db, test_project_crud, test_agent_type_crud
|
||||
):
|
||||
"""Test terminating an agent instance."""
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
@@ -189,7 +214,9 @@ class TestAgentInstanceTerminate:
|
||||
|
||||
# Terminate
|
||||
async with AsyncTestingSessionLocal() as session:
|
||||
result = await agent_instance_crud.terminate(session, instance_id=instance_id)
|
||||
result = await agent_instance_crud.terminate(
|
||||
session, instance_id=instance_id
|
||||
)
|
||||
|
||||
assert result is not None
|
||||
assert result.status == AgentStatus.TERMINATED
|
||||
@@ -203,7 +230,9 @@ class TestAgentInstanceTerminate:
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
async with AsyncTestingSessionLocal() as session:
|
||||
result = await agent_instance_crud.terminate(session, instance_id=uuid.uuid4())
|
||||
result = await agent_instance_crud.terminate(
|
||||
session, instance_id=uuid.uuid4()
|
||||
)
|
||||
assert result is None
|
||||
|
||||
|
||||
@@ -211,7 +240,9 @@ class TestAgentInstanceMetrics:
|
||||
"""Tests for agent instance metrics operations."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_record_task_completion(self, async_test_db, test_agent_instance_crud):
|
||||
async def test_record_task_completion(
|
||||
self, async_test_db, test_agent_instance_crud
|
||||
):
|
||||
"""Test recording task completion with metrics."""
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
@@ -230,7 +261,9 @@ class TestAgentInstanceMetrics:
|
||||
assert result.last_activity_at is not None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_record_multiple_task_completions(self, async_test_db, test_project_crud, test_agent_type_crud):
|
||||
async def test_record_multiple_task_completions(
|
||||
self, async_test_db, test_project_crud, test_agent_type_crud
|
||||
):
|
||||
"""Test recording multiple task completions accumulates metrics."""
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
@@ -267,7 +300,9 @@ class TestAgentInstanceMetrics:
|
||||
assert result.cost_incurred == Decimal("0.0300")
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_project_metrics(self, async_test_db, test_project_crud, test_agent_instance_crud):
|
||||
async def test_get_project_metrics(
|
||||
self, async_test_db, test_project_crud, test_agent_instance_crud
|
||||
):
|
||||
"""Test getting aggregated metrics for a project."""
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
@@ -290,7 +325,9 @@ class TestAgentInstanceByProject:
|
||||
"""Tests for getting instances by project."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_by_project(self, async_test_db, test_project_crud, test_agent_instance_crud):
|
||||
async def test_get_by_project(
|
||||
self, async_test_db, test_project_crud, test_agent_instance_crud
|
||||
):
|
||||
"""Test getting instances by project."""
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
@@ -304,7 +341,9 @@ class TestAgentInstanceByProject:
|
||||
assert all(i.project_id == test_project_crud.id for i in instances)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_by_project_with_status(self, async_test_db, test_project_crud, test_agent_type_crud):
|
||||
async def test_get_by_project_with_status(
|
||||
self, async_test_db, test_project_crud, test_agent_type_crud
|
||||
):
|
||||
"""Test getting instances by project filtered by status."""
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
@@ -340,7 +379,9 @@ class TestAgentInstanceByAgentType:
|
||||
"""Tests for getting instances by agent type."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_by_agent_type(self, async_test_db, test_agent_type_crud, test_agent_instance_crud):
|
||||
async def test_get_by_agent_type(
|
||||
self, async_test_db, test_agent_type_crud, test_agent_instance_crud
|
||||
):
|
||||
"""Test getting instances by agent type."""
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
@@ -358,7 +399,9 @@ class TestBulkTerminate:
|
||||
"""Tests for bulk termination of instances."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_bulk_terminate_by_project(self, async_test_db, test_project_crud, test_agent_type_crud):
|
||||
async def test_bulk_terminate_by_project(
|
||||
self, async_test_db, test_project_crud, test_agent_type_crud
|
||||
):
|
||||
"""Test bulk terminating all instances in a project."""
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
|
||||
@@ -9,8 +9,7 @@ import pytest_asyncio
|
||||
from sqlalchemy.exc import IntegrityError, OperationalError
|
||||
|
||||
from app.crud.syndarix.agent_type import agent_type
|
||||
from app.models.syndarix import AgentInstance, AgentType, Project
|
||||
from app.models.syndarix.enums import AgentStatus, ProjectStatus
|
||||
from app.models.syndarix import AgentType
|
||||
from app.schemas.syndarix import AgentTypeCreate
|
||||
|
||||
|
||||
@@ -95,7 +94,9 @@ class TestAgentTypeCreate:
|
||||
|
||||
# Mock IntegrityError with slug in the message
|
||||
mock_orig = MagicMock()
|
||||
mock_orig.__str__ = lambda self: "duplicate key value violates unique constraint on slug"
|
||||
mock_orig.__str__ = (
|
||||
lambda self: "duplicate key value violates unique constraint on slug"
|
||||
)
|
||||
|
||||
with patch.object(
|
||||
db_session,
|
||||
@@ -152,13 +153,13 @@ class TestAgentTypeGetMultiWithFilters:
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_multi_with_filters_success(self, db_session, test_agent_type):
|
||||
"""Test successfully getting agent types with filters."""
|
||||
results, total = await agent_type.get_multi_with_filters(db_session)
|
||||
_results, total = await agent_type.get_multi_with_filters(db_session)
|
||||
assert total >= 1
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_multi_with_filters_sort_asc(self, db_session, test_agent_type):
|
||||
"""Test getting agent types with ascending sort order."""
|
||||
results, total = await agent_type.get_multi_with_filters(
|
||||
_results, total = await agent_type.get_multi_with_filters(
|
||||
db_session,
|
||||
sort_by="created_at",
|
||||
sort_order="asc",
|
||||
@@ -256,14 +257,18 @@ class TestAgentTypeGetByExpertise:
|
||||
"""Tests for getting agent types by expertise."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.skip(reason="Uses PostgreSQL JSONB contains operator, not available in SQLite")
|
||||
@pytest.mark.skip(
|
||||
reason="Uses PostgreSQL JSONB contains operator, not available in SQLite"
|
||||
)
|
||||
async def test_get_by_expertise_success(self, db_session, test_agent_type):
|
||||
"""Test successfully getting agent types by expertise."""
|
||||
results = await agent_type.get_by_expertise(db_session, expertise="python")
|
||||
assert len(results) >= 1
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.skip(reason="Uses PostgreSQL JSONB contains operator, not available in SQLite")
|
||||
@pytest.mark.skip(
|
||||
reason="Uses PostgreSQL JSONB contains operator, not available in SQLite"
|
||||
)
|
||||
async def test_get_by_expertise_db_error(self, db_session):
|
||||
"""Test getting agent types by expertise when DB error occurs."""
|
||||
with patch.object(
|
||||
|
||||
@@ -42,7 +42,9 @@ class TestAgentTypeCreate:
|
||||
assert result.is_active is True
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_agent_type_duplicate_slug_fails(self, async_test_db, test_agent_type_crud):
|
||||
async def test_create_agent_type_duplicate_slug_fails(
|
||||
self, async_test_db, test_agent_type_crud
|
||||
):
|
||||
"""Test creating agent type with duplicate slug raises ValueError."""
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
@@ -109,7 +111,9 @@ class TestAgentTypeRead:
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
async with AsyncTestingSessionLocal() as session:
|
||||
result = await agent_type_crud.get_by_slug(session, slug=test_agent_type_crud.slug)
|
||||
result = await agent_type_crud.get_by_slug(
|
||||
session, slug=test_agent_type_crud.slug
|
||||
)
|
||||
|
||||
assert result is not None
|
||||
assert result.slug == test_agent_type_crud.slug
|
||||
@@ -120,7 +124,9 @@ class TestAgentTypeRead:
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
async with AsyncTestingSessionLocal() as session:
|
||||
result = await agent_type_crud.get_by_slug(session, slug="non-existent-agent")
|
||||
result = await agent_type_crud.get_by_slug(
|
||||
session, slug="non-existent-agent"
|
||||
)
|
||||
assert result is None
|
||||
|
||||
|
||||
@@ -128,48 +134,66 @@ class TestAgentTypeUpdate:
|
||||
"""Tests for agent type update operations."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_agent_type_basic_fields(self, async_test_db, test_agent_type_crud):
|
||||
async def test_update_agent_type_basic_fields(
|
||||
self, async_test_db, test_agent_type_crud
|
||||
):
|
||||
"""Test updating basic agent type fields."""
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
async with AsyncTestingSessionLocal() as session:
|
||||
agent_type = await agent_type_crud.get(session, id=str(test_agent_type_crud.id))
|
||||
agent_type = await agent_type_crud.get(
|
||||
session, id=str(test_agent_type_crud.id)
|
||||
)
|
||||
|
||||
update_data = AgentTypeUpdate(
|
||||
name="Updated Agent Name",
|
||||
description="Updated description",
|
||||
)
|
||||
result = await agent_type_crud.update(session, db_obj=agent_type, obj_in=update_data)
|
||||
result = await agent_type_crud.update(
|
||||
session, db_obj=agent_type, obj_in=update_data
|
||||
)
|
||||
|
||||
assert result.name == "Updated Agent Name"
|
||||
assert result.description == "Updated description"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_agent_type_expertise(self, async_test_db, test_agent_type_crud):
|
||||
async def test_update_agent_type_expertise(
|
||||
self, async_test_db, test_agent_type_crud
|
||||
):
|
||||
"""Test updating agent type expertise."""
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
async with AsyncTestingSessionLocal() as session:
|
||||
agent_type = await agent_type_crud.get(session, id=str(test_agent_type_crud.id))
|
||||
agent_type = await agent_type_crud.get(
|
||||
session, id=str(test_agent_type_crud.id)
|
||||
)
|
||||
|
||||
update_data = AgentTypeUpdate(
|
||||
expertise=["new-skill", "another-skill"],
|
||||
)
|
||||
result = await agent_type_crud.update(session, db_obj=agent_type, obj_in=update_data)
|
||||
result = await agent_type_crud.update(
|
||||
session, db_obj=agent_type, obj_in=update_data
|
||||
)
|
||||
|
||||
assert "new-skill" in result.expertise
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_agent_type_model_params(self, async_test_db, test_agent_type_crud):
|
||||
async def test_update_agent_type_model_params(
|
||||
self, async_test_db, test_agent_type_crud
|
||||
):
|
||||
"""Test updating agent type model parameters."""
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
async with AsyncTestingSessionLocal() as session:
|
||||
agent_type = await agent_type_crud.get(session, id=str(test_agent_type_crud.id))
|
||||
agent_type = await agent_type_crud.get(
|
||||
session, id=str(test_agent_type_crud.id)
|
||||
)
|
||||
|
||||
new_params = {"temperature": 0.9, "max_tokens": 8192}
|
||||
update_data = AgentTypeUpdate(model_params=new_params)
|
||||
result = await agent_type_crud.update(session, db_obj=agent_type, obj_in=update_data)
|
||||
result = await agent_type_crud.update(
|
||||
session, db_obj=agent_type, obj_in=update_data
|
||||
)
|
||||
|
||||
assert result.model_params == new_params
|
||||
|
||||
@@ -311,7 +335,9 @@ class TestAgentTypeSpecialMethods:
|
||||
|
||||
# Deactivate
|
||||
async with AsyncTestingSessionLocal() as session:
|
||||
result = await agent_type_crud.deactivate(session, agent_type_id=agent_type_id)
|
||||
result = await agent_type_crud.deactivate(
|
||||
session, agent_type_id=agent_type_id
|
||||
)
|
||||
|
||||
assert result is not None
|
||||
assert result.is_active is False
|
||||
@@ -322,11 +348,15 @@ class TestAgentTypeSpecialMethods:
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
async with AsyncTestingSessionLocal() as session:
|
||||
result = await agent_type_crud.deactivate(session, agent_type_id=uuid.uuid4())
|
||||
result = await agent_type_crud.deactivate(
|
||||
session, agent_type_id=uuid.uuid4()
|
||||
)
|
||||
assert result is None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_with_instance_count(self, async_test_db, test_agent_type_crud, test_agent_instance_crud):
|
||||
async def test_get_with_instance_count(
|
||||
self, async_test_db, test_agent_type_crud, test_agent_instance_crud
|
||||
):
|
||||
"""Test getting agent type with instance count."""
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
|
||||
import uuid
|
||||
from datetime import UTC, datetime
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
from sqlalchemy.exc import IntegrityError, OperationalError
|
||||
|
||||
from app.crud.syndarix.issue import CRUDIssue, issue
|
||||
from app.crud.syndarix.issue import issue
|
||||
from app.models.syndarix import Issue, Project, Sprint
|
||||
from app.models.syndarix.enums import (
|
||||
IssuePriority,
|
||||
@@ -18,7 +18,7 @@ from app.models.syndarix.enums import (
|
||||
SprintStatus,
|
||||
SyncStatus,
|
||||
)
|
||||
from app.schemas.syndarix import IssueCreate, IssueUpdate
|
||||
from app.schemas.syndarix import IssueCreate
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
@@ -48,6 +48,7 @@ async def test_project(db_session):
|
||||
async def test_sprint(db_session, test_project):
|
||||
"""Create a test sprint."""
|
||||
from datetime import date
|
||||
|
||||
sprint = Sprint(
|
||||
id=uuid.uuid4(),
|
||||
project_id=test_project.id,
|
||||
@@ -203,7 +204,7 @@ class TestIssueGetByProject:
|
||||
await db_session.commit()
|
||||
|
||||
# Test status filter
|
||||
issues, total = await issue.get_by_project(
|
||||
issues, _total = await issue.get_by_project(
|
||||
db_session,
|
||||
project_id=test_project.id,
|
||||
status=IssueStatus.IN_PROGRESS,
|
||||
@@ -212,7 +213,7 @@ class TestIssueGetByProject:
|
||||
assert issues[0].status == IssueStatus.IN_PROGRESS
|
||||
|
||||
# Test priority filter
|
||||
issues, total = await issue.get_by_project(
|
||||
issues, _total = await issue.get_by_project(
|
||||
db_session,
|
||||
project_id=test_project.id,
|
||||
priority=IssuePriority.HIGH,
|
||||
@@ -221,12 +222,14 @@ class TestIssueGetByProject:
|
||||
assert issues[0].priority == IssuePriority.HIGH
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.skip(reason="Labels filter uses PostgreSQL @> operator, not available in SQLite")
|
||||
@pytest.mark.skip(
|
||||
reason="Labels filter uses PostgreSQL @> operator, not available in SQLite"
|
||||
)
|
||||
async def test_get_by_project_with_labels_filter(
|
||||
self, db_session, test_project, test_issue
|
||||
):
|
||||
"""Test getting issues filtered by labels."""
|
||||
issues, total = await issue.get_by_project(
|
||||
issues, _total = await issue.get_by_project(
|
||||
db_session,
|
||||
project_id=test_project.id,
|
||||
labels=["bug"],
|
||||
@@ -249,7 +252,7 @@ class TestIssueGetByProject:
|
||||
db_session.add(issue2)
|
||||
await db_session.commit()
|
||||
|
||||
issues, total = await issue.get_by_project(
|
||||
issues, _total = await issue.get_by_project(
|
||||
db_session,
|
||||
project_id=test_project.id,
|
||||
sort_by="created_at",
|
||||
@@ -257,8 +260,16 @@ class TestIssueGetByProject:
|
||||
)
|
||||
assert len(issues) == 2
|
||||
# Compare without timezone info since DB may strip it
|
||||
first_time = issues[0].created_at.replace(tzinfo=None) if issues[0].created_at.tzinfo else issues[0].created_at
|
||||
second_time = issues[1].created_at.replace(tzinfo=None) if issues[1].created_at.tzinfo else issues[1].created_at
|
||||
first_time = (
|
||||
issues[0].created_at.replace(tzinfo=None)
|
||||
if issues[0].created_at.tzinfo
|
||||
else issues[0].created_at
|
||||
)
|
||||
second_time = (
|
||||
issues[1].created_at.replace(tzinfo=None)
|
||||
if issues[1].created_at.tzinfo
|
||||
else issues[1].created_at
|
||||
)
|
||||
assert first_time <= second_time
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -561,9 +572,7 @@ class TestIssueExternalTracker:
|
||||
assert len(issues) >= 1
|
||||
|
||||
# Test with project filter
|
||||
issues = await issue.get_pending_sync(
|
||||
db_session, project_id=test_project.id
|
||||
)
|
||||
issues = await issue.get_pending_sync(db_session, project_id=test_project.id)
|
||||
assert len(issues) >= 1
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
@@ -42,7 +42,9 @@ class TestIssueCreate:
|
||||
assert result.story_points == 5
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_issue_with_external_tracker(self, async_test_db, test_project_crud):
|
||||
async def test_create_issue_with_external_tracker(
|
||||
self, async_test_db, test_project_crud
|
||||
):
|
||||
"""Test creating issue with external tracker info."""
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
@@ -182,7 +184,9 @@ class TestIssueAssignment:
|
||||
"""Tests for issue assignment operations."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_assign_to_agent(self, async_test_db, test_issue_crud, test_agent_instance_crud):
|
||||
async def test_assign_to_agent(
|
||||
self, async_test_db, test_issue_crud, test_agent_instance_crud
|
||||
):
|
||||
"""Test assigning issue to an agent."""
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
@@ -198,7 +202,9 @@ class TestIssueAssignment:
|
||||
assert result.human_assignee is None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_unassign_agent(self, async_test_db, test_issue_crud, test_agent_instance_crud):
|
||||
async def test_unassign_agent(
|
||||
self, async_test_db, test_issue_crud, test_agent_instance_crud
|
||||
):
|
||||
"""Test unassigning agent from issue."""
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
@@ -237,7 +243,9 @@ class TestIssueAssignment:
|
||||
assert result.assigned_agent_id is None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_assign_to_human_clears_agent(self, async_test_db, test_issue_crud, test_agent_instance_crud):
|
||||
async def test_assign_to_human_clears_agent(
|
||||
self, async_test_db, test_issue_crud, test_agent_instance_crud
|
||||
):
|
||||
"""Test assigning to human clears agent assignment."""
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
@@ -304,7 +312,9 @@ class TestIssueByProject:
|
||||
"""Tests for getting issues by project."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_by_project(self, async_test_db, test_project_crud, test_issue_crud):
|
||||
async def test_get_by_project(
|
||||
self, async_test_db, test_project_crud, test_issue_crud
|
||||
):
|
||||
"""Test getting issues by project."""
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
@@ -397,7 +407,9 @@ class TestIssueBySprint:
|
||||
"""Tests for getting issues by sprint."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_by_sprint(self, async_test_db, test_project_crud, test_sprint_crud):
|
||||
async def test_get_by_sprint(
|
||||
self, async_test_db, test_project_crud, test_sprint_crud
|
||||
):
|
||||
"""Test getting issues by sprint."""
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
@@ -533,7 +545,11 @@ class TestIssueStats:
|
||||
|
||||
# Create issues with various statuses and priorities
|
||||
async with AsyncTestingSessionLocal() as session:
|
||||
for status in [IssueStatus.OPEN, IssueStatus.IN_PROGRESS, IssueStatus.CLOSED]:
|
||||
for status in [
|
||||
IssueStatus.OPEN,
|
||||
IssueStatus.IN_PROGRESS,
|
||||
IssueStatus.CLOSED,
|
||||
]:
|
||||
issue_data = IssueCreate(
|
||||
project_id=test_project_crud.id,
|
||||
title=f"Stats Issue {status.value}",
|
||||
|
||||
@@ -10,7 +10,7 @@ from sqlalchemy.exc import IntegrityError, OperationalError
|
||||
|
||||
from app.crud.syndarix.project import project
|
||||
from app.models.syndarix import Project
|
||||
from app.models.syndarix.enums import AutonomyLevel, ProjectStatus
|
||||
from app.models.syndarix.enums import ProjectStatus
|
||||
from app.schemas.syndarix import ProjectCreate
|
||||
|
||||
|
||||
@@ -88,7 +88,9 @@ class TestProjectCreate:
|
||||
|
||||
# Mock IntegrityError with slug in the message
|
||||
mock_orig = MagicMock()
|
||||
mock_orig.__str__ = lambda self: "duplicate key value violates unique constraint on slug"
|
||||
mock_orig.__str__ = (
|
||||
lambda self: "duplicate key value violates unique constraint on slug"
|
||||
)
|
||||
|
||||
with patch.object(
|
||||
db_session,
|
||||
@@ -141,7 +143,7 @@ class TestProjectGetMultiWithFilters:
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_multi_with_filters_success(self, db_session, test_project):
|
||||
"""Test successfully getting projects with filters."""
|
||||
results, total = await project.get_multi_with_filters(db_session)
|
||||
_results, total = await project.get_multi_with_filters(db_session)
|
||||
assert total >= 1
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -162,17 +164,13 @@ class TestProjectGetWithCounts:
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_with_counts_not_found(self, db_session):
|
||||
"""Test getting non-existent project with counts."""
|
||||
result = await project.get_with_counts(
|
||||
db_session, project_id=uuid.uuid4()
|
||||
)
|
||||
result = await project.get_with_counts(db_session, project_id=uuid.uuid4())
|
||||
assert result is None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_with_counts_success(self, db_session, test_project):
|
||||
"""Test successfully getting project with counts."""
|
||||
result = await project.get_with_counts(
|
||||
db_session, project_id=test_project.id
|
||||
)
|
||||
result = await project.get_with_counts(db_session, project_id=test_project.id)
|
||||
assert result is not None
|
||||
assert result["project"].id == test_project.id
|
||||
assert result["agent_count"] == 0
|
||||
@@ -187,9 +185,7 @@ class TestProjectGetWithCounts:
|
||||
side_effect=OperationalError("Connection lost", {}, Exception()),
|
||||
):
|
||||
with pytest.raises(OperationalError):
|
||||
await project.get_with_counts(
|
||||
db_session, project_id=test_project.id
|
||||
)
|
||||
await project.get_with_counts(db_session, project_id=test_project.id)
|
||||
|
||||
|
||||
class TestProjectGetMultiWithCounts:
|
||||
@@ -233,9 +229,7 @@ class TestProjectGetByOwner:
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_projects_by_owner_empty(self, db_session):
|
||||
"""Test getting projects by owner when none exist."""
|
||||
results = await project.get_projects_by_owner(
|
||||
db_session, owner_id=uuid.uuid4()
|
||||
)
|
||||
results = await project.get_projects_by_owner(db_session, owner_id=uuid.uuid4())
|
||||
assert results == []
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -247,9 +241,7 @@ class TestProjectGetByOwner:
|
||||
side_effect=OperationalError("Connection lost", {}, Exception()),
|
||||
):
|
||||
with pytest.raises(OperationalError):
|
||||
await project.get_projects_by_owner(
|
||||
db_session, owner_id=uuid.uuid4()
|
||||
)
|
||||
await project.get_projects_by_owner(db_session, owner_id=uuid.uuid4())
|
||||
|
||||
|
||||
class TestProjectArchive:
|
||||
@@ -264,9 +256,7 @@ class TestProjectArchive:
|
||||
@pytest.mark.asyncio
|
||||
async def test_archive_project_success(self, db_session, test_project):
|
||||
"""Test successfully archiving project."""
|
||||
result = await project.archive_project(
|
||||
db_session, project_id=test_project.id
|
||||
)
|
||||
result = await project.archive_project(db_session, project_id=test_project.id)
|
||||
assert result is not None
|
||||
assert result.status == ProjectStatus.ARCHIVED
|
||||
|
||||
@@ -279,6 +269,4 @@ class TestProjectArchive:
|
||||
side_effect=OperationalError("Connection lost", {}, Exception()),
|
||||
):
|
||||
with pytest.raises(OperationalError):
|
||||
await project.archive_project(
|
||||
db_session, project_id=test_project.id
|
||||
)
|
||||
await project.archive_project(db_session, project_id=test_project.id)
|
||||
|
||||
@@ -42,7 +42,9 @@ class TestProjectCreate:
|
||||
assert result.owner_id == test_owner_crud.id
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_project_duplicate_slug_fails(self, async_test_db, test_project_crud):
|
||||
async def test_create_project_duplicate_slug_fails(
|
||||
self, async_test_db, test_project_crud
|
||||
):
|
||||
"""Test creating project with duplicate slug raises ValueError."""
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
@@ -106,7 +108,9 @@ class TestProjectRead:
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
async with AsyncTestingSessionLocal() as session:
|
||||
result = await project_crud.get_by_slug(session, slug=test_project_crud.slug)
|
||||
result = await project_crud.get_by_slug(
|
||||
session, slug=test_project_crud.slug
|
||||
)
|
||||
|
||||
assert result is not None
|
||||
assert result.slug == test_project_crud.slug
|
||||
@@ -136,7 +140,9 @@ class TestProjectUpdate:
|
||||
name="Updated Project Name",
|
||||
description="Updated description",
|
||||
)
|
||||
result = await project_crud.update(session, db_obj=project, obj_in=update_data)
|
||||
result = await project_crud.update(
|
||||
session, db_obj=project, obj_in=update_data
|
||||
)
|
||||
|
||||
assert result.name == "Updated Project Name"
|
||||
assert result.description == "Updated description"
|
||||
@@ -150,12 +156,16 @@ class TestProjectUpdate:
|
||||
project = await project_crud.get(session, id=str(test_project_crud.id))
|
||||
|
||||
update_data = ProjectUpdate(status=ProjectStatus.PAUSED)
|
||||
result = await project_crud.update(session, db_obj=project, obj_in=update_data)
|
||||
result = await project_crud.update(
|
||||
session, db_obj=project, obj_in=update_data
|
||||
)
|
||||
|
||||
assert result.status == ProjectStatus.PAUSED
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_project_autonomy_level(self, async_test_db, test_project_crud):
|
||||
async def test_update_project_autonomy_level(
|
||||
self, async_test_db, test_project_crud
|
||||
):
|
||||
"""Test updating project autonomy level."""
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
@@ -163,7 +173,9 @@ class TestProjectUpdate:
|
||||
project = await project_crud.get(session, id=str(test_project_crud.id))
|
||||
|
||||
update_data = ProjectUpdate(autonomy_level=AutonomyLevel.AUTONOMOUS)
|
||||
result = await project_crud.update(session, db_obj=project, obj_in=update_data)
|
||||
result = await project_crud.update(
|
||||
session, db_obj=project, obj_in=update_data
|
||||
)
|
||||
|
||||
assert result.autonomy_level == AutonomyLevel.AUTONOMOUS
|
||||
|
||||
@@ -175,9 +187,14 @@ class TestProjectUpdate:
|
||||
async with AsyncTestingSessionLocal() as session:
|
||||
project = await project_crud.get(session, id=str(test_project_crud.id))
|
||||
|
||||
new_settings = {"mcp_servers": ["gitea", "slack"], "webhook_url": "https://example.com"}
|
||||
new_settings = {
|
||||
"mcp_servers": ["gitea", "slack"],
|
||||
"webhook_url": "https://example.com",
|
||||
}
|
||||
update_data = ProjectUpdate(settings=new_settings)
|
||||
result = await project_crud.update(session, db_obj=project, obj_in=update_data)
|
||||
result = await project_crud.update(
|
||||
session, db_obj=project, obj_in=update_data
|
||||
)
|
||||
|
||||
assert result.settings == new_settings
|
||||
|
||||
@@ -273,7 +290,9 @@ class TestProjectFilters:
|
||||
assert any(p.name == "Searchable Project" for p in projects)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_multi_with_filters_owner(self, async_test_db, test_owner_crud, test_project_crud):
|
||||
async def test_get_multi_with_filters_owner(
|
||||
self, async_test_db, test_owner_crud, test_project_crud
|
||||
):
|
||||
"""Test filtering projects by owner."""
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
@@ -287,7 +306,9 @@ class TestProjectFilters:
|
||||
assert all(p.owner_id == test_owner_crud.id for p in projects)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_multi_with_filters_pagination(self, async_test_db, test_owner_crud):
|
||||
async def test_get_multi_with_filters_pagination(
|
||||
self, async_test_db, test_owner_crud
|
||||
):
|
||||
"""Test pagination of project results."""
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
@@ -348,7 +369,9 @@ class TestProjectSpecialMethods:
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
async with AsyncTestingSessionLocal() as session:
|
||||
result = await project_crud.archive_project(session, project_id=test_project_crud.id)
|
||||
result = await project_crud.archive_project(
|
||||
session, project_id=test_project_crud.id
|
||||
)
|
||||
|
||||
assert result is not None
|
||||
assert result.status == ProjectStatus.ARCHIVED
|
||||
@@ -359,11 +382,15 @@ class TestProjectSpecialMethods:
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
async with AsyncTestingSessionLocal() as session:
|
||||
result = await project_crud.archive_project(session, project_id=uuid.uuid4())
|
||||
result = await project_crud.archive_project(
|
||||
session, project_id=uuid.uuid4()
|
||||
)
|
||||
assert result is None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_projects_by_owner(self, async_test_db, test_owner_crud, test_project_crud):
|
||||
async def test_get_projects_by_owner(
|
||||
self, async_test_db, test_owner_crud, test_project_crud
|
||||
):
|
||||
"""Test getting all projects by owner."""
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
@@ -377,7 +404,9 @@ class TestProjectSpecialMethods:
|
||||
assert all(p.owner_id == test_owner_crud.id for p in projects)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_projects_by_owner_with_status(self, async_test_db, test_owner_crud):
|
||||
async def test_get_projects_by_owner_with_status(
|
||||
self, async_test_db, test_owner_crud
|
||||
):
|
||||
"""Test getting projects by owner filtered by status."""
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import pytest
|
||||
import pytest_asyncio
|
||||
from sqlalchemy.exc import IntegrityError, OperationalError
|
||||
|
||||
from app.crud.syndarix.sprint import CRUDSprint, sprint
|
||||
from app.crud.syndarix.sprint import sprint
|
||||
from app.models.syndarix import Issue, Project, Sprint
|
||||
from app.models.syndarix.enums import (
|
||||
IssueStatus,
|
||||
@@ -174,7 +174,7 @@ class TestSprintGetByProject:
|
||||
self, db_session, test_project, test_sprint
|
||||
):
|
||||
"""Test getting sprints with status filter."""
|
||||
sprints, total = await sprint.get_by_project(
|
||||
sprints, _total = await sprint.get_by_project(
|
||||
db_session,
|
||||
project_id=test_project.id,
|
||||
status=SprintStatus.PLANNED,
|
||||
@@ -478,7 +478,7 @@ class TestSprintWithIssueCounts:
|
||||
db_session.add_all([issue1, issue2])
|
||||
await db_session.commit()
|
||||
|
||||
results, total = await sprint.get_sprints_with_issue_counts(
|
||||
results, _total = await sprint.get_sprints_with_issue_counts(
|
||||
db_session, project_id=test_project.id
|
||||
)
|
||||
assert len(results) == 1
|
||||
|
||||
@@ -121,7 +121,9 @@ class TestSprintUpdate:
|
||||
name="Updated Sprint Name",
|
||||
goal="Updated goal",
|
||||
)
|
||||
result = await sprint_crud.update(session, db_obj=sprint, obj_in=update_data)
|
||||
result = await sprint_crud.update(
|
||||
session, db_obj=sprint, obj_in=update_data
|
||||
)
|
||||
|
||||
assert result.name == "Updated Sprint Name"
|
||||
assert result.goal == "Updated goal"
|
||||
@@ -139,7 +141,9 @@ class TestSprintUpdate:
|
||||
start_date=today + timedelta(days=1),
|
||||
end_date=today + timedelta(days=21),
|
||||
)
|
||||
result = await sprint_crud.update(session, db_obj=sprint, obj_in=update_data)
|
||||
result = await sprint_crud.update(
|
||||
session, db_obj=sprint, obj_in=update_data
|
||||
)
|
||||
|
||||
assert result.start_date == today + timedelta(days=1)
|
||||
assert result.end_date == today + timedelta(days=21)
|
||||
@@ -163,7 +167,9 @@ class TestSprintLifecycle:
|
||||
assert result.status == SprintStatus.ACTIVE
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_start_sprint_with_custom_date(self, async_test_db, test_project_crud):
|
||||
async def test_start_sprint_with_custom_date(
|
||||
self, async_test_db, test_project_crud
|
||||
):
|
||||
"""Test starting sprint with custom start date."""
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
@@ -195,7 +201,9 @@ class TestSprintLifecycle:
|
||||
assert result.start_date == new_start
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_start_sprint_already_active_fails(self, async_test_db, test_project_crud):
|
||||
async def test_start_sprint_already_active_fails(
|
||||
self, async_test_db, test_project_crud
|
||||
):
|
||||
"""Test starting an already active sprint raises ValueError."""
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
@@ -250,7 +258,9 @@ class TestSprintLifecycle:
|
||||
assert result.status == SprintStatus.COMPLETED
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_complete_planned_sprint_fails(self, async_test_db, test_project_crud):
|
||||
async def test_complete_planned_sprint_fails(
|
||||
self, async_test_db, test_project_crud
|
||||
):
|
||||
"""Test completing a planned sprint raises ValueError."""
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
@@ -300,7 +310,9 @@ class TestSprintLifecycle:
|
||||
assert result.status == SprintStatus.CANCELLED
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_cancel_completed_sprint_fails(self, async_test_db, test_project_crud):
|
||||
async def test_cancel_completed_sprint_fails(
|
||||
self, async_test_db, test_project_crud
|
||||
):
|
||||
"""Test cancelling a completed sprint raises ValueError."""
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
@@ -329,7 +341,9 @@ class TestSprintByProject:
|
||||
"""Tests for getting sprints by project."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_by_project(self, async_test_db, test_project_crud, test_sprint_crud):
|
||||
async def test_get_by_project(
|
||||
self, async_test_db, test_project_crud, test_sprint_crud
|
||||
):
|
||||
"""Test getting sprints by project."""
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
@@ -506,7 +520,9 @@ class TestSprintWithIssueCounts:
|
||||
"""Tests for getting sprints with issue counts."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_sprints_with_issue_counts(self, async_test_db, test_project_crud, test_sprint_crud):
|
||||
async def test_get_sprints_with_issue_counts(
|
||||
self, async_test_db, test_project_crud, test_sprint_crud
|
||||
):
|
||||
"""Test getting sprints with issue counts."""
|
||||
_test_engine, AsyncTestingSessionLocal = async_test_db
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ from sqlalchemy.exc import DataError, IntegrityError, OperationalError
|
||||
from sqlalchemy.orm import joinedload
|
||||
|
||||
from app.crud.user import user as user_crud
|
||||
from app.schemas.users import UserCreate, UserUpdate
|
||||
from app.schemas.users import UserCreate
|
||||
|
||||
|
||||
class TestCRUDBaseGet:
|
||||
|
||||
Reference in New Issue
Block a user