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>
160 lines
5.1 KiB
Python
160 lines
5.1 KiB
Python
"""Phase 12.6 Kitchen API / tenant / validation tests."""
|
|
from __future__ import annotations
|
|
|
|
import uuid
|
|
|
|
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
|
|
|
|
|
|
async def _seed_venue(client, tenant_id, *, venue_code: str = "v1"):
|
|
h = tenant_headers(tenant_id)
|
|
venue = await client.post(
|
|
"/api/v1/venues",
|
|
json={"code": venue_code, "name": "Cafe", "status": "active", "venue_format": "cafe"},
|
|
headers=h,
|
|
)
|
|
assert venue.status_code == 201, venue.text
|
|
return {"headers": h, "venue_id": venue.json()["id"]}
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_kitchen_station_ticket_item_lifecycle(client):
|
|
seed = await _seed_venue(client, TENANT_A, venue_code="kit-v1")
|
|
h = seed["headers"]
|
|
|
|
station = await client.post(
|
|
"/api/v1/kitchen-stations",
|
|
json={"venue_id": seed["venue_id"], "code": "grill", "name": "Grill Station"},
|
|
headers=h,
|
|
)
|
|
assert station.status_code == 201, station.text
|
|
station_id = station.json()["id"]
|
|
|
|
source_id = str(uuid.uuid4())
|
|
ticket = await client.post(
|
|
"/api/v1/kitchen-tickets",
|
|
json={
|
|
"venue_id": seed["venue_id"],
|
|
"station_id": station_id,
|
|
"source_type": "pos_ticket",
|
|
"source_id": source_id,
|
|
"code": "kt-1",
|
|
},
|
|
headers=h,
|
|
)
|
|
assert ticket.status_code == 201, ticket.text
|
|
ticket_id = ticket.json()["id"]
|
|
assert ticket.json()["status"] == "queued"
|
|
|
|
item = await client.post(
|
|
"/api/v1/kitchen-ticket-items",
|
|
json={
|
|
"venue_id": seed["venue_id"],
|
|
"kitchen_ticket_id": ticket_id,
|
|
"menu_item_id": str(uuid.uuid4()),
|
|
"quantity": 2,
|
|
},
|
|
headers=h,
|
|
)
|
|
assert item.status_code == 201, item.text
|
|
item_id = item.json()["id"]
|
|
assert item.json()["status"] == "queued"
|
|
|
|
item_status = await client.post(
|
|
f"/api/v1/kitchen-ticket-items/{item_id}/status",
|
|
json={"status": "preparing"},
|
|
headers=h,
|
|
)
|
|
assert item_status.status_code == 200, item_status.text
|
|
assert item_status.json()["status"] == "preparing"
|
|
|
|
ticket_status = await client.post(
|
|
f"/api/v1/kitchen-tickets/{ticket_id}/status",
|
|
json={"status": "ready"},
|
|
headers=h,
|
|
)
|
|
assert ticket_status.status_code == 200, ticket_status.text
|
|
assert ticket_status.json()["status"] == "ready"
|
|
|
|
bumped = await client.post(
|
|
f"/api/v1/kitchen-tickets/{ticket_id}/status",
|
|
json={"status": "bumped"},
|
|
headers=h,
|
|
)
|
|
assert bumped.status_code == 200, bumped.text
|
|
|
|
blocked = await client.post(
|
|
f"/api/v1/kitchen-tickets/{ticket_id}/status",
|
|
json={"status": "queued"},
|
|
headers=h,
|
|
)
|
|
assert blocked.status_code == 409
|
|
|
|
published = {e.event_type for e in get_event_publisher().published}
|
|
assert HospitalityEventType.KITCHEN_STATION_CREATED.value in published
|
|
assert HospitalityEventType.KITCHEN_TICKET_CREATED.value in published
|
|
assert HospitalityEventType.KITCHEN_TICKET_STATUS_CHANGED.value in published
|
|
assert HospitalityEventType.KITCHEN_TICKET_ITEM_ADDED.value in published
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_kitchen_tenant_isolation(client):
|
|
seed = await _seed_venue(client, TENANT_A, venue_code="kit-iso-v1")
|
|
station = await client.post(
|
|
"/api/v1/kitchen-stations",
|
|
json={"venue_id": seed["venue_id"], "code": "iso-station", "name": "Iso Station"},
|
|
headers=seed["headers"],
|
|
)
|
|
assert station.status_code == 201, station.text
|
|
station_id = station.json()["id"]
|
|
|
|
denied = await client.get(
|
|
f"/api/v1/kitchen-stations/{station_id}", headers=tenant_headers(TENANT_B)
|
|
)
|
|
assert denied.status_code == 404
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_kitchen_ticket_item_quantity_validation(client):
|
|
seed = await _seed_venue(client, TENANT_A, venue_code="kit-qty-v1")
|
|
h = seed["headers"]
|
|
|
|
ticket = await client.post(
|
|
"/api/v1/kitchen-tickets",
|
|
json={
|
|
"venue_id": seed["venue_id"],
|
|
"source_type": "pos_ticket",
|
|
"source_id": str(uuid.uuid4()),
|
|
"code": "kit-qty-t1",
|
|
},
|
|
headers=h,
|
|
)
|
|
assert ticket.status_code == 201, ticket.text
|
|
ticket_id = ticket.json()["id"]
|
|
|
|
bad = await client.post(
|
|
"/api/v1/kitchen-ticket-items",
|
|
json={
|
|
"venue_id": seed["venue_id"],
|
|
"kitchen_ticket_id": ticket_id,
|
|
"menu_item_id": str(uuid.uuid4()),
|
|
"quantity": 0,
|
|
},
|
|
headers=h,
|
|
)
|
|
assert bad.status_code == 422
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_capabilities_phase_12_6(client):
|
|
resp = await client.get("/capabilities")
|
|
assert resp.status_code == 200
|
|
body = resp.json()
|
|
assert body["features"]["kitchen"] is True
|
|
assert body["features"]["kitchen_engine"] is True
|
|
assert body["features"]["pos_engine"] is False
|