refactor: rename shop to storefront throughout codebase

Rename "shop" to "storefront" as not all platforms sell items -
storefront is a more accurate term for the customer-facing interface.

Changes:
- Rename app/api/v1/shop/ → app/api/v1/storefront/
- Rename app/routes/shop_pages.py → app/routes/storefront_pages.py
- Rename app/modules/cms/routes/api/shop.py → storefront.py
- Rename tests/integration/api/v1/shop/ → storefront/
- Update API prefix from /api/v1/shop to /api/v1/storefront
- Update route tags from shop-* to storefront-*
- Rename get_shop_context() → get_storefront_context()
- Update architecture rules to reference storefront paths
- Update all test API endpoint paths

This is Phase 2 of the storefront module restructure plan.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-29 22:42:01 +01:00
parent 228163d920
commit 3e86d4b58b
20 changed files with 140 additions and 134 deletions

View File

@@ -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()"

View File

@@ -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"

View File

@@ -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)

View File

@@ -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"]

View File

@@ -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"]

View File

@@ -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.
"""

View File

@@ -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

View File

@@ -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)
)

26
main.py
View File

@@ -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)
# ============================================================================

View File

@@ -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,
)

View File

@@ -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,
)

View File

@@ -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",