mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
128 lines
3.9 KiB
Python
128 lines
3.9 KiB
Python
"""The task_cell_projects UNIQUE(task_id, team) constraint must hold on the real
|
|||
|
|
Postgres schema — the 'one project per cell per task' invariant (#67).
|
||
|
|
|
||
|
|
The cell-map resolution tests stub the map against SimpleNamespace objects and
|
||
|
|
never touch a DB session, so the real constraint is unexercised. If it were
|
||
|
|
mis-declared or dropped, two same-team rows could coexist and
|
||
|
|
``_resolve_subtask_project`` would non-deterministically return one — cutting a
|
||
|
|
subtask's branch/PR against the WRONG repo. This integration test inserts two
|
||
|
|
rows with the same (task_id, team) and asserts the second raises IntegrityError
|
||
|
|
on ``uq_task_cell_projects_task_team``.
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from typing import TYPE_CHECKING
|
||
|
|
from uuid import uuid4
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
import pytest_asyncio
|
||
|
|
from roboco.db.tables import (
|
||
|
|
AgentTable,
|
||
|
|
ProjectTable,
|
||
|
|
TaskCellProjectTable,
|
||
|
|
TaskTable,
|
||
|
|
)
|
||
|
|
from roboco.models import AgentRole, AgentStatus, Team
|
||
|
|
from roboco.models.base import Complexity, TaskNature, TaskStatus, TaskType
|
||
|
|
from sqlalchemy.exc import IntegrityError
|
||
|
|
|
||
|
|
if TYPE_CHECKING:
|
||
|
|
from collections.abc import AsyncIterator
|
||
|
|
|
||
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
|
|
||
|
|
|
||
|
|
@pytest_asyncio.fixture
|
||
|
|
async def setup_task_and_project(
|
||
|
|
db_session: AsyncSession,
|
||
|
|
) -> AsyncIterator[dict]:
|
||
|
|
system = AgentTable(
|
||
|
|
id=uuid4(),
|
||
|
|
name="System",
|
||
|
|
slug=f"system-{uuid4().hex[:8]}",
|
||
|
|
role=AgentRole.SYSTEM,
|
||
|
|
team=None,
|
||
|
|
status=AgentStatus.ACTIVE,
|
||
|
|
model_config={},
|
||
|
|
system_prompt="s",
|
||
|
|
capabilities=[],
|
||
|
|
permissions={},
|
||
|
|
metrics={},
|
||
|
|
)
|
||
|
|
db_session.add(system)
|
||
|
|
await db_session.flush()
|
||
|
|
|
||
|
|
project = ProjectTable(
|
||
|
|
id=uuid4(),
|
||
|
|
name="P",
|
||
|
|
slug=f"p-{uuid4().hex[:6]}",
|
||
|
|
git_url="https://example.com/r.git",
|
||
|
|
assigned_cell=Team.BACKEND,
|
||
|
|
created_by=system.id,
|
||
|
|
)
|
||
|
|
db_session.add(project)
|
||
|
|
await db_session.flush()
|
||
|
|
|
||
|
|
task = TaskTable(
|
||
|
|
id=uuid4(),
|
||
|
|
title="coordination root",
|
||
|
|
description="a real description over twenty chars long",
|
||
|
|
acceptance_criteria=["criterion one"],
|
||
|
|
team=Team.BACKEND,
|
||
|
|
status=TaskStatus.PENDING,
|
||
|
|
task_type=TaskType.CODE,
|
||
|
|
nature=TaskNature.TECHNICAL,
|
||
|
|
estimated_complexity=Complexity.MEDIUM,
|
||
|
|
created_by=system.id,
|
||
|
|
project_id=project.id,
|
||
|
|
)
|
||
|
|
db_session.add(task)
|
||
|
|
await db_session.flush()
|
||
|
|
|
||
|
|
yield {"task_id": task.id, "project_id": project.id}
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_duplicate_task_team_raises_integrity_error(
|
||
|
|
db_session: AsyncSession, setup_task_and_project: dict
|
||
|
|
) -> None:
|
||
|
|
"""A second row with the same (task_id, team) violates the unique constraint."""
|
||
|
|
task_id = setup_task_and_project["task_id"]
|
||
|
|
project_id = setup_task_and_project["project_id"]
|
||
|
|
|
||
|
|
first = TaskCellProjectTable(
|
||
|
|
id=uuid4(), task_id=task_id, team=Team.BACKEND, project_id=project_id
|
||
|
|
)
|
||
|
|
db_session.add(first)
|
||
|
|
await db_session.flush()
|
||
|
|
|
||
|
|
second = TaskCellProjectTable(
|
||
|
|
id=uuid4(), task_id=task_id, team=Team.BACKEND, project_id=project_id
|
||
|
|
)
|
||
|
|
db_session.add(second)
|
||
|
|
|
||
|
|
with pytest.raises(IntegrityError):
|
||
|
|
await db_session.flush()
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_same_task_different_teams_allowed(
|
||
|
|
db_session: AsyncSession, setup_task_and_project: dict
|
||
|
|
) -> None:
|
||
|
|
"""One project per cell per task — different cells coexist for one task."""
|
||
|
|
task_id = setup_task_and_project["task_id"]
|
||
|
|
project_id = setup_task_and_project["project_id"]
|
||
|
|
|
||
|
|
db_session.add_all(
|
||
|
|
[
|
||
|
|
TaskCellProjectTable(
|
||
|
|
id=uuid4(), task_id=task_id, team=Team.BACKEND, project_id=project_id
|
||
|
|
),
|
||
|
|
TaskCellProjectTable(
|
||
|
|
id=uuid4(), task_id=task_id, team=Team.FRONTEND, project_id=project_id
|
||
|
|
),
|
||
|
|
]
|
||
|
|
)
|
||
|
|
await db_session.flush() # no error — different teams
|