feat(delivery): the Application page + owner consoles (v2 phase A3-6)
The user-visible payoff of the whole delivery workstream. - (app)/app: four tabs — pipeline (runs + the eight steps + a live console), releases (with demo rollback), production (target summary, confirmed deploy, history), Git. The header answers "is it live": demo URL, live release, health state. - Live console: batches onto an animation frame, stops following the tail the moment the user scrolls away from the bottom, strips ANSI colour codes and carriage-return redraws. A console that yanks the view back is worse than none. - Production panel: the target is shown as the operator recognises their server plus has_secret/ثبت شده. No field anywhere could render a credential. - contracts: the v2 delivery types and seven new WsEventType members. - /admin/sandbox (owner): per-role container counts, running containers, per-org limits, image mapping, stop with the id typed back (audited). A provider without the capability reports an empty list rather than an error. - /admin/domains (owner): every route with its owning app, attach/detach (host typed back), idempotent re-sync. - /projects removed entirely, with a permanent redirect to /app. Tests: 502 API (+3), 78 web (+11); ruff, mypy, tsc and lint clean. Report: docs/plan/reports/agent-3-phase-6.md
This commit is contained in:
@@ -488,11 +488,6 @@ async def test_run_process_streams_output_to_the_callback():
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(autouse=True)
|
||||
async def _platform_seed(seeded):
|
||||
return seeded
|
||||
|
||||
|
||||
async def _owner_org(client: AsyncClient) -> tuple[str, dict]:
|
||||
from app.domain.identity.models import PlatformRole
|
||||
from tests.conftest import signup
|
||||
@@ -586,3 +581,65 @@ async def test_the_owner_check_helper_rejects_a_manager():
|
||||
_require_platform_owner(_Ctx(owner)) # must not raise
|
||||
|
||||
_ = (Membership, Role)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# HTTP surface: the owner's sandbox console
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(autouse=True)
|
||||
async def _platform_seed(seeded):
|
||||
return seeded
|
||||
|
||||
|
||||
async def _owner_org(client: AsyncClient) -> tuple[str, dict]:
|
||||
from app.domain.identity.models import PlatformRole
|
||||
from tests.conftest import signup
|
||||
|
||||
token = (
|
||||
await signup(
|
||||
client,
|
||||
"sandbox-owner@example.com",
|
||||
name="مالک سامانه",
|
||||
platform_role=PlatformRole.OWNER,
|
||||
)
|
||||
)["access_token"]
|
||||
headers = auth_header(token)
|
||||
org_id = (
|
||||
await client.post("/api/organizations", json={"name": "سازمان سندباکس"}, headers=headers)
|
||||
).json()["id"]
|
||||
return org_id, headers
|
||||
|
||||
|
||||
async def test_a_non_owner_cannot_list_containers(client: AsyncClient):
|
||||
from tests.conftest import signup
|
||||
|
||||
token = (await signup(client, "not-owner-sandbox@example.com"))["access_token"]
|
||||
response = await client.get("/api/admin/sandbox/containers", headers=auth_header(token))
|
||||
assert response.status_code == 403, response.text
|
||||
|
||||
|
||||
async def test_the_owner_sees_an_empty_list_with_a_mock_provider(
|
||||
client: AsyncClient,
|
||||
):
|
||||
"""The console must not invent containers it cannot see."""
|
||||
_org, headers = await _owner_org(client)
|
||||
response = await client.get("/api/admin/sandbox/containers", headers=headers)
|
||||
|
||||
assert response.status_code == 200, response.text
|
||||
body = response.json()
|
||||
assert body["containers"] == []
|
||||
# The image mapping is always available: it is configuration, not state.
|
||||
assert "python" in body["images"]
|
||||
|
||||
|
||||
async def test_stopping_a_container_requires_the_id_typed_back(client: AsyncClient):
|
||||
_org, headers = await _owner_org(client)
|
||||
response = await client.post(
|
||||
"/api/admin/sandbox/containers/abc123/stop",
|
||||
json={"confirm": "something-else"},
|
||||
headers=headers,
|
||||
)
|
||||
assert response.status_code == 422, response.text
|
||||
assert "مطابقت" in response.json()["error"]["message"]
|
||||
|
||||
Reference in New Issue
Block a user