Some checks failed
Consolidate User.role (2-value: admin/store) + User.is_super_admin (boolean) into a single 4-value UserRole enum: super_admin, platform_admin, merchant_owner, store_member. Drop stale StoreUser.user_type column. Fix role="user" bug in merchant creation. Key changes: - Expand UserRole enum from 2 to 4 values with computed properties (is_admin, is_super_admin, is_platform_admin, is_merchant_owner, is_store_user) - Add Alembic migration (tenancy_003) for data migration + column drops - Remove is_super_admin from JWT token payload - Update all auth dependencies, services, routes, templates, JS, and tests - Update all RBAC documentation 66 files changed, 1219 unit tests passing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
188 lines
6.2 KiB
Python
188 lines
6.2 KiB
Python
# tests/integration/api/v1/test_store_api_dashboard.py
|
|
"""
|
|
Integration tests for store dashboard API endpoints.
|
|
|
|
Tests cover:
|
|
1. Dashboard stats retrieval
|
|
2. Store-specific data isolation
|
|
3. Permission checks
|
|
4. Data accuracy
|
|
"""
|
|
|
|
import pytest
|
|
|
|
|
|
@pytest.mark.integration
|
|
@pytest.mark.api
|
|
@pytest.mark.store
|
|
class TestStoreDashboardAPI:
|
|
"""Test store dashboard stats endpoint"""
|
|
|
|
def test_get_dashboard_stats_structure(
|
|
self, client, store_user_headers, test_store_with_store_user, db
|
|
):
|
|
"""Test dashboard stats returns correct data structure"""
|
|
response = client.get(
|
|
"/api/v1/store/dashboard/stats", headers=store_user_headers
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
# Verify top-level structure
|
|
assert "store" in data
|
|
assert "products" in data
|
|
assert "orders" in data
|
|
assert "customers" in data
|
|
assert "revenue" in data
|
|
|
|
# Verify store info
|
|
assert "id" in data["store"]
|
|
assert "name" in data["store"]
|
|
assert "store_code" in data["store"]
|
|
assert data["store"]["id"] == test_store_with_store_user.id
|
|
|
|
# Verify products stats
|
|
assert "total" in data["products"]
|
|
assert "active" in data["products"]
|
|
assert isinstance(data["products"]["total"], int)
|
|
assert isinstance(data["products"]["active"], int)
|
|
|
|
# Verify orders stats
|
|
assert "total" in data["orders"]
|
|
assert "pending" in data["orders"]
|
|
assert "completed" in data["orders"]
|
|
|
|
# Verify customers stats
|
|
assert "total" in data["customers"]
|
|
assert "active" in data["customers"]
|
|
|
|
# Verify revenue stats
|
|
assert "total" in data["revenue"]
|
|
assert "this_month" in data["revenue"]
|
|
|
|
def test_dashboard_stats_without_store_association(self, client, db, auth_manager):
|
|
"""Test dashboard stats for user not associated with any store"""
|
|
import uuid
|
|
|
|
from app.modules.tenancy.models import User
|
|
|
|
# Create store user without store association
|
|
hashed_password = auth_manager.hash_password("testpass123")
|
|
orphan_user = User(
|
|
email=f"orphan_{uuid.uuid4().hex[:8]}@example.com",
|
|
username=f"orphanstore_{uuid.uuid4().hex[:8]}",
|
|
hashed_password=hashed_password,
|
|
role="merchant_owner",
|
|
is_active=True,
|
|
)
|
|
db.add(orphan_user)
|
|
db.commit()
|
|
db.refresh(orphan_user)
|
|
|
|
# Get token
|
|
token_data = auth_manager.create_access_token(orphan_user)
|
|
headers = {"Authorization": f"Bearer {token_data['access_token']}"}
|
|
|
|
# Try to get dashboard stats
|
|
response = client.get("/api/v1/store/dashboard/stats", headers=headers)
|
|
|
|
# Should fail - user not associated with store (401 if no store context, 403 if forbidden)
|
|
assert response.status_code in [401, 403, 404]
|
|
|
|
def test_dashboard_stats_with_inactive_store(
|
|
self, client, db, test_store_user, test_merchant, auth_manager
|
|
):
|
|
"""Test dashboard stats for inactive store"""
|
|
import uuid
|
|
|
|
from app.modules.tenancy.models import Store, StoreUser
|
|
|
|
# Create inactive store
|
|
unique_code = f"INACTIVE_{uuid.uuid4().hex[:8].upper()}"
|
|
store = Store(
|
|
store_code=unique_code,
|
|
subdomain=f"inactive-{uuid.uuid4().hex[:8]}",
|
|
name="Inactive Store",
|
|
merchant_id=test_merchant.id,
|
|
is_active=False, # Inactive
|
|
is_verified=True,
|
|
)
|
|
db.add(store)
|
|
db.commit()
|
|
|
|
# Associate with user (ownership determined via Merchant.owner_user_id)
|
|
store_user = StoreUser(
|
|
store_id=store.id,
|
|
user_id=test_store_user.id,
|
|
is_active=True,
|
|
)
|
|
db.add(store_user)
|
|
db.commit()
|
|
|
|
# Get token
|
|
token_data = auth_manager.create_access_token(test_store_user)
|
|
headers = {"Authorization": f"Bearer {token_data['access_token']}"}
|
|
|
|
# Try to get dashboard stats
|
|
response = client.get("/api/v1/store/dashboard/stats", headers=headers)
|
|
|
|
# Should fail - store is inactive (could be 401, 403 or 404 depending on implementation)
|
|
assert response.status_code in [401, 403, 404]
|
|
|
|
def test_dashboard_stats_empty_store(
|
|
self, client, store_user_headers, test_store_with_store_user
|
|
):
|
|
"""Test dashboard stats for store with no data"""
|
|
response = client.get(
|
|
"/api/v1/store/dashboard/stats", headers=store_user_headers
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
|
|
# Should return zeros for empty store
|
|
assert data["products"]["total"] == 0
|
|
assert data["products"]["active"] == 0
|
|
assert data["orders"]["total"] == 0
|
|
assert data["customers"]["total"] == 0
|
|
assert data["revenue"]["total"] == 0
|
|
|
|
def test_dashboard_stats_response_time(
|
|
self, client, store_user_headers, test_store_with_store_user
|
|
):
|
|
"""Test that dashboard stats responds quickly"""
|
|
import time
|
|
|
|
start_time = time.time()
|
|
response = client.get(
|
|
"/api/v1/store/dashboard/stats", headers=store_user_headers
|
|
)
|
|
end_time = time.time()
|
|
|
|
assert response.status_code == 200
|
|
# Should respond in less than 2 seconds
|
|
assert (end_time - start_time) < 2.0
|
|
|
|
def test_dashboard_stats_caching_behavior(
|
|
self, client, store_user_headers, test_store_with_store_user
|
|
):
|
|
"""Test that dashboard stats can be called multiple times"""
|
|
# Make multiple requests
|
|
responses = []
|
|
for _ in range(3):
|
|
response = client.get(
|
|
"/api/v1/store/dashboard/stats", headers=store_user_headers
|
|
)
|
|
responses.append(response)
|
|
|
|
# All should succeed
|
|
for response in responses:
|
|
assert response.status_code == 200
|
|
|
|
# All should return consistent data
|
|
data_list = [r.json() for r in responses]
|
|
for data in data_list[1:]:
|
|
assert data["store"]["id"] == data_list[0]["store"]["id"]
|
|
assert data["products"]["total"] == data_list[0]["products"]["total"]
|