Engine honours org policy and monthly budget, legacy runner skips engine tasks, sandbox session provisioned on demand, project fallback for threads, reviewer fallback, tenant checks on project ids, memory reflection respects grants, expiry worker, MCP pipeline_status, login redirect, sharing settings, e2e smoke. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
89 lines
3.9 KiB
Python
89 lines
3.9 KiB
Python
"""A manager order from a thread without a project still gets the organization's project.
|
|
|
|
The web UI creates the manager thread without ``project_id`` (``manager/page.tsx``). Before
|
|
this fix such an order made a task with no project, so the engine gave it no worktree and
|
|
no sandbox. ``ManagerTurns`` now falls back to the organization's project, for the task and
|
|
for its pipeline state. This test drives the path: the owner grants the default models, the
|
|
manager completes the wizard over HTTP, a thread is created over HTTP without ``project_id``,
|
|
and the order goes to the engine's manager, as in ``test_engine_manager.py``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from sqlalchemy import select
|
|
|
|
from app.application.container_engine import build_scheduler
|
|
from app.infrastructure.db.models import AiModelRow, OrganizationRow
|
|
from app.infrastructure.repositories.engine import PipelineStateRepository
|
|
from app.infrastructure.repositories.workforce import TaskRepository
|
|
from tests.conftest import auth_header
|
|
from tests.engine_fakes import ScriptedModel, ScriptedReply
|
|
from tests.test_org_wizard import _complete, _manager
|
|
|
|
DEFAULTS = "/api/admin/default-model-grants"
|
|
|
|
#: The manager's chat reply: one task tag, so the order becomes one task.
|
|
ORDER_REPLY = (
|
|
"باشه، یک کار میسازم.\n"
|
|
'[[CREATE_TASK title="صفحهٔ ورود" kind="feature"]]'
|
|
"ساخت صفحهٔ ورود با اعتبارسنجی[[/CREATE_TASK]]"
|
|
)
|
|
|
|
|
|
async def test_an_order_in_a_thread_without_a_project_gets_the_orgs_project(
|
|
client, session_factory, seeded, platform_owner_token, monkeypatch
|
|
):
|
|
scripted = ScriptedModel([ScriptedReply(content=ORDER_REPLY)])
|
|
monkeypatch.setattr("app.application.container.get_model_provider", lambda: scripted)
|
|
|
|
# The platform owner grants one reasoning model and one cheap model to the default set.
|
|
async with session_factory() as session:
|
|
models = list(
|
|
(await session.execute(select(AiModelRow).order_by(AiModelRow.internal_name)))
|
|
.scalars()
|
|
.all()
|
|
)
|
|
strong = next(m for m in models if "reasoning" in m.capabilities)
|
|
cheap = next(m for m in models if "reasoning" not in m.capabilities)
|
|
owner = auth_header(platform_owner_token)
|
|
for model in (strong, cheap):
|
|
granted = await client.put(
|
|
f"{DEFAULTS}/{model.id}", json={"is_active": True}, headers=owner
|
|
)
|
|
assert granted.status_code == 200, granted.text
|
|
|
|
# The manager completes the wizard: org, project, team and grants.
|
|
_, headers = await _manager(client)
|
|
body = await _complete(client, headers)
|
|
org_id = body["organization"]["id"]
|
|
project_id = body["project"]["id"]
|
|
|
|
# The web UI's way: a thread with no project_id.
|
|
created = await client.post(
|
|
f"/api/organizations/{org_id}/threads", json={"title": "گفتگو"}, headers=headers
|
|
)
|
|
assert created.status_code == 201, created.text
|
|
assert created.json()["project_id"] is None
|
|
thread_id = created.json()["id"]
|
|
|
|
# The order goes to the engine's manager. The HTTP message route builds its manager
|
|
# without pipeline states (workforce_service._manager_turns), so it cannot show the state.
|
|
async with session_factory() as session:
|
|
owner_user_id = (await session.get(OrganizationRow, org_id)).owner_user_id
|
|
_, assistant = await build_scheduler(session).manager.chat(
|
|
organization_id=org_id,
|
|
thread_id=thread_id,
|
|
content="یک صفحهٔ ورود بساز.",
|
|
user_id=owner_user_id,
|
|
)
|
|
await session.commit()
|
|
tasks = await TaskRepository(session).list_for_org(org_id)
|
|
states = await PipelineStateRepository(session).list_for_org(org_id)
|
|
|
|
assert "کار ساخته شد: صفحهٔ ورود" in assistant.content
|
|
|
|
assert len(tasks) == 1
|
|
assert tasks[0].project_id == project_id
|
|
assert len(states) == 1
|
|
assert states[0].project_id == project_id
|