"""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