diff --git a/.architecture-rules/api.yaml b/.architecture-rules/api.yaml index 19d29f59..d9e9364a 100644 --- a/.architecture-rules/api.yaml +++ b/.architecture-rules/api.yaml @@ -201,7 +201,7 @@ api_endpoint_rules: Use request.state.vendor_id from middleware. pattern: file_pattern: "app/api/v1/vendor/**/*.py" - file_pattern: "app/api/v1/shop/**/*.py" + file_pattern: "app/api/v1/storefront/**/*.py" discouraged_patterns: - "db.query(.*).all()" diff --git a/.architecture-rules/auth.yaml b/.architecture-rules/auth.yaml index 3cbed9c7..f9ac6358 100644 --- a/.architecture-rules/auth.yaml +++ b/.architecture-rules/auth.yaml @@ -39,7 +39,7 @@ auth_rules: 1. SHOP ENDPOINTS (public, no authentication required): - Use: vendor: Vendor = Depends(require_vendor_context()) - Vendor is detected from URL/subdomain/domain - - File pattern: app/api/v1/shop/**/*.py + - File pattern: app/api/v1/storefront/**/*.py - Mark as public with: # public 2. VENDOR API ENDPOINTS (authenticated): @@ -57,7 +57,7 @@ auth_rules: file_pattern: "app/api/v1/vendor/**/*.py" anti_patterns: - "require_vendor_context\\(\\)" - file_pattern: "app/api/v1/shop/**/*.py" + file_pattern: "app/api/v1/storefront/**/*.py" required_patterns: - "require_vendor_context\\(\\)|# public" diff --git a/app/api/main.py b/app/api/main.py index 93f3aedc..ec2e48af 100644 --- a/app/api/main.py +++ b/app/api/main.py @@ -4,13 +4,13 @@ API router configuration for multi-tenant ecommerce platform. This module provides: - API version 1 route aggregation -- Route organization by user type (admin, vendor, shop) +- Route organization by user type (admin, vendor, storefront) - Proper route prefixing and tagging """ from fastapi import APIRouter -from app.api.v1 import admin, platform, shop, vendor +from app.api.v1 import admin, platform, storefront, vendor from app.api.v1.shared import language, webhooks api_router = APIRouter() @@ -30,11 +30,12 @@ api_router.include_router(admin.router, prefix="/v1/admin", tags=["admin"]) api_router.include_router(vendor.router, prefix="/v1/vendor", tags=["vendor"]) # ============================================================================ -# SHOP ROUTES (Public shop frontend API) -# Prefix: /api/v1/shop +# STOREFRONT ROUTES (Public customer-facing API) +# Prefix: /api/v1/storefront +# Note: Previously /api/v1/shop, renamed as not all platforms sell items # ============================================================================ -api_router.include_router(shop.router, prefix="/v1/shop", tags=["shop"]) +api_router.include_router(storefront.router, prefix="/v1/storefront", tags=["storefront"]) # ============================================================================ # PLATFORM ROUTES (Public marketing and signup) diff --git a/app/api/v1/shop/__init__.py b/app/api/v1/storefront/__init__.py similarity index 50% rename from app/api/v1/shop/__init__.py rename to app/api/v1/storefront/__init__.py index f10a5063..ca145bfb 100644 --- a/app/api/v1/shop/__init__.py +++ b/app/api/v1/storefront/__init__.py @@ -1,8 +1,8 @@ -# app/api/v1/shop/__init__.py +# app/api/v1/storefront/__init__.py """ -Shop API router aggregation. +Storefront API router aggregation. -This module aggregates all shop-related JSON API endpoints (public facing). +This module aggregates all storefront-related JSON API endpoints (public facing). Uses vendor context from middleware - no vendor_id in URLs. Endpoints: @@ -16,48 +16,50 @@ Authentication: - Products, Cart, Content Pages: No auth required - Orders: Requires customer authentication (get_current_customer_api) - Auth: Public (login, register) + +Note: Previously named "shop", renamed to "storefront" as not all platforms +sell items - storefront is a more accurate term for the customer-facing interface. """ from fastapi import APIRouter -# Import shop routers +# Import storefront routers from . import addresses, auth, carts, messages, orders, products, profile # CMS module router -from app.modules.cms.routes.api.shop import router as cms_shop_router +from app.modules.cms.routes.api.storefront import router as cms_storefront_router -# Create shop router +# Create storefront router router = APIRouter() # ============================================================================ -# SHOP API ROUTES (All vendor-context aware via middleware) +# STOREFRONT API ROUTES (All vendor-context aware via middleware) # ============================================================================ # Addresses (authenticated) -router.include_router(addresses.router, tags=["shop-addresses"]) +router.include_router(addresses.router, tags=["storefront-addresses"]) # Authentication (public) -router.include_router(auth.router, tags=["shop-auth"]) +router.include_router(auth.router, tags=["storefront-auth"]) # Products (public) -router.include_router(products.router, tags=["shop-products"]) +router.include_router(products.router, tags=["storefront-products"]) # Shopping cart (public - session based) -router.include_router(carts.router, tags=["shop-cart"]) +router.include_router(carts.router, tags=["storefront-cart"]) # Orders (authenticated) -router.include_router(orders.router, tags=["shop-orders"]) +router.include_router(orders.router, tags=["storefront-orders"]) # Messages (authenticated) -router.include_router(messages.router, tags=["shop-messages"]) +router.include_router(messages.router, tags=["storefront-messages"]) # Profile (authenticated) -router.include_router(profile.router, tags=["shop-profile"]) +router.include_router(profile.router, tags=["storefront-profile"]) # CMS module router (self-contained module) router.include_router( - cms_shop_router, prefix="/content-pages", tags=["shop-content-pages"] + cms_storefront_router, prefix="/content-pages", tags=["storefront-content-pages"] ) -# Legacy: content_pages.router moved to app.modules.cms.routes.api.shop __all__ = ["router"] diff --git a/app/api/v1/shop/addresses.py b/app/api/v1/storefront/addresses.py similarity index 100% rename from app/api/v1/shop/addresses.py rename to app/api/v1/storefront/addresses.py diff --git a/app/api/v1/shop/auth.py b/app/api/v1/storefront/auth.py similarity index 100% rename from app/api/v1/shop/auth.py rename to app/api/v1/storefront/auth.py diff --git a/app/api/v1/shop/carts.py b/app/api/v1/storefront/carts.py similarity index 100% rename from app/api/v1/shop/carts.py rename to app/api/v1/storefront/carts.py diff --git a/app/api/v1/shop/messages.py b/app/api/v1/storefront/messages.py similarity index 100% rename from app/api/v1/shop/messages.py rename to app/api/v1/storefront/messages.py diff --git a/app/api/v1/shop/orders.py b/app/api/v1/storefront/orders.py similarity index 100% rename from app/api/v1/shop/orders.py rename to app/api/v1/storefront/orders.py diff --git a/app/api/v1/shop/products.py b/app/api/v1/storefront/products.py similarity index 100% rename from app/api/v1/shop/products.py rename to app/api/v1/storefront/products.py diff --git a/app/api/v1/shop/profile.py b/app/api/v1/storefront/profile.py similarity index 100% rename from app/api/v1/shop/profile.py rename to app/api/v1/storefront/profile.py diff --git a/app/modules/cms/routes/api/__init__.py b/app/modules/cms/routes/api/__init__.py index 8794c27c..fdf1fc23 100644 --- a/app/modules/cms/routes/api/__init__.py +++ b/app/modules/cms/routes/api/__init__.py @@ -5,11 +5,11 @@ CMS module API routes. Provides REST API endpoints for content page management: - Admin API: Full CRUD for platform administrators - Vendor API: Vendor-scoped CRUD with ownership checks -- Shop API: Public read-only access for storefronts +- Storefront API: Public read-only access for storefronts """ from app.modules.cms.routes.api.admin import router as admin_router from app.modules.cms.routes.api.vendor import router as vendor_router -from app.modules.cms.routes.api.shop import router as shop_router +from app.modules.cms.routes.api.storefront import router as storefront_router -__all__ = ["admin_router", "vendor_router", "shop_router"] +__all__ = ["admin_router", "vendor_router", "storefront_router"] diff --git a/app/modules/cms/routes/api/shop.py b/app/modules/cms/routes/api/storefront.py similarity index 94% rename from app/modules/cms/routes/api/shop.py rename to app/modules/cms/routes/api/storefront.py index 54efb08f..99d34f22 100644 --- a/app/modules/cms/routes/api/shop.py +++ b/app/modules/cms/routes/api/storefront.py @@ -1,8 +1,8 @@ -# app/modules/cms/routes/api/shop.py +# app/modules/cms/routes/api/storefront.py """ -Shop Content Pages API (Public) +Storefront Content Pages API (Public) -Public endpoints for retrieving content pages in shop frontend. +Public endpoints for retrieving content pages in storefront. No authentication required. """ diff --git a/app/routes/platform_pages.py b/app/routes/platform_pages.py index 92261e1f..a140f437 100644 --- a/app/routes/platform_pages.py +++ b/app/routes/platform_pages.py @@ -127,14 +127,14 @@ async def homepage( if landing_page: # Render landing page with selected template - from app.routes.shop_pages import get_shop_context + from app.routes.storefront_pages import get_storefront_context template_name = landing_page.template or "default" template_path = f"vendor/landing-{template_name}.html" logger.info(f"[HOMEPAGE] Rendering vendor landing page: {template_path}") return templates.TemplateResponse( - template_path, get_shop_context(request, db=db, page=landing_page) + template_path, get_storefront_context(request, db=db, page=landing_page) ) # No landing page - redirect to shop diff --git a/app/routes/shop_pages.py b/app/routes/storefront_pages.py similarity index 92% rename from app/routes/shop_pages.py rename to app/routes/storefront_pages.py index 0fe15989..2371d686 100644 --- a/app/routes/shop_pages.py +++ b/app/routes/storefront_pages.py @@ -1,19 +1,22 @@ -# app/routes/shop_pages.py +# app/routes/storefront_pages.py """ -Shop/Customer HTML page routes using Jinja2 templates. +Storefront/Customer HTML page routes using Jinja2 templates. -These routes serve the public-facing shop interface for customers. +These routes serve the public-facing storefront interface for customers. Authentication required only for account pages. +Note: Previously named "shop_pages.py", renamed to "storefront" as not all +platforms sell items - storefront is a more accurate term. + AUTHENTICATION: - Public pages (catalog, products): No auth required - Account pages (dashboard, orders): Requires customer authentication - Customer authentication accepts: - * customer_token cookie (path=/shop) - for page navigation + * customer_token cookie (path=/storefront) - for page navigation * Authorization header - for API calls - Customers CANNOT access admin or vendor routes -Routes (all mounted at /shop/* or /vendors/{code}/shop/* prefix): +Routes (all mounted at /storefront/* or /vendors/{code}/storefront/* prefix): - GET / → Shop homepage / product catalog - GET /products → Product catalog - GET /products/{id} → Product detail page @@ -86,7 +89,7 @@ def get_resolved_storefront_config(db: Session, vendor) -> dict: # ============================================================================ -def get_shop_context(request: Request, db: Session = None, **extra_context) -> dict: +def get_storefront_context(request: Request, db: Session = None, **extra_context) -> dict: """ Build template context for shop pages. @@ -103,13 +106,13 @@ def get_shop_context(request: Request, db: Session = None, **extra_context) -> d Example: # Simple usage - get_shop_context(request) + get_storefront_context(request) # With database session for navigation - get_shop_context(request, db=db) + get_storefront_context(request, db=db) # With extra data - get_shop_context(request, db=db, user=current_user, product_id=123) + get_storefront_context(request, db=db, user=current_user, product_id=123) """ # Extract from middleware state vendor = getattr(request.state, "vendor", None) @@ -235,7 +238,7 @@ async def shop_products_page(request: Request, db: Session = Depends(get_db)): ) return templates.TemplateResponse( - "shop/products.html", get_shop_context(request, db=db) + "shop/products.html", get_storefront_context(request, db=db) ) @@ -261,7 +264,7 @@ async def shop_product_detail_page( ) return templates.TemplateResponse( - "shop/product.html", get_shop_context(request, db=db, product_id=product_id) + "shop/product.html", get_storefront_context(request, db=db, product_id=product_id) ) @@ -287,7 +290,7 @@ async def shop_category_page( ) return templates.TemplateResponse( - "shop/category.html", get_shop_context(request, db=db, category_slug=category_slug) + "shop/category.html", get_storefront_context(request, db=db, category_slug=category_slug) ) @@ -306,7 +309,7 @@ async def shop_cart_page(request: Request, db: Session = Depends(get_db)): }, ) - return templates.TemplateResponse("shop/cart.html", get_shop_context(request, db=db)) + return templates.TemplateResponse("shop/cart.html", get_storefront_context(request, db=db)) @router.get("/checkout", response_class=HTMLResponse, include_in_schema=False) @@ -324,7 +327,7 @@ async def shop_checkout_page(request: Request, db: Session = Depends(get_db)): }, ) - return templates.TemplateResponse("shop/checkout.html", get_shop_context(request, db=db)) + return templates.TemplateResponse("shop/checkout.html", get_storefront_context(request, db=db)) @router.get("/search", response_class=HTMLResponse, include_in_schema=False) @@ -342,7 +345,7 @@ async def shop_search_page(request: Request, db: Session = Depends(get_db)): }, ) - return templates.TemplateResponse("shop/search.html", get_shop_context(request, db=db)) + return templates.TemplateResponse("shop/search.html", get_storefront_context(request, db=db)) # ============================================================================ @@ -366,7 +369,7 @@ async def shop_register_page(request: Request, db: Session = Depends(get_db)): ) return templates.TemplateResponse( - "shop/account/register.html", get_shop_context(request, db=db) + "shop/account/register.html", get_storefront_context(request, db=db) ) @@ -386,7 +389,7 @@ async def shop_login_page(request: Request, db: Session = Depends(get_db)): ) return templates.TemplateResponse( - "shop/account/login.html", get_shop_context(request, db=db) + "shop/account/login.html", get_storefront_context(request, db=db) ) @@ -408,7 +411,7 @@ async def shop_forgot_password_page(request: Request, db: Session = Depends(get_ ) return templates.TemplateResponse( - "shop/account/forgot-password.html", get_shop_context(request, db=db) + "shop/account/forgot-password.html", get_storefront_context(request, db=db) ) @@ -434,7 +437,7 @@ async def shop_reset_password_page( ) return templates.TemplateResponse( - "shop/account/reset-password.html", get_shop_context(request, db=db) + "shop/account/reset-password.html", get_storefront_context(request, db=db) ) @@ -500,7 +503,7 @@ async def shop_account_dashboard_page( ) return templates.TemplateResponse( - "shop/account/dashboard.html", get_shop_context(request, user=current_customer) + "shop/account/dashboard.html", get_storefront_context(request, user=current_customer) ) @@ -525,7 +528,7 @@ async def shop_orders_page( ) return templates.TemplateResponse( - "shop/account/orders.html", get_shop_context(request, user=current_customer) + "shop/account/orders.html", get_storefront_context(request, user=current_customer) ) @@ -554,7 +557,7 @@ async def shop_order_detail_page( return templates.TemplateResponse( "shop/account/order-detail.html", - get_shop_context(request, user=current_customer, order_id=order_id), + get_storefront_context(request, user=current_customer, order_id=order_id), ) @@ -579,7 +582,7 @@ async def shop_profile_page( ) return templates.TemplateResponse( - "shop/account/profile.html", get_shop_context(request, user=current_customer) + "shop/account/profile.html", get_storefront_context(request, user=current_customer) ) @@ -604,7 +607,7 @@ async def shop_addresses_page( ) return templates.TemplateResponse( - "shop/account/addresses.html", get_shop_context(request, user=current_customer) + "shop/account/addresses.html", get_storefront_context(request, user=current_customer) ) @@ -629,7 +632,7 @@ async def shop_wishlist_page( ) return templates.TemplateResponse( - "shop/account/wishlist.html", get_shop_context(request, user=current_customer) + "shop/account/wishlist.html", get_storefront_context(request, user=current_customer) ) @@ -654,7 +657,7 @@ async def shop_settings_page( ) return templates.TemplateResponse( - "shop/account/settings.html", get_shop_context(request, user=current_customer) + "shop/account/settings.html", get_storefront_context(request, user=current_customer) ) @@ -679,7 +682,7 @@ async def shop_messages_page( ) return templates.TemplateResponse( - "shop/account/messages.html", get_shop_context(request, db=db, user=current_customer) + "shop/account/messages.html", get_storefront_context(request, db=db, user=current_customer) ) @@ -711,7 +714,7 @@ async def shop_message_detail_page( return templates.TemplateResponse( "shop/account/messages.html", - get_shop_context( + get_storefront_context( request, db=db, user=current_customer, conversation_id=conversation_id ), ) @@ -787,7 +790,7 @@ async def generic_content_page( ) return templates.TemplateResponse( - "shop/content-page.html", get_shop_context(request, db=db, page=page) + "shop/content-page.html", get_storefront_context(request, db=db, page=page) ) diff --git a/main.py b/main.py index fec16cb2..9cf6edbc 100644 --- a/main.py +++ b/main.py @@ -62,7 +62,7 @@ from app.exceptions import ServiceUnavailableException from app.exceptions.handler import setup_exception_handlers # Import page routers (legacy routes - will be migrated to modules) -from app.routes import admin_pages, platform_pages, shop_pages, vendor_pages +from app.routes import admin_pages, platform_pages, storefront_pages, vendor_pages # Module route auto-discovery from app.modules.routes import discover_module_routes, get_vendor_page_routes @@ -366,20 +366,20 @@ for route_info in vendor_page_routes: ) # Customer shop pages - Register at TWO prefixes: -# 1. /shop/* (for subdomain/custom domain modes) -# 2. /vendors/{code}/shop/* (for path-based development mode) -logger.info("Registering shop page routes:") -logger.info(" - /shop/* (subdomain/custom domain mode)") -logger.info(" - /vendors/{code}/shop/* (path-based development mode)") +# 1. /storefront/* (for subdomain/custom domain modes) +# 2. /vendors/{code}/storefront/* (for path-based development mode) +logger.info("Registering storefront page routes:") +logger.info(" - /storefront/* (subdomain/custom domain mode)") +logger.info(" - /vendors/{code}/storefront/* (path-based development mode)") app.include_router( - shop_pages.router, prefix="/shop", tags=["shop-pages"], include_in_schema=False + storefront_pages.router, prefix="/storefront", tags=["storefront-pages"], include_in_schema=False ) app.include_router( - shop_pages.router, - prefix="/vendors/{vendor_code}/shop", - tags=["shop-pages"], + storefront_pages.router, + prefix="/vendors/{vendor_code}/storefront", + tags=["storefront-pages"], include_in_schema=False, ) @@ -399,7 +399,7 @@ async def vendor_root_path( if not vendor: raise HTTPException(status_code=404, detail=f"Vendor '{vendor_code}' not found") - from app.routes.shop_pages import get_shop_context + from app.routes.storefront_pages import get_storefront_context from app.modules.cms.services import content_page_service # Get platform_id (use platform from context or default to 1 for OMS) @@ -421,10 +421,10 @@ async def vendor_root_path( template_path = f"vendor/landing-{template_name}.html" return templates.TemplateResponse( - template_path, get_shop_context(request, db=db, page=landing_page) + template_path, get_storefront_context(request, db=db, page=landing_page) ) # No landing page - redirect to shop - return RedirectResponse(url=f"/vendors/{vendor_code}/shop/", status_code=302) + return RedirectResponse(url=f"/vendors/{vendor_code}/storefront/", status_code=302) # ============================================================================ diff --git a/tests/integration/api/v1/shop/__init__.py b/tests/integration/api/v1/storefront/__init__.py similarity index 100% rename from tests/integration/api/v1/shop/__init__.py rename to tests/integration/api/v1/storefront/__init__.py diff --git a/tests/integration/api/v1/shop/test_addresses.py b/tests/integration/api/v1/storefront/test_addresses.py similarity index 91% rename from tests/integration/api/v1/shop/test_addresses.py rename to tests/integration/api/v1/storefront/test_addresses.py index 7e2525d2..2ba612bf 100644 --- a/tests/integration/api/v1/shop/test_addresses.py +++ b/tests/integration/api/v1/storefront/test_addresses.py @@ -1,7 +1,7 @@ -# tests/integration/api/v1/shop/test_addresses.py +# tests/integration/api/v1/storefront/test_addresses.py """Integration tests for shop addresses API endpoints. -Tests the /api/v1/shop/addresses/* endpoints. +Tests the /api/v1/storefront/addresses/* endpoints. All endpoints require customer JWT authentication with vendor context. """ @@ -158,13 +158,13 @@ def other_customer_address(db, test_vendor, other_customer): @pytest.mark.api @pytest.mark.shop class TestShopAddressesListAPI: - """Test shop addresses list endpoint at /api/v1/shop/addresses.""" + """Test shop addresses list endpoint at /api/v1/storefront/addresses.""" def test_list_addresses_requires_authentication(self, client, test_vendor): """Test that listing addresses requires authentication.""" with patch("app.api.v1.shop.addresses.getattr") as mock_getattr: mock_getattr.return_value = test_vendor - response = client.get("/api/v1/shop/addresses") + response = client.get("/api/v1/storefront/addresses") assert response.status_code in [401, 403] def test_list_addresses_success( @@ -181,7 +181,7 @@ class TestShopAddressesListAPI: with patch("app.api.deps._validate_customer_token") as mock_validate: mock_validate.return_value = shop_customer response = client.get( - "/api/v1/shop/addresses", + "/api/v1/storefront/addresses", headers=shop_customer_headers, ) @@ -201,7 +201,7 @@ class TestShopAddressesListAPI: with patch("app.api.deps._validate_customer_token") as mock_validate: mock_validate.return_value = shop_customer response = client.get( - "/api/v1/shop/addresses", + "/api/v1/storefront/addresses", headers=shop_customer_headers, ) @@ -225,7 +225,7 @@ class TestShopAddressesListAPI: with patch("app.api.deps._validate_customer_token") as mock_validate: mock_validate.return_value = shop_customer response = client.get( - "/api/v1/shop/addresses", + "/api/v1/storefront/addresses", headers=shop_customer_headers, ) @@ -242,7 +242,7 @@ class TestShopAddressesListAPI: @pytest.mark.api @pytest.mark.shop class TestShopAddressDetailAPI: - """Test shop address detail endpoint at /api/v1/shop/addresses/{address_id}.""" + """Test shop address detail endpoint at /api/v1/storefront/addresses/{address_id}.""" def test_get_address_success( self, @@ -258,7 +258,7 @@ class TestShopAddressDetailAPI: with patch("app.api.deps._validate_customer_token") as mock_validate: mock_validate.return_value = shop_customer response = client.get( - f"/api/v1/shop/addresses/{customer_address.id}", + f"/api/v1/storefront/addresses/{customer_address.id}", headers=shop_customer_headers, ) @@ -278,7 +278,7 @@ class TestShopAddressDetailAPI: with patch("app.api.deps._validate_customer_token") as mock_validate: mock_validate.return_value = shop_customer response = client.get( - "/api/v1/shop/addresses/99999", + "/api/v1/storefront/addresses/99999", headers=shop_customer_headers, ) @@ -298,7 +298,7 @@ class TestShopAddressDetailAPI: with patch("app.api.deps._validate_customer_token") as mock_validate: mock_validate.return_value = shop_customer response = client.get( - f"/api/v1/shop/addresses/{other_customer_address.id}", + f"/api/v1/storefront/addresses/{other_customer_address.id}", headers=shop_customer_headers, ) @@ -310,7 +310,7 @@ class TestShopAddressDetailAPI: @pytest.mark.api @pytest.mark.shop class TestShopAddressCreateAPI: - """Test shop address creation at POST /api/v1/shop/addresses.""" + """Test shop address creation at POST /api/v1/storefront/addresses.""" def test_create_address_success( self, client, shop_customer_headers, test_vendor, shop_customer @@ -333,7 +333,7 @@ class TestShopAddressCreateAPI: with patch("app.api.deps._validate_customer_token") as mock_validate: mock_validate.return_value = shop_customer response = client.post( - "/api/v1/shop/addresses", + "/api/v1/storefront/addresses", headers=shop_customer_headers, json=address_data, ) @@ -367,7 +367,7 @@ class TestShopAddressCreateAPI: with patch("app.api.deps._validate_customer_token") as mock_validate: mock_validate.return_value = shop_customer response = client.post( - "/api/v1/shop/addresses", + "/api/v1/storefront/addresses", headers=shop_customer_headers, json=address_data, ) @@ -391,7 +391,7 @@ class TestShopAddressCreateAPI: with patch("app.api.deps._validate_customer_token") as mock_validate: mock_validate.return_value = shop_customer response = client.post( - "/api/v1/shop/addresses", + "/api/v1/storefront/addresses", headers=shop_customer_headers, json=address_data, ) @@ -403,7 +403,7 @@ class TestShopAddressCreateAPI: @pytest.mark.api @pytest.mark.shop class TestShopAddressUpdateAPI: - """Test shop address update at PUT /api/v1/shop/addresses/{address_id}.""" + """Test shop address update at PUT /api/v1/storefront/addresses/{address_id}.""" def test_update_address_success( self, @@ -424,7 +424,7 @@ class TestShopAddressUpdateAPI: with patch("app.api.deps._validate_customer_token") as mock_validate: mock_validate.return_value = shop_customer response = client.put( - f"/api/v1/shop/addresses/{customer_address.id}", + f"/api/v1/storefront/addresses/{customer_address.id}", headers=shop_customer_headers, json=update_data, ) @@ -445,7 +445,7 @@ class TestShopAddressUpdateAPI: with patch("app.api.deps._validate_customer_token") as mock_validate: mock_validate.return_value = shop_customer response = client.put( - "/api/v1/shop/addresses/99999", + "/api/v1/storefront/addresses/99999", headers=shop_customer_headers, json=update_data, ) @@ -468,7 +468,7 @@ class TestShopAddressUpdateAPI: with patch("app.api.deps._validate_customer_token") as mock_validate: mock_validate.return_value = shop_customer response = client.put( - f"/api/v1/shop/addresses/{other_customer_address.id}", + f"/api/v1/storefront/addresses/{other_customer_address.id}", headers=shop_customer_headers, json=update_data, ) @@ -480,7 +480,7 @@ class TestShopAddressUpdateAPI: @pytest.mark.api @pytest.mark.shop class TestShopAddressDeleteAPI: - """Test shop address deletion at DELETE /api/v1/shop/addresses/{address_id}.""" + """Test shop address deletion at DELETE /api/v1/storefront/addresses/{address_id}.""" def test_delete_address_success( self, @@ -496,7 +496,7 @@ class TestShopAddressDeleteAPI: with patch("app.api.deps._validate_customer_token") as mock_validate: mock_validate.return_value = shop_customer response = client.delete( - f"/api/v1/shop/addresses/{customer_address.id}", + f"/api/v1/storefront/addresses/{customer_address.id}", headers=shop_customer_headers, ) @@ -511,7 +511,7 @@ class TestShopAddressDeleteAPI: with patch("app.api.deps._validate_customer_token") as mock_validate: mock_validate.return_value = shop_customer response = client.delete( - "/api/v1/shop/addresses/99999", + "/api/v1/storefront/addresses/99999", headers=shop_customer_headers, ) @@ -531,7 +531,7 @@ class TestShopAddressDeleteAPI: with patch("app.api.deps._validate_customer_token") as mock_validate: mock_validate.return_value = shop_customer response = client.delete( - f"/api/v1/shop/addresses/{other_customer_address.id}", + f"/api/v1/storefront/addresses/{other_customer_address.id}", headers=shop_customer_headers, ) @@ -542,7 +542,7 @@ class TestShopAddressDeleteAPI: @pytest.mark.api @pytest.mark.shop class TestShopAddressSetDefaultAPI: - """Test set address as default at PUT /api/v1/shop/addresses/{address_id}/default.""" + """Test set address as default at PUT /api/v1/storefront/addresses/{address_id}/default.""" def test_set_default_success( self, @@ -577,7 +577,7 @@ class TestShopAddressSetDefaultAPI: with patch("app.api.deps._validate_customer_token") as mock_validate: mock_validate.return_value = shop_customer response = client.put( - f"/api/v1/shop/addresses/{second_address.id}/default", + f"/api/v1/storefront/addresses/{second_address.id}/default", headers=shop_customer_headers, ) @@ -594,7 +594,7 @@ class TestShopAddressSetDefaultAPI: with patch("app.api.deps._validate_customer_token") as mock_validate: mock_validate.return_value = shop_customer response = client.put( - "/api/v1/shop/addresses/99999/default", + "/api/v1/storefront/addresses/99999/default", headers=shop_customer_headers, ) @@ -614,7 +614,7 @@ class TestShopAddressSetDefaultAPI: with patch("app.api.deps._validate_customer_token") as mock_validate: mock_validate.return_value = shop_customer response = client.put( - f"/api/v1/shop/addresses/{other_customer_address.id}/default", + f"/api/v1/storefront/addresses/{other_customer_address.id}/default", headers=shop_customer_headers, ) diff --git a/tests/integration/api/v1/shop/test_orders.py b/tests/integration/api/v1/storefront/test_orders.py similarity index 94% rename from tests/integration/api/v1/shop/test_orders.py rename to tests/integration/api/v1/storefront/test_orders.py index 550fedeb..6c09afc9 100644 --- a/tests/integration/api/v1/shop/test_orders.py +++ b/tests/integration/api/v1/storefront/test_orders.py @@ -1,7 +1,7 @@ -# tests/integration/api/v1/shop/test_orders.py +# tests/integration/api/v1/storefront/test_orders.py """Integration tests for shop orders API endpoints. -Tests the /api/v1/shop/orders/* endpoints. +Tests the /api/v1/storefront/orders/* endpoints. All endpoints require customer JWT authentication with vendor context. """ @@ -306,13 +306,13 @@ def other_customer_order(db, test_vendor, other_customer): @pytest.mark.api @pytest.mark.shop class TestShopOrdersListAPI: - """Test shop orders list endpoint at /api/v1/shop/orders.""" + """Test shop orders list endpoint at /api/v1/storefront/orders.""" def test_list_orders_requires_authentication(self, client, test_vendor): """Test that listing orders requires authentication.""" with patch("app.api.v1.shop.orders.getattr") as mock_getattr: mock_getattr.return_value = test_vendor - response = client.get("/api/v1/shop/orders") + response = client.get("/api/v1/storefront/orders") # Without token, should get 401 or 403 assert response.status_code in [401, 403] @@ -327,7 +327,7 @@ class TestShopOrdersListAPI: with patch("app.api.deps._validate_customer_token") as mock_validate: mock_validate.return_value = shop_customer response = client.get( - "/api/v1/shop/orders", + "/api/v1/storefront/orders", headers=shop_customer_headers, ) @@ -345,7 +345,7 @@ class TestShopOrdersListAPI: with patch("app.api.deps._validate_customer_token") as mock_validate: mock_validate.return_value = shop_customer response = client.get( - "/api/v1/shop/orders", + "/api/v1/storefront/orders", headers=shop_customer_headers, ) @@ -358,7 +358,7 @@ class TestShopOrdersListAPI: @pytest.mark.api @pytest.mark.shop class TestShopOrderDetailAPI: - """Test shop order detail endpoint at /api/v1/shop/orders/{order_id}.""" + """Test shop order detail endpoint at /api/v1/storefront/orders/{order_id}.""" def test_get_order_detail_success( self, client, shop_customer_headers, shop_order, test_vendor, shop_customer @@ -369,7 +369,7 @@ class TestShopOrderDetailAPI: with patch("app.api.deps._validate_customer_token") as mock_validate: mock_validate.return_value = shop_customer response = client.get( - f"/api/v1/shop/orders/{shop_order.id}", + f"/api/v1/storefront/orders/{shop_order.id}", headers=shop_customer_headers, ) @@ -390,7 +390,7 @@ class TestShopOrderDetailAPI: with patch("app.api.deps._validate_customer_token") as mock_validate: mock_validate.return_value = shop_customer response = client.get( - "/api/v1/shop/orders/99999", + "/api/v1/storefront/orders/99999", headers=shop_customer_headers, ) @@ -410,7 +410,7 @@ class TestShopOrderDetailAPI: with patch("app.api.deps._validate_customer_token") as mock_validate: mock_validate.return_value = shop_customer response = client.get( - f"/api/v1/shop/orders/{other_customer_order.id}", + f"/api/v1/storefront/orders/{other_customer_order.id}", headers=shop_customer_headers, ) @@ -422,7 +422,7 @@ class TestShopOrderDetailAPI: @pytest.mark.api @pytest.mark.shop class TestShopOrderInvoiceDownloadAPI: - """Test shop order invoice download at /api/v1/shop/orders/{order_id}/invoice.""" + """Test shop order invoice download at /api/v1/storefront/orders/{order_id}/invoice.""" def test_download_invoice_pending_order_rejected( self, client, shop_customer_headers, shop_order, test_vendor, shop_customer @@ -433,7 +433,7 @@ class TestShopOrderInvoiceDownloadAPI: with patch("app.api.deps._validate_customer_token") as mock_validate: mock_validate.return_value = shop_customer response = client.get( - f"/api/v1/shop/orders/{shop_order.id}/invoice", + f"/api/v1/storefront/orders/{shop_order.id}/invoice", headers=shop_customer_headers, ) @@ -485,7 +485,7 @@ class TestShopOrderInvoiceDownloadAPI: with patch("app.api.deps._validate_customer_token") as mock_validate: mock_validate.return_value = shop_customer response = client.get( - f"/api/v1/shop/orders/{other_customer_order.id}/invoice", + f"/api/v1/storefront/orders/{other_customer_order.id}/invoice", headers=shop_customer_headers, ) @@ -501,7 +501,7 @@ class TestShopOrderInvoiceDownloadAPI: with patch("app.api.deps._validate_customer_token") as mock_validate: mock_validate.return_value = shop_customer response = client.get( - "/api/v1/shop/orders/99999/invoice", + "/api/v1/storefront/orders/99999/invoice", headers=shop_customer_headers, ) @@ -523,7 +523,7 @@ class TestShopOrderVATFields: with patch("app.api.deps._validate_customer_token") as mock_validate: mock_validate.return_value = shop_customer response = client.get( - f"/api/v1/shop/orders/{shop_order.id}", + f"/api/v1/storefront/orders/{shop_order.id}", headers=shop_customer_headers, ) @@ -544,7 +544,7 @@ class TestShopOrderVATFields: with patch("app.api.deps._validate_customer_token") as mock_validate: mock_validate.return_value = shop_customer response = client.get( - "/api/v1/shop/orders", + "/api/v1/storefront/orders", headers=shop_customer_headers, ) diff --git a/tests/integration/api/v1/shop/test_password_reset.py b/tests/integration/api/v1/storefront/test_password_reset.py similarity index 93% rename from tests/integration/api/v1/shop/test_password_reset.py rename to tests/integration/api/v1/storefront/test_password_reset.py index 6bbbea3f..abf3f9cf 100644 --- a/tests/integration/api/v1/shop/test_password_reset.py +++ b/tests/integration/api/v1/storefront/test_password_reset.py @@ -1,7 +1,7 @@ -# tests/integration/api/v1/shop/test_password_reset.py +# tests/integration/api/v1/storefront/test_password_reset.py """Integration tests for shop password reset API endpoints. -Tests the /api/v1/shop/auth/forgot-password and /api/v1/shop/auth/reset-password endpoints. +Tests the /api/v1/storefront/auth/forgot-password and /api/v1/storefront/auth/reset-password endpoints. """ from datetime import UTC, datetime, timedelta @@ -104,7 +104,7 @@ def used_reset_token(db, shop_customer): @pytest.mark.api @pytest.mark.shop class TestForgotPasswordAPI: - """Test forgot password endpoint at /api/v1/shop/auth/forgot-password.""" + """Test forgot password endpoint at /api/v1/storefront/auth/forgot-password.""" def test_forgot_password_existing_customer( self, client, db, test_vendor, shop_customer @@ -119,7 +119,7 @@ class TestForgotPasswordAPI: mock_email_service.return_value = mock_instance response = client.post( - "/api/v1/shop/auth/forgot-password", + "/api/v1/storefront/auth/forgot-password", params={"email": shop_customer.email}, ) @@ -139,7 +139,7 @@ class TestForgotPasswordAPI: mock_getattr.return_value = test_vendor response = client.post( - "/api/v1/shop/auth/forgot-password", + "/api/v1/storefront/auth/forgot-password", params={"email": "nonexistent@example.com"}, ) @@ -156,7 +156,7 @@ class TestForgotPasswordAPI: mock_getattr.return_value = test_vendor response = client.post( - "/api/v1/shop/auth/forgot-password", + "/api/v1/storefront/auth/forgot-password", params={"email": inactive_customer.email}, ) @@ -174,7 +174,7 @@ class TestForgotPasswordAPI: with patch("app.api.v1.shop.auth.EmailService"): response = client.post( - "/api/v1/shop/auth/forgot-password", + "/api/v1/storefront/auth/forgot-password", params={"email": shop_customer.email}, ) @@ -210,7 +210,7 @@ class TestForgotPasswordAPI: with patch("app.api.v1.shop.auth.EmailService"): response = client.post( - "/api/v1/shop/auth/forgot-password", + "/api/v1/storefront/auth/forgot-password", params={"email": shop_customer.email}, ) @@ -232,7 +232,7 @@ class TestForgotPasswordAPI: @pytest.mark.api @pytest.mark.shop class TestResetPasswordAPI: - """Test reset password endpoint at /api/v1/shop/auth/reset-password.""" + """Test reset password endpoint at /api/v1/storefront/auth/reset-password.""" def test_reset_password_success( self, client, db, test_vendor, shop_customer, valid_reset_token @@ -243,7 +243,7 @@ class TestResetPasswordAPI: new_password = "newpassword123" response = client.post( - "/api/v1/shop/auth/reset-password", + "/api/v1/storefront/auth/reset-password", params={ "reset_token": valid_reset_token, "new_password": new_password, @@ -271,7 +271,7 @@ class TestResetPasswordAPI: mock_getattr.return_value = test_vendor response = client.post( - "/api/v1/shop/auth/reset-password", + "/api/v1/storefront/auth/reset-password", params={ "reset_token": valid_reset_token, "new_password": "newpassword123", @@ -294,7 +294,7 @@ class TestResetPasswordAPI: mock_getattr.return_value = test_vendor response = client.post( - "/api/v1/shop/auth/reset-password", + "/api/v1/storefront/auth/reset-password", params={ "reset_token": "invalid_token_12345", "new_password": "newpassword123", @@ -311,7 +311,7 @@ class TestResetPasswordAPI: mock_getattr.return_value = test_vendor response = client.post( - "/api/v1/shop/auth/reset-password", + "/api/v1/storefront/auth/reset-password", params={ "reset_token": expired_reset_token, "new_password": "newpassword123", @@ -328,7 +328,7 @@ class TestResetPasswordAPI: mock_getattr.return_value = test_vendor response = client.post( - "/api/v1/shop/auth/reset-password", + "/api/v1/storefront/auth/reset-password", params={ "reset_token": used_reset_token, "new_password": "newpassword123", @@ -345,7 +345,7 @@ class TestResetPasswordAPI: mock_getattr.return_value = test_vendor response = client.post( - "/api/v1/shop/auth/reset-password", + "/api/v1/storefront/auth/reset-password", params={ "reset_token": valid_reset_token, "new_password": "short", # Less than 8 chars @@ -363,7 +363,7 @@ class TestResetPasswordAPI: # First reset should succeed response1 = client.post( - "/api/v1/shop/auth/reset-password", + "/api/v1/storefront/auth/reset-password", params={ "reset_token": valid_reset_token, "new_password": "newpassword123", @@ -373,7 +373,7 @@ class TestResetPasswordAPI: # Second reset with same token should fail response2 = client.post( - "/api/v1/shop/auth/reset-password", + "/api/v1/storefront/auth/reset-password", params={ "reset_token": valid_reset_token, "new_password": "anotherpassword123",