Files
smart-org/apps/api/tests/test_foundation.py
T
mjalmoudy94andClaude Sonnet 5.5 0cd9194b64 v2 foundation: shared contracts, app profile schema, wiring stubs
- projects: app profile columns + one app per org; project_external_git,
  deploy_targets, org_model_grants tables (migration a0f0e1d2c3b4)
- ports + fakes: RepoWorkspace, DeliveryHooks, DeployTarget, Memory,
  ModelGrants, WorkforceBootstrap; AppProfile + AppProfileRepository
- per-workstream wiring (container_org/engine/delivery), config blocks,
  empty routers, delivery worker, MCP/delivery service shells, web stubs
- admin nav derives from ADMIN_ITEMS; plan docs updated for ext4 worktrees

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-13 23:21:17 +03:30

227 lines
8.9 KiB
Python

"""v2 foundation: schema invariants, shared repository, contracts and wiring."""
from __future__ import annotations
import pytest
from sqlalchemy.exc import IntegrityError
import app.application.container as container
from app.application.ports.delivery_hooks import DeliveryHooks, NullDeliveryHooks
from app.application.ports.deploy_target import (
DeployTargetSpec,
DeployTargetTester,
FakeDeployTargetTester,
)
from app.application.ports.memory import MemoryPort, NullMemory, TaskOutcome
from app.application.ports.model_grants import ModelGrantPort, tier_for
from app.application.ports.repo_workspace import (
DiffResult,
FakeRepoWorkspace,
MergeOutcome,
RemoteCredentials,
RepoWorkspacePort,
)
from app.application.ports.workforce_bootstrap import (
NullWorkforceBootstrap,
WorkforceBootstrapPort,
)
from app.domain.base import new_id
from app.domain.identity.models import PlatformRole
from app.infrastructure.db.models import (
AiModelRow,
AiProviderRow,
OrganizationRow,
ProjectRow,
UserRow,
)
from app.infrastructure.repositories.app_profile import AppProfileRepository
async def _org_with_user(session, slug: str) -> tuple[str, str]:
user = UserRow(
id=new_id(),
email=f"{slug}@example.com",
password_hash="x",
name="t",
platform_role=PlatformRole.MANAGER.value,
)
session.add(user)
await session.flush()
org = OrganizationRow(id=new_id(), name=slug, slug=slug, owner_user_id=user.id)
session.add(org)
await session.flush()
return org.id, user.id
@pytest.mark.asyncio
async def test_one_project_per_organization(session_factory):
async with session_factory() as session:
org_id, _ = await _org_with_user(session, "solo")
session.add(ProjectRow(id=new_id(), organization_id=org_id, name="a", slug="a"))
await session.commit()
session.add(ProjectRow(id=new_id(), organization_id=org_id, name="b", slug="b"))
with pytest.raises(IntegrityError):
await session.commit()
@pytest.mark.asyncio
async def test_app_profile_repository_is_tenant_scoped(session_factory):
async with session_factory() as session:
org_a, _ = await _org_with_user(session, "org-a")
org_b, _ = await _org_with_user(session, "org-b")
project = ProjectRow(id=new_id(), organization_id=org_a, name="a", slug="a")
session.add(project)
await session.commit()
repo = AppProfileRepository(session)
profile = await repo.get_for_org(org_a)
assert profile is not None
assert profile.healthcheck_path == "/"
assert profile.git_source == "platform"
assert profile.wizard_completed is False
# Another org can neither read nor update this project's profile.
assert await repo.get_for_org(org_b) is None
assert await repo.get(organization_id=org_b, project_id=project.id) is None
assert (
await repo.update_profile(
organization_id=org_b, project_id=project.id, values={"language": "go"}
)
is None
)
updated = await repo.update_profile(
organization_id=org_a,
project_id=project.id,
values={"language": "python", "framework": "fastapi", "app_port": 8000},
)
assert updated is not None
assert (updated.language, updated.framework, updated.app_port) == (
"python",
"fastapi",
8000,
)
with pytest.raises(ValueError):
await repo.update_profile(
organization_id=org_a, project_id=project.id, values={"organization_id": "x"}
)
@pytest.mark.asyncio
async def test_default_model_grants_return_active_models_with_tiers(session_factory):
async with session_factory() as session:
provider = AiProviderRow(id=new_id(), name="agy-test")
session.add(provider)
await session.flush()
session.add_all(
[
AiModelRow(
id="m-strong", provider_id=provider.id, internal_name="s",
display_name_for_admin="S", capabilities=["text", "reasoning"],
output_price_per_1m=10.0,
),
AiModelRow(
id="m-fast", provider_id=provider.id, internal_name="f",
display_name_for_admin="F", capabilities=["text"], output_price_per_1m=0.5,
),
AiModelRow(
id="m-off", provider_id=provider.id, internal_name="o",
display_name_for_admin="O", capabilities=["text"], is_active=False,
),
]
)
await session.commit()
grants = container.build_model_grants(session)
assert isinstance(grants, ModelGrantPort)
granted = {g.ai_model_id: g.tier for g in await grants.granted_models(organization_id="o")}
assert granted == {"m-strong": "strong", "m-fast": "fast"}
assert await grants.is_granted(organization_id="o", ai_model_id="m-fast")
assert not await grants.is_granted(organization_id="o", ai_model_id="m-off")
def test_tier_for_never_uses_names():
assert tier_for(capabilities=["reasoning"], output_price_per_1m=99) == "strong"
assert tier_for(capabilities=["text"], output_price_per_1m=1) == "fast"
assert tier_for(capabilities=["text"], output_price_per_1m=5) == "balanced"
@pytest.mark.asyncio
async def test_contract_fakes_satisfy_their_protocols():
assert isinstance(FakeRepoWorkspace(), RepoWorkspacePort)
assert isinstance(NullDeliveryHooks(), DeliveryHooks)
assert isinstance(FakeDeployTargetTester(), DeployTargetTester)
assert isinstance(NullMemory(), MemoryPort)
assert isinstance(NullWorkforceBootstrap(), WorkforceBootstrapPort)
@pytest.mark.asyncio
async def test_fake_repo_workspace_full_flow():
repo = FakeRepoWorkspace()
ws = await repo.create_task_worktree(project_id="p", task_id="t1")
assert ws.branch == "task/t1" and ws.sandbox_path.endswith("/t1")
# An empty diff commits nothing.
assert await repo.commit(worktree=ws, message="m", author="a") is None
repo.diffs["t1"] = DiffResult(text="+x", changed_files=["a.py"])
sha = await repo.commit(worktree=ws, message="m", author="a")
assert sha
merged = await repo.merge_into(project_id="p", branch=ws.branch)
assert merged.ok and merged.sha == sha
assert (await repo.push(project_id="p", branch="dev")).ok
assert await repo.head(project_id="p", branch="dev") == sha
repo.merge_outcomes.append(MergeOutcome(ok=False, conflict_files=["a.py"]))
assert (await repo.merge_into(project_id="p", branch="x")).conflict_files == ["a.py"]
check = await repo.test_remote(url="https://x/y.git", credentials=RemoteCredentials())
assert check.ok
@pytest.mark.asyncio
async def test_fake_deploy_tester_and_hooks_and_memory_record_calls():
tester = FakeDeployTargetTester()
ok = await tester.test_connection(
target=DeployTargetSpec(kind="ssh", host="h.example.com", port=22, username="u")
)
bad = await tester.test_connection(
target=DeployTargetSpec(kind="ftp", host="fail.example.com", port=21, username="u")
)
assert ok.ok and ok.fingerprint and not bad.ok
hooks = NullDeliveryHooks()
await hooks.on_app_created(organization_id="o", project_id="p")
await hooks.on_branch_pushed(
organization_id="o", project_id="p", branch="dev", sha="abc", source="engine"
)
assert len(hooks.created) == 1 and hooks.pushed[0]["sha"] == "abc"
memory = NullMemory()
assert await memory.build_context(organization_id="o", role="developer", query="q") == ""
await memory.on_task_finished(
organization_id="o", task=TaskOutcome(task_id="t", title="x", status="completed")
)
assert memory.finished[0].task_id == "t"
@pytest.mark.asyncio
async def test_container_exposes_all_v2_builders(session_factory):
async with session_factory() as session:
assert isinstance(container.build_repo_workspace(session), RepoWorkspacePort)
assert isinstance(container.build_delivery_hooks(session), DeliveryHooks)
assert isinstance(container.build_deploy_target_tester(session), DeployTargetTester)
assert isinstance(container.build_memory(session), MemoryPort)
assert isinstance(container.build_workforce_bootstrap(session), WorkforceBootstrapPort)
@pytest.mark.asyncio
async def test_app_builds_with_v2_routers_and_delivery_worker_runs(client):
# The full handler app imports every v2 router; /health proves it assembled.
assert (await client.get("/health")).status_code == 200
from app.workers.__main__ import _build_parser, _pass_function
from app.workers.delivery import DeliveryWorker
assert (await DeliveryWorker().run_pass()).failed == 0
# ...and it is selectable from the CLI.
one_pass = _pass_function(_build_parser().parse_args(["delivery"]))
assert (await one_pass()).name == "delivery"