Files
orion/tests/integration/api/v1/storefront/test_addresses.py
Samir Boulahtit 3e86d4b58b 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>
2026-01-29 22:42:01 +01:00

622 lines
21 KiB
Python

# tests/integration/api/v1/storefront/test_addresses.py
"""Integration tests for shop addresses API endpoints.
Tests the /api/v1/storefront/addresses/* endpoints.
All endpoints require customer JWT authentication with vendor context.
"""
from datetime import UTC, datetime, timedelta
from unittest.mock import patch
import pytest
from jose import jwt
from app.modules.customers.models.customer import Customer, CustomerAddress
@pytest.fixture
def shop_customer(db, test_vendor):
"""Create a test customer for shop API tests."""
from middleware.auth import AuthManager
auth_manager = AuthManager()
customer = Customer(
vendor_id=test_vendor.id,
email="shopcustomer@example.com",
hashed_password=auth_manager.hash_password("testpass123"),
first_name="Shop",
last_name="Customer",
customer_number="SHOP001",
is_active=True,
)
db.add(customer)
db.commit()
db.refresh(customer)
return customer
@pytest.fixture
def shop_customer_token(shop_customer, test_vendor):
"""Create JWT token for shop customer."""
from middleware.auth import AuthManager
auth_manager = AuthManager()
expires_delta = timedelta(minutes=auth_manager.token_expire_minutes)
expire = datetime.now(UTC) + expires_delta
payload = {
"sub": str(shop_customer.id),
"email": shop_customer.email,
"vendor_id": test_vendor.id,
"type": "customer",
"exp": expire,
"iat": datetime.now(UTC),
}
token = jwt.encode(
payload, auth_manager.secret_key, algorithm=auth_manager.algorithm
)
return token
@pytest.fixture
def shop_customer_headers(shop_customer_token):
"""Get authentication headers for shop customer."""
return {"Authorization": f"Bearer {shop_customer_token}"}
@pytest.fixture
def customer_address(db, test_vendor, shop_customer):
"""Create a test address for shop customer."""
address = CustomerAddress(
vendor_id=test_vendor.id,
customer_id=shop_customer.id,
address_type="shipping",
first_name="Ship",
last_name="Address",
address_line_1="123 Shipping St",
city="Luxembourg",
postal_code="L-1234",
country_name="Luxembourg",
country_iso="LU",
is_default=True,
)
db.add(address)
db.commit()
db.refresh(address)
return address
@pytest.fixture
def customer_billing_address(db, test_vendor, shop_customer):
"""Create a billing address for shop customer."""
address = CustomerAddress(
vendor_id=test_vendor.id,
customer_id=shop_customer.id,
address_type="billing",
first_name="Bill",
last_name="Address",
company="Test Company",
address_line_1="456 Billing Ave",
city="Esch-sur-Alzette",
postal_code="L-5678",
country_name="Luxembourg",
country_iso="LU",
is_default=True,
)
db.add(address)
db.commit()
db.refresh(address)
return address
@pytest.fixture
def other_customer(db, test_vendor):
"""Create another customer for testing access controls."""
from middleware.auth import AuthManager
auth_manager = AuthManager()
customer = Customer(
vendor_id=test_vendor.id,
email="othercustomer@example.com",
hashed_password=auth_manager.hash_password("otherpass123"),
first_name="Other",
last_name="Customer",
customer_number="OTHER001",
is_active=True,
)
db.add(customer)
db.commit()
db.refresh(customer)
return customer
@pytest.fixture
def other_customer_address(db, test_vendor, other_customer):
"""Create an address for another customer."""
address = CustomerAddress(
vendor_id=test_vendor.id,
customer_id=other_customer.id,
address_type="shipping",
first_name="Other",
last_name="Address",
address_line_1="999 Other St",
city="Differdange",
postal_code="L-9999",
country_name="Luxembourg",
country_iso="LU",
is_default=True,
)
db.add(address)
db.commit()
db.refresh(address)
return address
@pytest.mark.integration
@pytest.mark.api
@pytest.mark.shop
class TestShopAddressesListAPI:
"""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/storefront/addresses")
assert response.status_code in [401, 403]
def test_list_addresses_success(
self,
client,
shop_customer_headers,
customer_address,
test_vendor,
shop_customer,
):
"""Test listing customer addresses successfully."""
with patch("app.api.v1.shop.addresses.getattr") as mock_getattr:
mock_getattr.return_value = test_vendor
with patch("app.api.deps._validate_customer_token") as mock_validate:
mock_validate.return_value = shop_customer
response = client.get(
"/api/v1/storefront/addresses",
headers=shop_customer_headers,
)
assert response.status_code == 200
data = response.json()
assert "addresses" in data
assert "total" in data
assert data["total"] == 1
assert data["addresses"][0]["first_name"] == "Ship"
def test_list_addresses_empty(
self, client, shop_customer_headers, test_vendor, shop_customer
):
"""Test listing addresses when customer has none."""
with patch("app.api.v1.shop.addresses.getattr") as mock_getattr:
mock_getattr.return_value = test_vendor
with patch("app.api.deps._validate_customer_token") as mock_validate:
mock_validate.return_value = shop_customer
response = client.get(
"/api/v1/storefront/addresses",
headers=shop_customer_headers,
)
assert response.status_code == 200
data = response.json()
assert data["total"] == 0
assert data["addresses"] == []
def test_list_addresses_multiple_types(
self,
client,
shop_customer_headers,
customer_address,
customer_billing_address,
test_vendor,
shop_customer,
):
"""Test listing addresses includes both shipping and billing."""
with patch("app.api.v1.shop.addresses.getattr") as mock_getattr:
mock_getattr.return_value = test_vendor
with patch("app.api.deps._validate_customer_token") as mock_validate:
mock_validate.return_value = shop_customer
response = client.get(
"/api/v1/storefront/addresses",
headers=shop_customer_headers,
)
assert response.status_code == 200
data = response.json()
assert data["total"] == 2
types = {addr["address_type"] for addr in data["addresses"]}
assert "shipping" in types
assert "billing" in types
@pytest.mark.integration
@pytest.mark.api
@pytest.mark.shop
class TestShopAddressDetailAPI:
"""Test shop address detail endpoint at /api/v1/storefront/addresses/{address_id}."""
def test_get_address_success(
self,
client,
shop_customer_headers,
customer_address,
test_vendor,
shop_customer,
):
"""Test getting address details successfully."""
with patch("app.api.v1.shop.addresses.getattr") as mock_getattr:
mock_getattr.return_value = test_vendor
with patch("app.api.deps._validate_customer_token") as mock_validate:
mock_validate.return_value = shop_customer
response = client.get(
f"/api/v1/storefront/addresses/{customer_address.id}",
headers=shop_customer_headers,
)
assert response.status_code == 200
data = response.json()
assert data["id"] == customer_address.id
assert data["first_name"] == "Ship"
assert data["country_iso"] == "LU"
assert data["country_name"] == "Luxembourg"
def test_get_address_not_found(
self, client, shop_customer_headers, test_vendor, shop_customer
):
"""Test getting non-existent address returns 404."""
with patch("app.api.v1.shop.addresses.getattr") as mock_getattr:
mock_getattr.return_value = test_vendor
with patch("app.api.deps._validate_customer_token") as mock_validate:
mock_validate.return_value = shop_customer
response = client.get(
"/api/v1/storefront/addresses/99999",
headers=shop_customer_headers,
)
assert response.status_code == 404
def test_get_address_other_customer(
self,
client,
shop_customer_headers,
other_customer_address,
test_vendor,
shop_customer,
):
"""Test cannot access another customer's address."""
with patch("app.api.v1.shop.addresses.getattr") as mock_getattr:
mock_getattr.return_value = test_vendor
with patch("app.api.deps._validate_customer_token") as mock_validate:
mock_validate.return_value = shop_customer
response = client.get(
f"/api/v1/storefront/addresses/{other_customer_address.id}",
headers=shop_customer_headers,
)
# Should return 404 to prevent enumeration
assert response.status_code == 404
@pytest.mark.integration
@pytest.mark.api
@pytest.mark.shop
class TestShopAddressCreateAPI:
"""Test shop address creation at POST /api/v1/storefront/addresses."""
def test_create_address_success(
self, client, shop_customer_headers, test_vendor, shop_customer
):
"""Test creating a new address."""
address_data = {
"address_type": "shipping",
"first_name": "New",
"last_name": "Address",
"address_line_1": "789 New St",
"city": "Luxembourg",
"postal_code": "L-1111",
"country_name": "Luxembourg",
"country_iso": "LU",
"is_default": False,
}
with patch("app.api.v1.shop.addresses.getattr") as mock_getattr:
mock_getattr.return_value = test_vendor
with patch("app.api.deps._validate_customer_token") as mock_validate:
mock_validate.return_value = shop_customer
response = client.post(
"/api/v1/storefront/addresses",
headers=shop_customer_headers,
json=address_data,
)
assert response.status_code == 201
data = response.json()
assert data["first_name"] == "New"
assert data["last_name"] == "Address"
assert data["country_iso"] == "LU"
assert "id" in data
def test_create_address_with_company(
self, client, shop_customer_headers, test_vendor, shop_customer
):
"""Test creating address with company name."""
address_data = {
"address_type": "billing",
"first_name": "Business",
"last_name": "Address",
"company": "Acme Corp",
"address_line_1": "100 Business Park",
"city": "Luxembourg",
"postal_code": "L-2222",
"country_name": "Luxembourg",
"country_iso": "LU",
"is_default": True,
}
with patch("app.api.v1.shop.addresses.getattr") as mock_getattr:
mock_getattr.return_value = test_vendor
with patch("app.api.deps._validate_customer_token") as mock_validate:
mock_validate.return_value = shop_customer
response = client.post(
"/api/v1/storefront/addresses",
headers=shop_customer_headers,
json=address_data,
)
assert response.status_code == 201
data = response.json()
assert data["company"] == "Acme Corp"
def test_create_address_validation_error(
self, client, shop_customer_headers, test_vendor, shop_customer
):
"""Test validation error for missing required fields."""
address_data = {
"address_type": "shipping",
"first_name": "Test",
# Missing required fields
}
with patch("app.api.v1.shop.addresses.getattr") as mock_getattr:
mock_getattr.return_value = test_vendor
with patch("app.api.deps._validate_customer_token") as mock_validate:
mock_validate.return_value = shop_customer
response = client.post(
"/api/v1/storefront/addresses",
headers=shop_customer_headers,
json=address_data,
)
assert response.status_code == 422
@pytest.mark.integration
@pytest.mark.api
@pytest.mark.shop
class TestShopAddressUpdateAPI:
"""Test shop address update at PUT /api/v1/storefront/addresses/{address_id}."""
def test_update_address_success(
self,
client,
shop_customer_headers,
customer_address,
test_vendor,
shop_customer,
):
"""Test updating an address."""
update_data = {
"first_name": "Updated",
"city": "Esch-sur-Alzette",
}
with patch("app.api.v1.shop.addresses.getattr") as mock_getattr:
mock_getattr.return_value = test_vendor
with patch("app.api.deps._validate_customer_token") as mock_validate:
mock_validate.return_value = shop_customer
response = client.put(
f"/api/v1/storefront/addresses/{customer_address.id}",
headers=shop_customer_headers,
json=update_data,
)
assert response.status_code == 200
data = response.json()
assert data["first_name"] == "Updated"
assert data["city"] == "Esch-sur-Alzette"
def test_update_address_not_found(
self, client, shop_customer_headers, test_vendor, shop_customer
):
"""Test updating non-existent address returns 404."""
update_data = {"first_name": "Test"}
with patch("app.api.v1.shop.addresses.getattr") as mock_getattr:
mock_getattr.return_value = test_vendor
with patch("app.api.deps._validate_customer_token") as mock_validate:
mock_validate.return_value = shop_customer
response = client.put(
"/api/v1/storefront/addresses/99999",
headers=shop_customer_headers,
json=update_data,
)
assert response.status_code == 404
def test_update_address_other_customer(
self,
client,
shop_customer_headers,
other_customer_address,
test_vendor,
shop_customer,
):
"""Test cannot update another customer's address."""
update_data = {"first_name": "Hacked"}
with patch("app.api.v1.shop.addresses.getattr") as mock_getattr:
mock_getattr.return_value = test_vendor
with patch("app.api.deps._validate_customer_token") as mock_validate:
mock_validate.return_value = shop_customer
response = client.put(
f"/api/v1/storefront/addresses/{other_customer_address.id}",
headers=shop_customer_headers,
json=update_data,
)
assert response.status_code == 404
@pytest.mark.integration
@pytest.mark.api
@pytest.mark.shop
class TestShopAddressDeleteAPI:
"""Test shop address deletion at DELETE /api/v1/storefront/addresses/{address_id}."""
def test_delete_address_success(
self,
client,
shop_customer_headers,
customer_address,
test_vendor,
shop_customer,
):
"""Test deleting an address."""
with patch("app.api.v1.shop.addresses.getattr") as mock_getattr:
mock_getattr.return_value = test_vendor
with patch("app.api.deps._validate_customer_token") as mock_validate:
mock_validate.return_value = shop_customer
response = client.delete(
f"/api/v1/storefront/addresses/{customer_address.id}",
headers=shop_customer_headers,
)
assert response.status_code == 204
def test_delete_address_not_found(
self, client, shop_customer_headers, test_vendor, shop_customer
):
"""Test deleting non-existent address returns 404."""
with patch("app.api.v1.shop.addresses.getattr") as mock_getattr:
mock_getattr.return_value = test_vendor
with patch("app.api.deps._validate_customer_token") as mock_validate:
mock_validate.return_value = shop_customer
response = client.delete(
"/api/v1/storefront/addresses/99999",
headers=shop_customer_headers,
)
assert response.status_code == 404
def test_delete_address_other_customer(
self,
client,
shop_customer_headers,
other_customer_address,
test_vendor,
shop_customer,
):
"""Test cannot delete another customer's address."""
with patch("app.api.v1.shop.addresses.getattr") as mock_getattr:
mock_getattr.return_value = test_vendor
with patch("app.api.deps._validate_customer_token") as mock_validate:
mock_validate.return_value = shop_customer
response = client.delete(
f"/api/v1/storefront/addresses/{other_customer_address.id}",
headers=shop_customer_headers,
)
assert response.status_code == 404
@pytest.mark.integration
@pytest.mark.api
@pytest.mark.shop
class TestShopAddressSetDefaultAPI:
"""Test set address as default at PUT /api/v1/storefront/addresses/{address_id}/default."""
def test_set_default_success(
self,
client,
shop_customer_headers,
customer_address,
test_vendor,
shop_customer,
db,
):
"""Test setting address as default."""
# Create a second non-default address
second_address = CustomerAddress(
vendor_id=test_vendor.id,
customer_id=shop_customer.id,
address_type="shipping",
first_name="Second",
last_name="Address",
address_line_1="222 Second St",
city="Dudelange",
postal_code="L-3333",
country_name="Luxembourg",
country_iso="LU",
is_default=False,
)
db.add(second_address)
db.commit()
db.refresh(second_address)
with patch("app.api.v1.shop.addresses.getattr") as mock_getattr:
mock_getattr.return_value = test_vendor
with patch("app.api.deps._validate_customer_token") as mock_validate:
mock_validate.return_value = shop_customer
response = client.put(
f"/api/v1/storefront/addresses/{second_address.id}/default",
headers=shop_customer_headers,
)
assert response.status_code == 200
data = response.json()
assert data["is_default"] is True
def test_set_default_not_found(
self, client, shop_customer_headers, test_vendor, shop_customer
):
"""Test setting default on non-existent address returns 404."""
with patch("app.api.v1.shop.addresses.getattr") as mock_getattr:
mock_getattr.return_value = test_vendor
with patch("app.api.deps._validate_customer_token") as mock_validate:
mock_validate.return_value = shop_customer
response = client.put(
"/api/v1/storefront/addresses/99999/default",
headers=shop_customer_headers,
)
assert response.status_code == 404
def test_set_default_other_customer(
self,
client,
shop_customer_headers,
other_customer_address,
test_vendor,
shop_customer,
):
"""Test cannot set default on another customer's address."""
with patch("app.api.v1.shop.addresses.getattr") as mock_getattr:
mock_getattr.return_value = test_vendor
with patch("app.api.deps._validate_customer_token") as mock_validate:
mock_validate.return_value = shop_customer
response = client.put(
f"/api/v1/storefront/addresses/{other_customer_address.id}/default",
headers=shop_customer_headers,
)
assert response.status_code == 404