Files
smart-org/apps/api/tests/test_integration_tenant_project.py
mjalmoudy94andClaude Sonnet 5.5 fef5a9deab feat(integration): wire v2 workstreams together (engine policy/budget, sandbox, tenant checks, order route, docs)
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>
2026-10-14 20:57:56 +03:30

165 lines
6.7 KiB
Python

"""Integration: a project id from another organization never reaches a thread, a task or a sandbox.
The caller names the project of a thread or a task. The service answers 404 for a project
that is not the caller's organization's, so one tenant cannot put work into another
tenant's project. The engine applies the same rule before it reuses or starts a sandbox,
so a task row that names a foreign project never gets that project's session, and a
workspace of another organization on the same project is ignored.
"""
from __future__ import annotations
from app.application.container_engine import _EngineSandboxSessions
from app.domain.base import new_id
from app.domain.sandbox.models import SandboxSession, SessionStatus, Workspace, WorkspaceStatus
from app.infrastructure.db.models import OrganizationRow, ProjectRow, TaskRow, UserRow
from app.infrastructure.repositories.sandbox import (
SandboxSessionRepository,
SandboxTemplateRepository,
WorkspaceRepository,
)
from app.infrastructure.repositories.workforce import TaskRepository
from tests.conftest import auth_header, signup
from tests.test_org_wizard import _complete
async def _org_with_project(session, *, label: str) -> tuple[str, str, str]:
"""One organization with its owner and one project. Returns (org, owner, project) ids."""
user = UserRow(email=f"{label}-{new_id()[:8]}@example.com", password_hash="x")
session.add(user)
await session.flush()
org = OrganizationRow(
name=f"سازمان {label}", slug=f"{label}-{user.id[:6]}", owner_user_id=user.id
)
session.add(org)
await session.flush()
project = ProjectRow(
id=new_id(), organization_id=org.id, name="app", slug="app", test_command="pytest -q"
)
session.add(project)
await session.flush()
return org.id, user.id, project.id
async def test_a_thread_or_task_cannot_name_another_orgs_project(client, seeded):
headers_a = auth_header((await signup(client, "tenant-a@example.com"))["access_token"])
headers_b = auth_header((await signup(client, "tenant-b@example.com"))["access_token"])
body_a = await _complete(client, headers_a, {"org_name": "سازمان الف", "slug": "tenant-alpha"})
body_b = await _complete(client, headers_b, {"org_name": "سازمان ب", "slug": "tenant-bravo"})
org_a = body_a["organization"]["id"]
own_project = body_a["project"]["id"]
foreign_project = body_b["project"]["id"]
thread = await client.post(
f"/api/organizations/{org_a}/threads",
json={"title": "گفتگو", "project_id": foreign_project},
headers=headers_a,
)
task = await client.post(
f"/api/organizations/{org_a}/tasks",
json={"title": "کار مشترک", "project_id": foreign_project},
headers=headers_a,
)
own = await client.post(
f"/api/organizations/{org_a}/tasks",
json={"title": "کار خودی", "project_id": own_project},
headers=headers_a,
)
listed = await client.get(f"/api/organizations/{org_a}/tasks", headers=headers_a)
assert thread.status_code == 404, thread.text
assert task.status_code == 404, task.text
assert own.status_code == 201, own.text
# The refused task was never stored: the only task of org A is the one in its own project.
assert [row["project_id"] for row in listed.json()] == [own_project]
async def test_the_engine_never_runs_a_task_in_another_orgs_project(session_factory, seeded):
"""Org B has a running session. A task of org A that names B's project gets none of it."""
async with session_factory() as session:
org_b, _, project_b = await _org_with_project(session, label="sbx-b")
org_a, user_a, _ = await _org_with_project(session, label="sbx-a")
template = (await SandboxTemplateRepository(session).list())[0]
workspace_b = await WorkspaceRepository(session).create(
Workspace(
organization_id=org_b,
project_id=project_b,
template_id=template.id,
status=WorkspaceStatus.READY,
)
)
await SandboxSessionRepository(session).create(
SandboxSession(
workspace_id=workspace_b.id,
external_session_id="sess-b",
status=SessionStatus.RUNNING,
)
)
task = TaskRow(
organization_id=org_a,
title="کار غریبه",
project_id=project_b,
created_by_user_id=user_a,
)
session.add(task)
await session.commit()
task_id = task.id
async with session_factory() as session:
row = await TaskRepository(session).get(task_id)
assert row is not None
sandbox_id = await _EngineSandboxSessions(session).for_task(row)
await session.commit()
workspaces = await WorkspaceRepository(session).list_for_project(project_b)
assert sandbox_id == ""
# Nothing was provisioned under org A's name in org B's project.
assert [w.organization_id for w in workspaces] == [org_b]
async def test_a_workspace_of_another_org_on_the_same_project_is_ignored(session_factory, seeded):
"""A foreign workspace linked to org A's own project is never the one the task uses."""
async with session_factory() as session:
org_a, user_a, project_a = await _org_with_project(session, label="sbx-own")
org_b, _, _ = await _org_with_project(session, label="sbx-other")
template = (await SandboxTemplateRepository(session).list())[0]
foreign = await WorkspaceRepository(session).create(
Workspace(
organization_id=org_b,
project_id=project_a,
template_id=template.id,
status=WorkspaceStatus.READY,
)
)
await SandboxSessionRepository(session).create(
SandboxSession(
workspace_id=foreign.id,
external_session_id="sess-foreign",
status=SessionStatus.RUNNING,
)
)
task = TaskRow(
organization_id=org_a,
title="کار خودی",
project_id=project_a,
created_by_user_id=user_a,
)
session.add(task)
await session.commit()
task_id = task.id
async with session_factory() as session:
row = await TaskRepository(session).get(task_id)
assert row is not None
sandbox_id = await _EngineSandboxSessions(session).for_task(row)
await session.commit()
own = [
w
for w in await WorkspaceRepository(session).list_for_project(project_a)
if w.organization_id == org_a
]
assert sandbox_id
assert sandbox_id != "sess-foreign"
assert len(own) == 1