Add the experience service with sites through analytics/AI hooks, migrations through 0011, and phase docs/manifests marking the track complete. Co-authored-by: Cursor <cursoragent@cursor.com>
170 lines
5.4 KiB
Python
170 lines
5.4 KiB
Python
"""Sites & pages API tests — Phase 11.1."""
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from app.events.publisher import get_event_publisher
|
|
from app.events.types import ExperienceEventType
|
|
from app.tests.conftest import TENANT_A, TENANT_B, tenant_headers
|
|
|
|
|
|
async def _workspace(client, code="ws1"):
|
|
resp = await client.post(
|
|
"/api/v1/workspaces",
|
|
json={"code": code, "name": "Workspace", "status": "active"},
|
|
headers=tenant_headers(TENANT_A),
|
|
)
|
|
assert resp.status_code == 201, resp.text
|
|
return resp.json()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_health_phase_111(client):
|
|
resp = await client.get("/health")
|
|
assert resp.status_code == 200
|
|
# Health version tracks current service version (forward-compatible).
|
|
assert resp.json()["version"].startswith("0.11.")
|
|
|
|
caps = await client.get("/capabilities")
|
|
body = caps.json()
|
|
assert body["features"]["sites"] is True
|
|
assert body["features"]["pages"] is True
|
|
assert body["features"]["page_builder"] is False
|
|
assert "landing" in body["page_types"]
|
|
assert "digital_menu" in body["page_types"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_sites_pages_domains_publish_flow(client):
|
|
ws = await _workspace(client)
|
|
workspace_id = ws["id"]
|
|
|
|
site = await client.post(
|
|
"/api/v1/sites",
|
|
json={
|
|
"workspace_id": workspace_id,
|
|
"code": "main",
|
|
"name": "Main Site",
|
|
"default_locale": "fa",
|
|
"default_direction": "rtl",
|
|
},
|
|
headers=tenant_headers(TENANT_A),
|
|
)
|
|
assert site.status_code == 201, site.text
|
|
site_id = site.json()["id"]
|
|
assert site.json()["publish_status"] == "draft"
|
|
|
|
page = await client.post(
|
|
"/api/v1/pages",
|
|
json={
|
|
"workspace_id": workspace_id,
|
|
"site_id": site_id,
|
|
"code": "home",
|
|
"slug": "home",
|
|
"title": "Home",
|
|
"page_type": "landing",
|
|
"is_home": True,
|
|
},
|
|
headers=tenant_headers(TENANT_A),
|
|
)
|
|
assert page.status_code == 201, page.text
|
|
page_id = page.json()["id"]
|
|
assert page.json()["page_type"] == "landing"
|
|
|
|
menu = await client.post(
|
|
"/api/v1/pages",
|
|
json={
|
|
"workspace_id": workspace_id,
|
|
"site_id": site_id,
|
|
"code": "menu",
|
|
"slug": "menu",
|
|
"title": "Digital Menu",
|
|
"page_type": "digital_menu",
|
|
"external_refs": {"hospitality_menu_ref": "menu-1"},
|
|
},
|
|
headers=tenant_headers(TENANT_A),
|
|
)
|
|
assert menu.status_code == 201, menu.text
|
|
|
|
domain = await client.post(
|
|
"/api/v1/site-domains",
|
|
json={
|
|
"workspace_id": workspace_id,
|
|
"site_id": site_id,
|
|
"host": "pages.example.local",
|
|
"domain_kind": "subdomain",
|
|
"is_primary": True,
|
|
},
|
|
headers=tenant_headers(TENANT_A),
|
|
)
|
|
assert domain.status_code == 201, domain.text
|
|
assert domain.json()["is_primary"] is True
|
|
|
|
published_page = await client.post(
|
|
f"/api/v1/pages/{page_id}/publish",
|
|
headers=tenant_headers(TENANT_A),
|
|
)
|
|
assert published_page.status_code == 200
|
|
assert published_page.json()["publish_status"] == "published"
|
|
|
|
published_site = await client.post(
|
|
f"/api/v1/sites/{site_id}/publish",
|
|
headers=tenant_headers(TENANT_A),
|
|
)
|
|
assert published_site.status_code == 200
|
|
assert published_site.json()["publish_status"] == "published"
|
|
assert published_site.json()["primary_domain"] == "pages.example.local"
|
|
|
|
archived = await client.post(
|
|
f"/api/v1/sites/{site_id}/archive",
|
|
headers=tenant_headers(TENANT_A),
|
|
)
|
|
assert archived.status_code == 200
|
|
assert archived.json()["publish_status"] == "archived"
|
|
|
|
events = {e.event_type for e in get_event_publisher().published}
|
|
assert ExperienceEventType.SITE_CREATED.value in events
|
|
assert ExperienceEventType.PAGE_CREATED.value in events
|
|
assert ExperienceEventType.PAGE_PUBLISHED.value in events
|
|
assert ExperienceEventType.SITE_PUBLISHED.value in events
|
|
assert ExperienceEventType.SITE_DOMAIN_BOUND.value in events
|
|
assert ExperienceEventType.SITE_ARCHIVED.value in events
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_site_tenant_isolation(client):
|
|
ws = await _workspace(client, code="iso")
|
|
site = await client.post(
|
|
"/api/v1/sites",
|
|
json={"workspace_id": ws["id"], "code": "s1", "name": "S1"},
|
|
headers=tenant_headers(TENANT_A),
|
|
)
|
|
assert site.status_code == 201
|
|
site_id = site.json()["id"]
|
|
|
|
other = await client.get(
|
|
f"/api/v1/sites/{site_id}",
|
|
headers=tenant_headers(TENANT_B),
|
|
)
|
|
assert other.status_code == 404
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_archive_blocks_publish(client):
|
|
ws = await _workspace(client, code="arch")
|
|
site = await client.post(
|
|
"/api/v1/sites",
|
|
json={"workspace_id": ws["id"], "code": "s2", "name": "S2"},
|
|
headers=tenant_headers(TENANT_A),
|
|
)
|
|
site_id = site.json()["id"]
|
|
await client.post(
|
|
f"/api/v1/sites/{site_id}/archive",
|
|
headers=tenant_headers(TENANT_A),
|
|
)
|
|
again = await client.post(
|
|
f"/api/v1/sites/{site_id}/publish",
|
|
headers=tenant_headers(TENANT_A),
|
|
)
|
|
assert again.status_code == 409
|