TorbatYar/backend/services/hospitality/app/tests/test_api.py
Mortezakoohjani 5c6a2e78cf feat(platform): seed service registry, deploy all modules, and fix homepage catalog.
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>
2026-07-27 12:39:51 +03:30

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)