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:
@@ -233,7 +233,9 @@ class TestListSprints:
|
||||
assert len(data["data"]) == 3
|
||||
assert data["pagination"]["total"] == 3
|
||||
|
||||
async def test_list_sprints_filter_by_status(self, client, user_token, test_project):
|
||||
async def test_list_sprints_filter_by_status(
|
||||
self, client, user_token, test_project
|
||||
):
|
||||
"""Test filtering sprints by status."""
|
||||
project_id = test_project["id"]
|
||||
start_date = date.today()
|
||||
@@ -582,7 +584,9 @@ class TestSprintLifecycle:
|
||||
class TestDeleteSprint:
|
||||
"""Tests for DELETE /api/v1/projects/{project_id}/sprints/{sprint_id} endpoint."""
|
||||
|
||||
async def test_delete_planned_sprint_success(self, client, user_token, test_project):
|
||||
async def test_delete_planned_sprint_success(
|
||||
self, client, user_token, test_project
|
||||
):
|
||||
"""Test deleting a planned sprint."""
|
||||
project_id = test_project["id"]
|
||||
start_date = date.today()
|
||||
@@ -1119,3 +1123,419 @@ class TestSprintCrossProjectValidation:
|
||||
)
|
||||
|
||||
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
class TestSprintStatusTransitions:
|
||||
"""Tests for invalid sprint status transitions."""
|
||||
|
||||
async def test_cancel_completed_sprint(self, client, user_token, test_project):
|
||||
"""Test that cancelling a completed sprint fails."""
|
||||
project_id = test_project["id"]
|
||||
start_date = date.today()
|
||||
end_date = start_date + timedelta(days=14)
|
||||
|
||||
# Create, start, and complete sprint
|
||||
create_response = await client.post(
|
||||
f"/api/v1/projects/{project_id}/sprints",
|
||||
json={
|
||||
"project_id": project_id,
|
||||
"name": "Sprint to Complete Then Cancel",
|
||||
"number": 1,
|
||||
"start_date": start_date.isoformat(),
|
||||
"end_date": end_date.isoformat(),
|
||||
},
|
||||
headers={"Authorization": f"Bearer {user_token}"},
|
||||
)
|
||||
sprint_id = create_response.json()["id"]
|
||||
|
||||
await client.post(
|
||||
f"/api/v1/projects/{project_id}/sprints/{sprint_id}/start",
|
||||
headers={"Authorization": f"Bearer {user_token}"},
|
||||
)
|
||||
await client.post(
|
||||
f"/api/v1/projects/{project_id}/sprints/{sprint_id}/complete",
|
||||
headers={"Authorization": f"Bearer {user_token}"},
|
||||
)
|
||||
|
||||
# Try to cancel completed sprint
|
||||
response = await client.post(
|
||||
f"/api/v1/projects/{project_id}/sprints/{sprint_id}/cancel",
|
||||
headers={"Authorization": f"Bearer {user_token}"},
|
||||
)
|
||||
|
||||
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
|
||||
|
||||
async def test_cancel_already_cancelled_sprint(
|
||||
self, client, user_token, test_project
|
||||
):
|
||||
"""Test that cancelling an already cancelled sprint fails."""
|
||||
project_id = test_project["id"]
|
||||
start_date = date.today()
|
||||
end_date = start_date + timedelta(days=14)
|
||||
|
||||
# Create and cancel sprint
|
||||
create_response = await client.post(
|
||||
f"/api/v1/projects/{project_id}/sprints",
|
||||
json={
|
||||
"project_id": project_id,
|
||||
"name": "Double Cancel Sprint",
|
||||
"number": 1,
|
||||
"start_date": start_date.isoformat(),
|
||||
"end_date": end_date.isoformat(),
|
||||
},
|
||||
headers={"Authorization": f"Bearer {user_token}"},
|
||||
)
|
||||
sprint_id = create_response.json()["id"]
|
||||
|
||||
# Cancel once
|
||||
first_cancel = await client.post(
|
||||
f"/api/v1/projects/{project_id}/sprints/{sprint_id}/cancel",
|
||||
headers={"Authorization": f"Bearer {user_token}"},
|
||||
)
|
||||
assert first_cancel.status_code == status.HTTP_200_OK
|
||||
|
||||
# Try to cancel again
|
||||
response = await client.post(
|
||||
f"/api/v1/projects/{project_id}/sprints/{sprint_id}/cancel",
|
||||
headers={"Authorization": f"Bearer {user_token}"},
|
||||
)
|
||||
|
||||
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
|
||||
|
||||
async def test_complete_already_completed_sprint(
|
||||
self, client, user_token, test_project
|
||||
):
|
||||
"""Test that completing an already completed sprint fails."""
|
||||
project_id = test_project["id"]
|
||||
start_date = date.today()
|
||||
end_date = start_date + timedelta(days=14)
|
||||
|
||||
# Create, start, and complete sprint
|
||||
create_response = await client.post(
|
||||
f"/api/v1/projects/{project_id}/sprints",
|
||||
json={
|
||||
"project_id": project_id,
|
||||
"name": "Double Complete Sprint",
|
||||
"number": 1,
|
||||
"start_date": start_date.isoformat(),
|
||||
"end_date": end_date.isoformat(),
|
||||
},
|
||||
headers={"Authorization": f"Bearer {user_token}"},
|
||||
)
|
||||
sprint_id = create_response.json()["id"]
|
||||
|
||||
await client.post(
|
||||
f"/api/v1/projects/{project_id}/sprints/{sprint_id}/start",
|
||||
headers={"Authorization": f"Bearer {user_token}"},
|
||||
)
|
||||
|
||||
# Complete once
|
||||
first_complete = await client.post(
|
||||
f"/api/v1/projects/{project_id}/sprints/{sprint_id}/complete",
|
||||
headers={"Authorization": f"Bearer {user_token}"},
|
||||
)
|
||||
assert first_complete.status_code == status.HTTP_200_OK
|
||||
|
||||
# Try to complete again
|
||||
response = await client.post(
|
||||
f"/api/v1/projects/{project_id}/sprints/{sprint_id}/complete",
|
||||
headers={"Authorization": f"Bearer {user_token}"},
|
||||
)
|
||||
|
||||
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
|
||||
|
||||
async def test_complete_cancelled_sprint(self, client, user_token, test_project):
|
||||
"""Test that completing a cancelled sprint fails."""
|
||||
project_id = test_project["id"]
|
||||
start_date = date.today()
|
||||
end_date = start_date + timedelta(days=14)
|
||||
|
||||
# Create and cancel sprint
|
||||
create_response = await client.post(
|
||||
f"/api/v1/projects/{project_id}/sprints",
|
||||
json={
|
||||
"project_id": project_id,
|
||||
"name": "Complete Cancelled Sprint",
|
||||
"number": 1,
|
||||
"start_date": start_date.isoformat(),
|
||||
"end_date": end_date.isoformat(),
|
||||
},
|
||||
headers={"Authorization": f"Bearer {user_token}"},
|
||||
)
|
||||
sprint_id = create_response.json()["id"]
|
||||
|
||||
await client.post(
|
||||
f"/api/v1/projects/{project_id}/sprints/{sprint_id}/cancel",
|
||||
headers={"Authorization": f"Bearer {user_token}"},
|
||||
)
|
||||
|
||||
# Try to complete cancelled sprint
|
||||
response = await client.post(
|
||||
f"/api/v1/projects/{project_id}/sprints/{sprint_id}/complete",
|
||||
headers={"Authorization": f"Bearer {user_token}"},
|
||||
)
|
||||
|
||||
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
|
||||
|
||||
async def test_start_cancelled_sprint(self, client, user_token, test_project):
|
||||
"""Test that starting a cancelled sprint fails."""
|
||||
project_id = test_project["id"]
|
||||
start_date = date.today()
|
||||
end_date = start_date + timedelta(days=14)
|
||||
|
||||
# Create and cancel sprint
|
||||
create_response = await client.post(
|
||||
f"/api/v1/projects/{project_id}/sprints",
|
||||
json={
|
||||
"project_id": project_id,
|
||||
"name": "Start Cancelled Sprint",
|
||||
"number": 1,
|
||||
"start_date": start_date.isoformat(),
|
||||
"end_date": end_date.isoformat(),
|
||||
},
|
||||
headers={"Authorization": f"Bearer {user_token}"},
|
||||
)
|
||||
sprint_id = create_response.json()["id"]
|
||||
|
||||
await client.post(
|
||||
f"/api/v1/projects/{project_id}/sprints/{sprint_id}/cancel",
|
||||
headers={"Authorization": f"Bearer {user_token}"},
|
||||
)
|
||||
|
||||
# Try to start cancelled sprint
|
||||
response = await client.post(
|
||||
f"/api/v1/projects/{project_id}/sprints/{sprint_id}/start",
|
||||
headers={"Authorization": f"Bearer {user_token}"},
|
||||
)
|
||||
|
||||
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
|
||||
|
||||
async def test_start_completed_sprint(self, client, user_token, test_project):
|
||||
"""Test that starting a completed sprint fails."""
|
||||
project_id = test_project["id"]
|
||||
start_date = date.today()
|
||||
end_date = start_date + timedelta(days=14)
|
||||
|
||||
# Create, start, and complete sprint
|
||||
create_response = await client.post(
|
||||
f"/api/v1/projects/{project_id}/sprints",
|
||||
json={
|
||||
"project_id": project_id,
|
||||
"name": "Start Completed Sprint",
|
||||
"number": 1,
|
||||
"start_date": start_date.isoformat(),
|
||||
"end_date": end_date.isoformat(),
|
||||
},
|
||||
headers={"Authorization": f"Bearer {user_token}"},
|
||||
)
|
||||
sprint_id = create_response.json()["id"]
|
||||
|
||||
await client.post(
|
||||
f"/api/v1/projects/{project_id}/sprints/{sprint_id}/start",
|
||||
headers={"Authorization": f"Bearer {user_token}"},
|
||||
)
|
||||
await client.post(
|
||||
f"/api/v1/projects/{project_id}/sprints/{sprint_id}/complete",
|
||||
headers={"Authorization": f"Bearer {user_token}"},
|
||||
)
|
||||
|
||||
# Try to start completed sprint
|
||||
response = await client.post(
|
||||
f"/api/v1/projects/{project_id}/sprints/{sprint_id}/start",
|
||||
headers={"Authorization": f"Bearer {user_token}"},
|
||||
)
|
||||
|
||||
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
class TestSprintWrongProject:
|
||||
"""Tests for sprint operations when sprint belongs to different project."""
|
||||
|
||||
async def test_complete_sprint_wrong_project(self, client, user_token):
|
||||
"""Test completing a sprint via wrong project returns 404."""
|
||||
# Create two projects
|
||||
project1 = await client.post(
|
||||
"/api/v1/projects",
|
||||
json={"name": "Complete P1", "slug": f"complete-p1-{uuid.uuid4().hex[:6]}"},
|
||||
headers={"Authorization": f"Bearer {user_token}"},
|
||||
)
|
||||
project2 = await client.post(
|
||||
"/api/v1/projects",
|
||||
json={"name": "Complete P2", "slug": f"complete-p2-{uuid.uuid4().hex[:6]}"},
|
||||
headers={"Authorization": f"Bearer {user_token}"},
|
||||
)
|
||||
project1_id = project1.json()["id"]
|
||||
project2_id = project2.json()["id"]
|
||||
|
||||
start_date = date.today()
|
||||
end_date = start_date + timedelta(days=14)
|
||||
|
||||
# Create and start sprint in project1
|
||||
sprint_response = await client.post(
|
||||
f"/api/v1/projects/{project1_id}/sprints",
|
||||
json={
|
||||
"project_id": project1_id,
|
||||
"name": "Complete Sprint",
|
||||
"number": 1,
|
||||
"start_date": start_date.isoformat(),
|
||||
"end_date": end_date.isoformat(),
|
||||
},
|
||||
headers={"Authorization": f"Bearer {user_token}"},
|
||||
)
|
||||
sprint_id = sprint_response.json()["id"]
|
||||
|
||||
await client.post(
|
||||
f"/api/v1/projects/{project1_id}/sprints/{sprint_id}/start",
|
||||
headers={"Authorization": f"Bearer {user_token}"},
|
||||
)
|
||||
|
||||
# Try to complete via wrong project
|
||||
response = await client.post(
|
||||
f"/api/v1/projects/{project2_id}/sprints/{sprint_id}/complete",
|
||||
headers={"Authorization": f"Bearer {user_token}"},
|
||||
)
|
||||
|
||||
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||
|
||||
async def test_cancel_sprint_wrong_project(self, client, user_token):
|
||||
"""Test cancelling a sprint via wrong project returns 404."""
|
||||
# Create two projects
|
||||
project1 = await client.post(
|
||||
"/api/v1/projects",
|
||||
json={"name": "Cancel P1", "slug": f"cancel-p1-{uuid.uuid4().hex[:6]}"},
|
||||
headers={"Authorization": f"Bearer {user_token}"},
|
||||
)
|
||||
project2 = await client.post(
|
||||
"/api/v1/projects",
|
||||
json={"name": "Cancel P2", "slug": f"cancel-p2-{uuid.uuid4().hex[:6]}"},
|
||||
headers={"Authorization": f"Bearer {user_token}"},
|
||||
)
|
||||
project1_id = project1.json()["id"]
|
||||
project2_id = project2.json()["id"]
|
||||
|
||||
start_date = date.today()
|
||||
end_date = start_date + timedelta(days=14)
|
||||
|
||||
# Create sprint in project1
|
||||
sprint_response = await client.post(
|
||||
f"/api/v1/projects/{project1_id}/sprints",
|
||||
json={
|
||||
"project_id": project1_id,
|
||||
"name": "Cancel Sprint",
|
||||
"number": 1,
|
||||
"start_date": start_date.isoformat(),
|
||||
"end_date": end_date.isoformat(),
|
||||
},
|
||||
headers={"Authorization": f"Bearer {user_token}"},
|
||||
)
|
||||
sprint_id = sprint_response.json()["id"]
|
||||
|
||||
# Try to cancel via wrong project
|
||||
response = await client.post(
|
||||
f"/api/v1/projects/{project2_id}/sprints/{sprint_id}/cancel",
|
||||
headers={"Authorization": f"Bearer {user_token}"},
|
||||
)
|
||||
|
||||
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||
|
||||
async def test_delete_sprint_wrong_project(self, client, user_token):
|
||||
"""Test deleting a sprint via wrong project returns 404."""
|
||||
# Create two projects
|
||||
project1 = await client.post(
|
||||
"/api/v1/projects",
|
||||
json={"name": "Delete P1", "slug": f"delete-p1-{uuid.uuid4().hex[:6]}"},
|
||||
headers={"Authorization": f"Bearer {user_token}"},
|
||||
)
|
||||
project2 = await client.post(
|
||||
"/api/v1/projects",
|
||||
json={"name": "Delete P2", "slug": f"delete-p2-{uuid.uuid4().hex[:6]}"},
|
||||
headers={"Authorization": f"Bearer {user_token}"},
|
||||
)
|
||||
project1_id = project1.json()["id"]
|
||||
project2_id = project2.json()["id"]
|
||||
|
||||
start_date = date.today()
|
||||
end_date = start_date + timedelta(days=14)
|
||||
|
||||
# Create sprint in project1
|
||||
sprint_response = await client.post(
|
||||
f"/api/v1/projects/{project1_id}/sprints",
|
||||
json={
|
||||
"project_id": project1_id,
|
||||
"name": "Delete Sprint",
|
||||
"number": 1,
|
||||
"start_date": start_date.isoformat(),
|
||||
"end_date": end_date.isoformat(),
|
||||
},
|
||||
headers={"Authorization": f"Bearer {user_token}"},
|
||||
)
|
||||
sprint_id = sprint_response.json()["id"]
|
||||
|
||||
# Try to delete via wrong project
|
||||
response = await client.delete(
|
||||
f"/api/v1/projects/{project2_id}/sprints/{sprint_id}",
|
||||
headers={"Authorization": f"Bearer {user_token}"},
|
||||
)
|
||||
|
||||
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||
|
||||
async def test_add_issue_to_sprint_wrong_project(self, client, user_token):
|
||||
"""Test adding issue to sprint via wrong project returns 404."""
|
||||
# Create two projects
|
||||
project1 = await client.post(
|
||||
"/api/v1/projects",
|
||||
json={
|
||||
"name": "Add Issue P1",
|
||||
"slug": f"add-issue-p1-{uuid.uuid4().hex[:6]}",
|
||||
},
|
||||
headers={"Authorization": f"Bearer {user_token}"},
|
||||
)
|
||||
project2 = await client.post(
|
||||
"/api/v1/projects",
|
||||
json={
|
||||
"name": "Add Issue P2",
|
||||
"slug": f"add-issue-p2-{uuid.uuid4().hex[:6]}",
|
||||
},
|
||||
headers={"Authorization": f"Bearer {user_token}"},
|
||||
)
|
||||
project1_id = project1.json()["id"]
|
||||
project2_id = project2.json()["id"]
|
||||
|
||||
start_date = date.today()
|
||||
end_date = start_date + timedelta(days=14)
|
||||
|
||||
# Create sprint in project1
|
||||
sprint_response = await client.post(
|
||||
f"/api/v1/projects/{project1_id}/sprints",
|
||||
json={
|
||||
"project_id": project1_id,
|
||||
"name": "Add Issue Sprint",
|
||||
"number": 1,
|
||||
"start_date": start_date.isoformat(),
|
||||
"end_date": end_date.isoformat(),
|
||||
},
|
||||
headers={"Authorization": f"Bearer {user_token}"},
|
||||
)
|
||||
sprint_id = sprint_response.json()["id"]
|
||||
|
||||
# Create issue in project1
|
||||
issue_response = await client.post(
|
||||
f"/api/v1/projects/{project1_id}/issues",
|
||||
json={
|
||||
"project_id": project1_id,
|
||||
"title": "Test Issue",
|
||||
},
|
||||
headers={"Authorization": f"Bearer {user_token}"},
|
||||
)
|
||||
issue_id = issue_response.json()["id"]
|
||||
|
||||
# Try to add issue via wrong project
|
||||
response = await client.post(
|
||||
f"/api/v1/projects/{project2_id}/sprints/{sprint_id}/issues",
|
||||
params={"issue_id": issue_id},
|
||||
headers={"Authorization": f"Bearer {user_token}"},
|
||||
)
|
||||
|
||||
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||
|
||||
Reference in New Issue
Block a user