"""Architecture tests — Hospitality module boundary enforcement.""" from __future__ import annotations import ast from pathlib import Path from app.models import analytics as analytics_models from app.models import catalog as catalog_models from app.models import connectors as connectors_models from app.models import foundation as models from app.models import kitchen as kitchen_models from app.models import pos_lite as pos_lite_models from app.models import pos_pro as pos_pro_models from app.models import qr as qr_models from app.models import table_service as table_service_models FORBIDDEN_IMPORT_PREFIXES = ( "backend.services.accounting", "backend.services.crm", "backend.services.loyalty", "backend.services.communication", "backend.services.sports_center", "backend.services.automation", "backend.services.notification", "backend.services.file_storage", "backend.services.identity_access", "backend.core_service", ) FOUNDATION_MODELS = [ models.Venue, models.Branch, models.DiningArea, models.DiningTable, models.Menu, models.MenuCategory, models.MenuItem, models.BundleDefinition, models.TenantBundle, models.FeatureToggle, models.HospitalityRole, models.HospitalityPermission, models.HospitalityConfiguration, models.HospitalityEvent, models.HospitalitySetting, models.HospitalityAuditLog, ] CATALOG_MODELS = [ catalog_models.Allergen, catalog_models.ModifierGroup, catalog_models.ModifierOption, catalog_models.MenuItemModifier, catalog_models.MenuItemAllergen, catalog_models.AvailabilityWindow, catalog_models.MenuMediaRef, catalog_models.MenuLocalization, ] QR_MODELS = [ qr_models.QrCode, qr_models.QrMenuSession, qr_models.QrOrderingSession, qr_models.Cart, qr_models.CartLine, ] TABLE_SERVICE_MODELS = [ table_service_models.Reservation, table_service_models.TableAssignment, table_service_models.ServiceRequest, table_service_models.WaitlistEntry, ] POS_LITE_MODELS = [ pos_lite_models.PosRegister, pos_lite_models.PosShift, pos_lite_models.PosTicket, pos_lite_models.PosTicketLine, ] POS_PRO_MODELS = [ pos_pro_models.PosPayment, pos_pro_models.PosDiscount, pos_pro_models.PosTaxLine, pos_pro_models.PosFloorPlan, pos_pro_models.PosStation, ] KITCHEN_MODELS = [ kitchen_models.KitchenStation, kitchen_models.KitchenTicket, kitchen_models.KitchenTicketItem, ] CONNECTORS_MODELS = [ connectors_models.ConnectorRegistration, connectors_models.ConnectorDispatch, ] ANALYTICS_MODELS = [ analytics_models.AnalyticsReportDefinition, analytics_models.AnalyticsSnapshot, ] def test_all_models_have_tenant_id(): from app.core.database import Base import app.models # noqa: F401 skip = {"alembic_version"} for table in Base.metadata.tables.values(): if table.name in skip: continue assert "tenant_id" in table.columns, f"{table.name} missing tenant_id" def test_foundation_aggregates_are_independent(): assert len(FOUNDATION_MODELS) == 16 for model in FOUNDATION_MODELS: assert hasattr(model, "tenant_id") assert hasattr(model, "id") assert not model.__mapper__.relationships def test_catalog_aggregates_are_independent(): assert len(CATALOG_MODELS) == 8 for model in CATALOG_MODELS: assert hasattr(model, "tenant_id") assert hasattr(model, "id") assert not model.__mapper__.relationships def test_qr_aggregates_are_independent(): assert len(QR_MODELS) == 5 for model in QR_MODELS: assert hasattr(model, "tenant_id") assert hasattr(model, "id") assert not model.__mapper__.relationships def test_table_service_aggregates_are_independent(): assert len(TABLE_SERVICE_MODELS) == 4 for model in TABLE_SERVICE_MODELS: assert hasattr(model, "tenant_id") assert hasattr(model, "id") assert not model.__mapper__.relationships def test_pos_lite_aggregates_are_independent(): assert len(POS_LITE_MODELS) == 4 for model in POS_LITE_MODELS: assert hasattr(model, "tenant_id") assert hasattr(model, "id") assert not model.__mapper__.relationships def test_pos_pro_aggregates_are_independent(): assert len(POS_PRO_MODELS) == 5 for model in POS_PRO_MODELS: assert hasattr(model, "tenant_id") assert hasattr(model, "id") assert not model.__mapper__.relationships def test_kitchen_aggregates_are_independent(): assert len(KITCHEN_MODELS) == 3 for model in KITCHEN_MODELS: assert hasattr(model, "tenant_id") assert hasattr(model, "id") assert not model.__mapper__.relationships def test_connectors_aggregates_are_independent(): assert len(CONNECTORS_MODELS) == 2 for model in CONNECTORS_MODELS: assert hasattr(model, "tenant_id") assert hasattr(model, "id") assert not model.__mapper__.relationships def test_analytics_aggregates_are_independent(): assert len(ANALYTICS_MODELS) == 2 for model in ANALYTICS_MODELS: assert hasattr(model, "tenant_id") assert hasattr(model, "id") assert not model.__mapper__.relationships def test_no_format_specific_hardcoded_engines(): forbidden = {"pos_engine", "kitchen_engine", "ordering_engine", "vendor_sdk"} for model in ( FOUNDATION_MODELS + CATALOG_MODELS + QR_MODELS + TABLE_SERVICE_MODELS + POS_LITE_MODELS + POS_PRO_MODELS + KITCHEN_MODELS + CONNECTORS_MODELS + ANALYTICS_MODELS ): columns = set(model.__table__.columns.keys()) assert forbidden.isdisjoint(columns), model.__tablename__ def test_permissions_defined(): from app.permissions.definitions import ALL_PERMISSIONS, PERMISSION_PREFIXES assert "hospitality.view" in ALL_PERMISSIONS assert "hospitality.venues.create" in ALL_PERMISSIONS assert "hospitality.bundles.manage" in ALL_PERMISSIONS assert "hospitality.tenant_bundles.manage" in ALL_PERMISSIONS assert "hospitality.feature_toggles.manage" in ALL_PERMISSIONS assert "hospitality.menus.manage" in ALL_PERMISSIONS assert "hospitality.allergens.manage" in ALL_PERMISSIONS assert "hospitality.modifier_groups.manage" in ALL_PERMISSIONS assert "hospitality.availability_windows.manage" in ALL_PERMISSIONS assert "hospitality.menu_media.manage" in ALL_PERMISSIONS assert "hospitality.menu_localizations.manage" in ALL_PERMISSIONS assert "hospitality.qr_codes.manage" in ALL_PERMISSIONS assert "hospitality.qr_menu_sessions.manage" in ALL_PERMISSIONS assert "hospitality.qr_ordering_sessions.manage" in ALL_PERMISSIONS assert "hospitality.carts.manage" in ALL_PERMISSIONS assert "hospitality.reservations.manage" in ALL_PERMISSIONS assert "hospitality.table_assignments.manage" in ALL_PERMISSIONS assert "hospitality.service_requests.manage" in ALL_PERMISSIONS assert "hospitality.waitlist.manage" in ALL_PERMISSIONS assert "hospitality.pos_registers.manage" in ALL_PERMISSIONS assert "hospitality.pos_shifts.manage" in ALL_PERMISSIONS assert "hospitality.pos_tickets.manage" in ALL_PERMISSIONS assert "hospitality.pos_payments.manage" in ALL_PERMISSIONS assert "hospitality.pos_discounts.manage" in ALL_PERMISSIONS assert "hospitality.pos_floor_plans.manage" in ALL_PERMISSIONS assert "hospitality.kitchen_stations.manage" in ALL_PERMISSIONS assert "hospitality.kitchen_tickets.manage" in ALL_PERMISSIONS assert "hospitality.connector_registrations.manage" in ALL_PERMISSIONS assert "hospitality.connector_dispatches.manage" in ALL_PERMISSIONS assert "hospitality.analytics_reports.manage" in ALL_PERMISSIONS assert "hospitality.analytics_snapshots.manage" in ALL_PERMISSIONS assert "hospitality.digital_menu.view" in ALL_PERMISSIONS assert "hospitality.pos_lite.manage" in ALL_PERMISSIONS assert "hospitality.audit.view" in ALL_PERMISSIONS for prefix in PERMISSION_PREFIXES: assert any(p.startswith(prefix.rstrip(".")) or p.startswith(prefix) for p in ALL_PERMISSIONS) def test_events_defined(): from app.events.types import HospitalityEventType assert HospitalityEventType.VENUE_CREATED.value == "hospitality.venue.created" assert HospitalityEventType.MENU_CREATED.value == "hospitality.menu.created" assert ( HospitalityEventType.TENANT_BUNDLE_ACTIVATED.value == "hospitality.tenant_bundle.activated" ) assert ( HospitalityEventType.FEATURE_TOGGLE_UPSERTED.value == "hospitality.feature_toggle.upserted" ) assert HospitalityEventType.ALLERGEN_CREATED.value == "hospitality.allergen.created" assert ( HospitalityEventType.MODIFIER_GROUP_CREATED.value == "hospitality.modifier_group.created" ) assert ( HospitalityEventType.MENU_LOCALIZATION_UPSERTED.value == "hospitality.menu_localization.upserted" ) assert HospitalityEventType.QR_CODE_CREATED.value == "hospitality.qr_code.created" assert ( HospitalityEventType.QR_ORDERING_SESSION_SUBMITTED.value == "hospitality.qr_ordering_session.submitted" ) assert HospitalityEventType.CART_CREATED.value == "hospitality.cart.created" assert HospitalityEventType.CART_LINE_ADDED.value == "hospitality.cart_line.added" assert ( HospitalityEventType.RESERVATION_CREATED.value == "hospitality.reservation.created" ) assert ( HospitalityEventType.RESERVATION_STATUS_CHANGED.value == "hospitality.reservation.status_changed" ) assert ( HospitalityEventType.TABLE_ASSIGNMENT_CREATED.value == "hospitality.table_assignment.created" ) assert ( HospitalityEventType.SERVICE_REQUEST_CREATED.value == "hospitality.service_request.created" ) assert ( HospitalityEventType.WAITLIST_ENTRY_CREATED.value == "hospitality.waitlist_entry.created" ) assert HospitalityEventType.POS_REGISTER_CREATED.value == "hospitality.pos_register.created" assert HospitalityEventType.POS_SHIFT_OPENED.value == "hospitality.pos_shift.opened" assert HospitalityEventType.POS_TICKET_VOIDED.value == "hospitality.pos_ticket.voided" assert HospitalityEventType.POS_PAYMENT_RECORDED.value == "hospitality.pos_payment.recorded" assert HospitalityEventType.POS_STATION_CREATED.value == "hospitality.pos_station.created" assert HospitalityEventType.KITCHEN_TICKET_CREATED.value == "hospitality.kitchen_ticket.created" assert ( HospitalityEventType.KITCHEN_TICKET_ITEM_ADDED.value == "hospitality.kitchen_ticket_item.added" ) assert ( HospitalityEventType.CONNECTOR_REGISTRATION_CREATED.value == "hospitality.connector_registration.created" ) assert ( HospitalityEventType.CONNECTOR_DISPATCH_CREATED.value == "hospitality.connector_dispatch.created" ) assert ( HospitalityEventType.ANALYTICS_SNAPSHOT_REFRESHED.value == "hospitality.analytics_snapshot.refreshed" ) def test_platform_provider_contracts_exist(): from app.providers import ( AIProvider, AccountingProvider, CRMProvider, CommunicationProvider, Customer360Provider, DeliveryProvider, IdentityProvider, LoyaltyProvider, NotificationProvider, StorageProvider, WebsiteBuilderProvider, ) assert AccountingProvider is not None assert CRMProvider is not None assert LoyaltyProvider is not None assert CommunicationProvider is not None assert DeliveryProvider is not None assert WebsiteBuilderProvider is not None assert NotificationProvider is not None assert StorageProvider is not None assert AIProvider is not None assert IdentityProvider is not None assert Customer360Provider is not None def test_architecture_folders_exist(): root = Path(__file__).resolve().parents[1] for name in ("policies", "specifications", "commands", "queries", "providers"): assert (root / name).is_dir(), name def test_no_forbidden_cross_service_imports(): root = Path(__file__).resolve().parents[1] for path in root.rglob("*.py"): if "tests" in path.parts: continue tree = ast.parse(path.read_text(encoding="utf-8")) for node in ast.walk(tree): if isinstance(node, ast.Import): for alias in node.names: for prefix in FORBIDDEN_IMPORT_PREFIXES: assert not alias.name.startswith(prefix), path if isinstance(node, ast.ImportFrom) and node.module: for prefix in FORBIDDEN_IMPORT_PREFIXES: assert not node.module.startswith(prefix), path def test_api_ownership_no_foreign_domains(): root = Path(__file__).resolve().parents[1] / "api" / "v1" text = "\n".join(p.read_text(encoding="utf-8") for p in root.glob("*.py")) for forbidden in ("/accounting", "/crm/", "/loyalty", "/sports-center"): assert forbidden not in text