TorbatYar/backend/services/experience/app/tests/test_integrations.py
Mortezakoohjani 203671a7bf feat(experience): ship Experience Platform phases 11.0-11.10
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>
2026-07-27 11:43:10 +03:30

227 lines
7.2 KiB
Python

"""Consumer connectors, widgets & notify dispatch API tests — Phase 11.9."""
import pytest
from app.tests.conftest import TENANT_A, TENANT_B, tenant_headers
async def _workspace(client, code="integrations"):
response = await client.post(
"/api/v1/workspaces",
json={"code": code, "name": "Integrations", "status": "active"},
headers=tenant_headers(TENANT_A),
)
assert response.status_code == 201, response.text
return response.json()
async def _site(client, workspace_id, code="main-site"):
response = await client.post(
"/api/v1/sites",
json={
"workspace_id": workspace_id,
"code": code,
"name": "Main Site",
"default_locale": "fa",
"default_direction": "rtl",
},
headers=tenant_headers(TENANT_A),
)
assert response.status_code == 201, response.text
return response.json()
@pytest.mark.asyncio
async def test_connector_widget_notify_happy_path(client):
caps = (await client.get("/capabilities")).json()
assert caps["phase"] == "11.10"
assert caps["features"]["widgets"] is True
assert caps["features"]["consumer_connectors"] is True
ws = await _workspace(client)
site = await _site(client, ws["id"])
headers = tenant_headers(TENANT_A)
registration = await client.post(
"/api/v1/consumer-connector-registrations",
json={
"workspace_id": ws["id"],
"site_id": site["id"],
"kind": "hospitality",
"code": "hosp-1",
"name": "Hospitality Connector",
"status": "active",
},
headers=headers,
)
assert registration.status_code == 201, registration.text
reg_id = registration.json()["id"]
dispatch = await client.post(
"/api/v1/consumer-connector-dispatches",
json={
"registration_id": reg_id,
"workspace_id": ws["id"],
"site_id": site["id"],
"direction": "outbound",
"event_type": "menu.sync",
"payload_ref": "menu:123",
},
headers=headers,
)
assert dispatch.status_code == 201, dispatch.text
executed = await client.post(
f"/api/v1/consumer-connector-dispatches/{dispatch.json()['id']}/execute",
headers=headers,
)
assert executed.status_code == 200, executed.text
assert executed.json()["status"] == "acknowledged"
assert executed.json()["response_json"]["provider"] == "mock_hospitality"
definition = await client.post(
"/api/v1/widget-definitions",
json={
"workspace_id": ws["id"],
"code": "menu-embed",
"widget_kind": "menu_embed",
"name": "Menu Embed",
"status": "available",
"connector_kind": "hospitality",
},
headers=headers,
)
assert definition.status_code == 201, definition.text
instance = await client.post(
"/api/v1/widget-instances",
json={
"workspace_id": ws["id"],
"site_id": site["id"],
"widget_definition_id": definition.json()["id"],
"widget_kind": "menu_embed",
"code": "menu-1",
"name": "Menu Widget",
"status": "active",
"connector_registration_id": reg_id,
"embed_ref": "embed:menu:main",
},
headers=headers,
)
assert instance.status_code == 201, instance.text
embed = await client.get(
"/api/v1/widget-instances/by-embed/embed:menu:main",
headers=headers,
)
assert embed.status_code == 200, embed.text
assert embed.json()["code"] == "menu-1"
notify = await client.post(
"/api/v1/notify-dispatches",
json={
"workspace_id": ws["id"],
"site_id": site["id"],
"widget_instance_id": instance.json()["id"],
"channel": "sms",
"template_key": "widget.notify",
"to_ref": "user:1",
"payload_json": {"message": "hello"},
},
headers=headers,
)
assert notify.status_code == 201, notify.text
sent = await client.post(
f"/api/v1/notify-dispatches/{notify.json()['id']}/send",
headers=headers,
)
assert sent.status_code == 200, sent.text
body = sent.json()
assert body["status"] == "sent"
assert body["communication_request_ref"].startswith("communication:request:")
@pytest.mark.asyncio
async def test_tenant_isolation(client):
ws = await _workspace(client, "iso")
site = await _site(client, ws["id"], "iso-site")
headers = tenant_headers(TENANT_A)
registration = await client.post(
"/api/v1/consumer-connector-registrations",
json={
"workspace_id": ws["id"],
"kind": "crm",
"code": "crm-iso",
"name": "CRM",
"status": "active",
},
headers=headers,
)
assert registration.status_code == 201, registration.text
reg_id = registration.json()["id"]
other = await client.get(
f"/api/v1/consumer-connector-registrations/{reg_id}",
headers=tenant_headers(TENANT_B),
)
assert other.status_code == 404
@pytest.mark.asyncio
async def test_rejects_forbidden_ownership_keys(client):
ws = await _workspace(client, "forbidden")
headers = tenant_headers(TENANT_A)
rejected_config = await client.post(
"/api/v1/consumer-connector-registrations",
json={
"workspace_id": ws["id"],
"kind": "communication",
"code": "bad-config",
"name": "Bad",
"config_json": {"sms_provider": "local"},
},
headers=headers,
)
assert rejected_config.status_code == 422
rejected_meta = await client.post(
"/api/v1/widget-definitions",
json={
"code": "bad-meta",
"widget_kind": "custom",
"name": "Bad Meta",
"metadata_json": {"booking_engine": "local"},
},
headers=headers,
)
assert rejected_meta.status_code == 422
registration = await client.post(
"/api/v1/consumer-connector-registrations",
json={
"workspace_id": ws["id"],
"kind": "marketplace",
"code": "inactive",
"name": "Inactive",
"status": "draft",
},
headers=headers,
)
assert registration.status_code == 201, registration.text
dispatch = await client.post(
"/api/v1/consumer-connector-dispatches",
json={
"registration_id": registration.json()["id"],
"workspace_id": ws["id"],
"direction": "outbound",
"event_type": "catalog.sync",
},
headers=headers,
)
assert dispatch.status_code == 201, dispatch.text
executed = await client.post(
f"/api/v1/consumer-connector-dispatches/{dispatch.json()['id']}/execute",
headers=headers,
)
assert executed.status_code == 200, executed.text
assert executed.json()["status"] == "failed"