Register all active platform services and base features in core DB so admin can manage them, add delivery/hospitality/sports-center backend phases, update apps catalog and production deploy tooling. Co-authored-by: Cursor <cursoragent@cursor.com>
208 lines
6.8 KiB
Python
208 lines
6.8 KiB
Python
"""Hospitality API foundation flow — Phase 12.0."""
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from app.events.publisher import get_event_publisher
|
|
from app.events.types import HospitalityEventType
|
|
from app.tests.conftest import TENANT_A, TENANT_B, tenant_headers
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_health(client):
|
|
resp = await client.get("/health")
|
|
assert resp.status_code == 200
|
|
body = resp.json()
|
|
assert body["service"] == "hospitality-service"
|
|
assert body["status"] == "ok"
|
|
assert body["version"] == "0.12.8.0"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_capabilities(client):
|
|
resp = await client.get("/capabilities")
|
|
assert resp.status_code == 200
|
|
body = resp.json()
|
|
assert body["phase"] == "12.8"
|
|
assert body["product"] == "Torbat Food"
|
|
assert body["features"]["foundation"] is True
|
|
assert body["features"]["bundle_based_licensing"] is True
|
|
assert body["features"]["digital_menu_catalog"] is True
|
|
assert body["features"]["qr_menu"] is True
|
|
assert body["features"]["qr_ordering"] is True
|
|
assert body["features"]["table_service"] is True
|
|
assert body["features"]["reservation"] is True
|
|
assert body["features"]["pos_lite"] is True
|
|
assert body["features"]["pos_pro"] is True
|
|
assert body["features"]["kitchen"] is True
|
|
assert body["features"]["delivery_integration"] is True
|
|
assert body["features"]["accounting_integration"] is True
|
|
assert body["features"]["analytics"] is True
|
|
assert body["features"]["pos_engine"] is False
|
|
assert "digital_menu" in body["bundles"]
|
|
assert "cafe" in body["venue_formats"]
|
|
assert body["independence"]["integration_mode"] == "api_and_events_only"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_foundation_flow_and_events(client):
|
|
venue = await client.post(
|
|
"/api/v1/venues",
|
|
json={
|
|
"code": "main",
|
|
"name": "Main Cafe",
|
|
"status": "active",
|
|
"venue_format": "cafe",
|
|
},
|
|
headers=tenant_headers(TENANT_A),
|
|
)
|
|
assert venue.status_code == 201, venue.text
|
|
venue_id = venue.json()["id"]
|
|
assert venue.json()["code"] == "MAIN"
|
|
assert venue.json()["venue_format"] == "cafe"
|
|
|
|
branch = await client.post(
|
|
"/api/v1/branches",
|
|
json={"venue_id": venue_id, "code": "b1", "name": "Branch 1"},
|
|
headers=tenant_headers(TENANT_A),
|
|
)
|
|
assert branch.status_code == 201, branch.text
|
|
|
|
area = await client.post(
|
|
"/api/v1/dining-areas",
|
|
json={"venue_id": venue_id, "code": "hall", "name": "Main Hall"},
|
|
headers=tenant_headers(TENANT_A),
|
|
)
|
|
assert area.status_code == 201, area.text
|
|
area_id = area.json()["id"]
|
|
|
|
table = await client.post(
|
|
"/api/v1/tables",
|
|
json={
|
|
"venue_id": venue_id,
|
|
"dining_area_id": area_id,
|
|
"code": "t1",
|
|
"name": "Table 1",
|
|
"capacity": 4,
|
|
},
|
|
headers=tenant_headers(TENANT_A),
|
|
)
|
|
assert table.status_code == 201, table.text
|
|
|
|
menu = await client.post(
|
|
"/api/v1/menus",
|
|
json={"venue_id": venue_id, "code": "day", "name": "Day Menu"},
|
|
headers=tenant_headers(TENANT_A),
|
|
)
|
|
assert menu.status_code == 201, menu.text
|
|
menu_id = menu.json()["id"]
|
|
|
|
category = await client.post(
|
|
"/api/v1/menu-categories",
|
|
json={
|
|
"venue_id": venue_id,
|
|
"menu_id": menu_id,
|
|
"code": "drinks",
|
|
"name": "Drinks",
|
|
},
|
|
headers=tenant_headers(TENANT_A),
|
|
)
|
|
assert category.status_code == 201, category.text
|
|
category_id = category.json()["id"]
|
|
|
|
item = await client.post(
|
|
"/api/v1/menu-items",
|
|
json={
|
|
"venue_id": venue_id,
|
|
"menu_id": menu_id,
|
|
"category_id": category_id,
|
|
"code": "latte",
|
|
"name": "Latte",
|
|
"base_price": 150000,
|
|
},
|
|
headers=tenant_headers(TENANT_A),
|
|
)
|
|
assert item.status_code == 201, item.text
|
|
|
|
bundle = await client.post(
|
|
"/api/v1/bundle-definitions",
|
|
json={
|
|
"code": "dm",
|
|
"bundle_key": "digital_menu",
|
|
"name": "Digital Menu",
|
|
"feature_keys": ["hospitality.digital_menu"],
|
|
"permission_prefixes": ["hospitality.digital_menu."],
|
|
"api_prefixes": ["/api/v1/menus"],
|
|
},
|
|
headers=tenant_headers(TENANT_A),
|
|
)
|
|
assert bundle.status_code == 201, bundle.text
|
|
bundle_id = bundle.json()["id"]
|
|
|
|
activated = await client.post(
|
|
"/api/v1/tenant-bundles/activate",
|
|
json={"bundle_definition_id": bundle_id, "venue_id": venue_id},
|
|
headers=tenant_headers(TENANT_A),
|
|
)
|
|
assert activated.status_code == 201, activated.text
|
|
assert activated.json()["bundle_key"] == "digital_menu"
|
|
assert activated.json()["status"] == "active"
|
|
|
|
toggle = await client.post(
|
|
"/api/v1/feature-toggles/upsert",
|
|
json={
|
|
"feature_key": "hospitality.digital_menu",
|
|
"enabled": True,
|
|
"venue_id": venue_id,
|
|
"bundle_key": "digital_menu",
|
|
},
|
|
headers=tenant_headers(TENANT_A),
|
|
)
|
|
assert toggle.status_code == 200, toggle.text
|
|
assert toggle.json()["enabled"] is True
|
|
|
|
role = await client.post(
|
|
"/api/v1/roles",
|
|
json={"code": "manager", "name": "Manager", "venue_id": venue_id},
|
|
headers=tenant_headers(TENANT_A),
|
|
)
|
|
assert role.status_code == 201, role.text
|
|
|
|
cfg = await client.post(
|
|
"/api/v1/configurations",
|
|
json={
|
|
"code": "hours",
|
|
"name": "Working Hours",
|
|
"venue_id": venue_id,
|
|
"config": {"timezone": "Asia/Tehran"},
|
|
},
|
|
headers=tenant_headers(TENANT_A),
|
|
)
|
|
assert cfg.status_code == 201, cfg.text
|
|
|
|
published = {e.event_type for e in get_event_publisher().published}
|
|
assert HospitalityEventType.VENUE_CREATED.value in published
|
|
assert HospitalityEventType.MENU_CREATED.value in published
|
|
assert HospitalityEventType.TENANT_BUNDLE_ACTIVATED.value in published
|
|
assert HospitalityEventType.FEATURE_TOGGLE_UPSERTED.value in published
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_tenant_isolation(client):
|
|
venue = await client.post(
|
|
"/api/v1/venues",
|
|
json={"code": "a1", "name": "Tenant A Venue", "status": "active"},
|
|
headers=tenant_headers(TENANT_A),
|
|
)
|
|
assert venue.status_code == 201
|
|
venue_id = venue.json()["id"]
|
|
|
|
other = await client.get(
|
|
f"/api/v1/venues/{venue_id}",
|
|
headers=tenant_headers(TENANT_B),
|
|
)
|
|
assert other.status_code == 404
|
|
|
|
missing = await client.get("/api/v1/venues")
|
|
assert missing.status_code in (400, 422)
|