Adding vendor api tests
This commit is contained in:
32
tests/integration/api/v1/README.md
Normal file
32
tests/integration/api/v1/README.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# API v1 Integration Tests
|
||||
|
||||
## Documentation
|
||||
|
||||
For comprehensive test structure documentation, please see:
|
||||
|
||||
**[Test Structure Guide](https://yourusername.github.io/wizamart/testing/test-structure/)** in MkDocs
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Run all v1 API tests
|
||||
pytest tests/integration/api/v1/ -v
|
||||
|
||||
# Run specific area
|
||||
pytest tests/integration/api/v1/vendor/ -v
|
||||
pytest tests/integration/api/v1/admin/ -v
|
||||
pytest tests/integration/api/v1/public/ -v
|
||||
pytest tests/integration/api/v1/shared/ -v
|
||||
```
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
tests/integration/api/v1/
|
||||
├── admin/ # Admin API tests
|
||||
├── vendor/ # Vendor API tests
|
||||
├── public/ # Public API tests
|
||||
└── shared/ # Shared/common tests
|
||||
```
|
||||
|
||||
See full documentation: [Test Structure Guide](https://yourusername.github.io/wizamart/testing/test-structure/)
|
||||
20
tests/integration/api/v1/admin/README.md
Normal file
20
tests/integration/api/v1/admin/README.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Admin API Integration Tests
|
||||
|
||||
## Documentation
|
||||
|
||||
For comprehensive testing documentation, see:
|
||||
|
||||
**[Test Structure Guide](https://yourusername.github.io/wizamart/testing/test-structure/)** in MkDocs
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Run all admin tests
|
||||
pytest tests/integration/api/v1/admin/ -v
|
||||
```
|
||||
|
||||
## Status
|
||||
|
||||
⚠️ Tests to be migrated from legacy test files.
|
||||
|
||||
See [Test Structure Guide](https://yourusername.github.io/wizamart/testing/test-structure/) for migration details.
|
||||
0
tests/integration/api/v1/admin/__init__.py
Normal file
0
tests/integration/api/v1/admin/__init__.py
Normal file
20
tests/integration/api/v1/public/README.md
Normal file
20
tests/integration/api/v1/public/README.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Public API Integration Tests
|
||||
|
||||
## Documentation
|
||||
|
||||
For comprehensive testing documentation, see:
|
||||
|
||||
**[Test Structure Guide](https://yourusername.github.io/wizamart/testing/test-structure/)** in MkDocs
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Run all public tests
|
||||
pytest tests/integration/api/v1/public/ -v
|
||||
```
|
||||
|
||||
## Status
|
||||
|
||||
⚠️ Tests to be created.
|
||||
|
||||
See [Test Structure Guide](https://yourusername.github.io/wizamart/testing/test-structure/) for details.
|
||||
0
tests/integration/api/v1/public/__init__.py
Normal file
0
tests/integration/api/v1/public/__init__.py
Normal file
20
tests/integration/api/v1/shared/README.md
Normal file
20
tests/integration/api/v1/shared/README.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Shared API Integration Tests
|
||||
|
||||
## Documentation
|
||||
|
||||
For comprehensive testing documentation, see:
|
||||
|
||||
**[Test Structure Guide](https://yourusername.github.io/wizamart/testing/test-structure/)** in MkDocs
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Run all shared tests
|
||||
pytest tests/integration/api/v1/shared/ -v
|
||||
```
|
||||
|
||||
## Status
|
||||
|
||||
⚠️ Tests to be migrated from legacy test files (auth, pagination, filtering).
|
||||
|
||||
See [Test Structure Guide](https://yourusername.github.io/wizamart/testing/test-structure/) for migration details.
|
||||
0
tests/integration/api/v1/shared/__init__.py
Normal file
0
tests/integration/api/v1/shared/__init__.py
Normal file
36
tests/integration/api/v1/vendor/README.md
vendored
Normal file
36
tests/integration/api/v1/vendor/README.md
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
# Vendor API Integration Tests
|
||||
|
||||
## Documentation
|
||||
|
||||
For comprehensive vendor API testing documentation, please see:
|
||||
|
||||
**[Vendor API Testing Guide](https://yourusername.github.io/wizamart/testing/vendor-api-testing/)** in MkDocs
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Run all vendor tests
|
||||
pytest tests/integration/api/v1/vendor/ -v
|
||||
|
||||
# Run with coverage
|
||||
pytest tests/integration/api/v1/vendor/ \
|
||||
--cov=app/api/v1/vendor \
|
||||
--cov-report=html
|
||||
```
|
||||
|
||||
## Test Files
|
||||
|
||||
- `test_authentication.py` - Authentication tests (30+ tests)
|
||||
- `test_dashboard.py` - Dashboard stats tests (12 tests)
|
||||
|
||||
## Fixtures
|
||||
|
||||
Key fixtures for vendor testing:
|
||||
|
||||
- `vendor_user_headers` - Authentication headers for vendor API
|
||||
- `test_vendor_with_vendor_user` - Vendor with VendorUser association
|
||||
|
||||
## See Also
|
||||
|
||||
- [Vendor API Testing Guide](https://yourusername.github.io/wizamart/testing/vendor-api-testing/) - Full documentation
|
||||
- [Test Structure](https://yourusername.github.io/wizamart/testing/test-structure/) - Overall test organization
|
||||
0
tests/integration/api/v1/vendor/__init__.py
vendored
Normal file
0
tests/integration/api/v1/vendor/__init__.py
vendored
Normal file
384
tests/integration/api/v1/vendor/test_authentication.py
vendored
Normal file
384
tests/integration/api/v1/vendor/test_authentication.py
vendored
Normal file
@@ -0,0 +1,384 @@
|
||||
# tests/integration/api/v1/test_vendor_api_authentication.py
|
||||
"""
|
||||
Integration tests for vendor API authentication using get_current_vendor_api.
|
||||
|
||||
These tests verify that:
|
||||
1. Vendor API endpoints require Authorization header (not cookies)
|
||||
2. Only vendor-role users can access vendor API endpoints
|
||||
3. Admin users are blocked from vendor API routes
|
||||
4. Invalid/expired tokens are rejected
|
||||
5. Vendor context middleware works correctly with API authentication
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from jose import jwt
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.api
|
||||
@pytest.mark.vendor
|
||||
@pytest.mark.auth
|
||||
class TestVendorAPIAuthentication:
|
||||
"""Test authentication for vendor API endpoints using get_current_vendor_api"""
|
||||
|
||||
# ========================================================================
|
||||
# Authentication Tests - /api/v1/vendor/auth/me
|
||||
# ========================================================================
|
||||
|
||||
def test_vendor_auth_me_success(self, client, vendor_user_headers, test_vendor_user):
|
||||
"""Test /auth/me endpoint with valid vendor user token"""
|
||||
response = client.get("/api/v1/vendor/auth/me", headers=vendor_user_headers)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["username"] == test_vendor_user.username
|
||||
assert data["email"] == test_vendor_user.email
|
||||
assert data["role"] == "vendor"
|
||||
assert data["is_active"] is True
|
||||
|
||||
def test_vendor_auth_me_without_token(self, client):
|
||||
"""Test /auth/me endpoint without authorization header"""
|
||||
response = client.get("/api/v1/vendor/auth/me")
|
||||
|
||||
assert response.status_code == 401
|
||||
data = response.json()
|
||||
assert data["error_code"] == "INVALID_TOKEN"
|
||||
assert "Authorization header required" in data["message"]
|
||||
|
||||
def test_vendor_auth_me_invalid_token(self, client):
|
||||
"""Test /auth/me endpoint with invalid token format"""
|
||||
response = client.get(
|
||||
"/api/v1/vendor/auth/me",
|
||||
headers={"Authorization": "Bearer invalid_token_here"}
|
||||
)
|
||||
|
||||
assert response.status_code == 401
|
||||
data = response.json()
|
||||
assert data["error_code"] == "INVALID_TOKEN"
|
||||
|
||||
def test_vendor_auth_me_with_admin_token(self, client, admin_headers, test_admin):
|
||||
"""Test /auth/me endpoint rejects admin users"""
|
||||
response = client.get("/api/v1/vendor/auth/me", headers=admin_headers)
|
||||
|
||||
assert response.status_code == 403
|
||||
data = response.json()
|
||||
assert data["error_code"] == "FORBIDDEN"
|
||||
assert "Admin users cannot access vendor API" in data["message"]
|
||||
|
||||
def test_vendor_auth_me_with_regular_user_token(self, client, auth_headers, test_user):
|
||||
"""Test /auth/me endpoint rejects regular users"""
|
||||
response = client.get("/api/v1/vendor/auth/me", headers=auth_headers)
|
||||
|
||||
assert response.status_code == 403
|
||||
data = response.json()
|
||||
assert data["error_code"] == "FORBIDDEN"
|
||||
assert "vendor API routes" in data["message"].lower()
|
||||
|
||||
def test_vendor_auth_me_expired_token(self, client, test_vendor_user, auth_manager):
|
||||
"""Test /auth/me endpoint with expired token"""
|
||||
# Create expired token
|
||||
expired_payload = {
|
||||
"sub": str(test_vendor_user.id),
|
||||
"username": test_vendor_user.username,
|
||||
"email": test_vendor_user.email,
|
||||
"role": test_vendor_user.role,
|
||||
"exp": datetime.now(timezone.utc) - timedelta(hours=1),
|
||||
"iat": datetime.now(timezone.utc) - timedelta(hours=2),
|
||||
}
|
||||
|
||||
expired_token = jwt.encode(
|
||||
expired_payload,
|
||||
auth_manager.secret_key,
|
||||
algorithm=auth_manager.algorithm
|
||||
)
|
||||
|
||||
response = client.get(
|
||||
"/api/v1/vendor/auth/me",
|
||||
headers={"Authorization": f"Bearer {expired_token}"}
|
||||
)
|
||||
|
||||
assert response.status_code == 401
|
||||
data = response.json()
|
||||
assert data["error_code"] == "TOKEN_EXPIRED"
|
||||
|
||||
# ========================================================================
|
||||
# Dashboard Stats Endpoint Tests - /api/v1/vendor/dashboard/stats
|
||||
# ========================================================================
|
||||
|
||||
def test_vendor_dashboard_stats_success(
|
||||
self, client, vendor_user_headers, test_vendor_with_vendor_user, db
|
||||
):
|
||||
"""Test dashboard stats with valid vendor authentication"""
|
||||
response = client.get(
|
||||
"/api/v1/vendor/dashboard/stats",
|
||||
headers=vendor_user_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "vendor" in data
|
||||
assert "products" in data
|
||||
assert "orders" in data
|
||||
assert "customers" in data
|
||||
assert "revenue" in data
|
||||
|
||||
def test_vendor_dashboard_stats_without_auth(self, client):
|
||||
"""Test dashboard stats without authentication"""
|
||||
response = client.get("/api/v1/vendor/dashboard/stats")
|
||||
|
||||
assert response.status_code == 401
|
||||
|
||||
def test_vendor_dashboard_stats_with_admin(self, client, admin_headers):
|
||||
"""Test dashboard stats rejects admin users"""
|
||||
response = client.get(
|
||||
"/api/v1/vendor/dashboard/stats",
|
||||
headers=admin_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 403
|
||||
data = response.json()
|
||||
assert "Admin users cannot access vendor API" in data["message"]
|
||||
|
||||
def test_vendor_dashboard_stats_with_cookie_only(self, client, test_vendor_user):
|
||||
"""Test dashboard stats does not accept cookie authentication"""
|
||||
# Login to get session cookie
|
||||
login_response = client.post(
|
||||
"/api/v1/vendor/auth/login",
|
||||
json={
|
||||
"username": test_vendor_user.username,
|
||||
"password": "vendorpass123"
|
||||
}
|
||||
)
|
||||
assert login_response.status_code == 200
|
||||
|
||||
# Try to access API endpoint with just cookies (no Authorization header)
|
||||
response = client.get("/api/v1/vendor/dashboard/stats")
|
||||
|
||||
# Should fail because get_current_vendor_api requires Authorization header
|
||||
assert response.status_code == 401
|
||||
|
||||
# ========================================================================
|
||||
# CSRF Protection Tests
|
||||
# ========================================================================
|
||||
|
||||
def test_csrf_protection_api_endpoints_require_header(
|
||||
self, client, test_vendor_user
|
||||
):
|
||||
"""Test that API endpoints require Authorization header (CSRF protection)"""
|
||||
# Get a valid session by logging in
|
||||
login_response = client.post(
|
||||
"/api/v1/vendor/auth/login",
|
||||
json={
|
||||
"username": test_vendor_user.username,
|
||||
"password": "vendorpass123"
|
||||
}
|
||||
)
|
||||
assert login_response.status_code == 200
|
||||
|
||||
# List of vendor API endpoints that should require header auth
|
||||
api_endpoints = [
|
||||
"/api/v1/vendor/auth/me",
|
||||
"/api/v1/vendor/dashboard/stats",
|
||||
"/api/v1/vendor/products",
|
||||
"/api/v1/vendor/orders",
|
||||
"/api/v1/vendor/profile",
|
||||
"/api/v1/vendor/settings",
|
||||
]
|
||||
|
||||
for endpoint in api_endpoints:
|
||||
# Try to access with just session cookie (no Authorization header)
|
||||
response = client.get(endpoint)
|
||||
|
||||
# All should fail with 401 (header required)
|
||||
assert response.status_code == 401, \
|
||||
f"Endpoint {endpoint} should reject cookie-only auth"
|
||||
|
||||
# ========================================================================
|
||||
# Role-Based Access Control Tests
|
||||
# ========================================================================
|
||||
|
||||
def test_vendor_endpoints_block_non_vendor_roles(
|
||||
self, client, auth_headers, admin_headers
|
||||
):
|
||||
"""Test that vendor API endpoints block non-vendor users"""
|
||||
endpoints = [
|
||||
"/api/v1/vendor/auth/me",
|
||||
"/api/v1/vendor/dashboard/stats",
|
||||
"/api/v1/vendor/profile",
|
||||
]
|
||||
|
||||
for endpoint in endpoints:
|
||||
# Test with regular user token
|
||||
response = client.get(endpoint, headers=auth_headers)
|
||||
assert response.status_code == 403, \
|
||||
f"Endpoint {endpoint} should reject regular users"
|
||||
|
||||
# Test with admin token
|
||||
response = client.get(endpoint, headers=admin_headers)
|
||||
assert response.status_code == 403, \
|
||||
f"Endpoint {endpoint} should reject admin users"
|
||||
|
||||
def test_vendor_api_accepts_only_vendor_role(
|
||||
self, client, vendor_user_headers, test_vendor_user
|
||||
):
|
||||
"""Test that vendor API endpoints accept vendor-role users"""
|
||||
endpoints = [
|
||||
"/api/v1/vendor/auth/me",
|
||||
]
|
||||
|
||||
for endpoint in endpoints:
|
||||
response = client.get(endpoint, headers=vendor_user_headers)
|
||||
assert response.status_code in [200, 404], \
|
||||
f"Endpoint {endpoint} should accept vendor users (got {response.status_code})"
|
||||
|
||||
# ========================================================================
|
||||
# Token Validation Tests
|
||||
# ========================================================================
|
||||
|
||||
def test_malformed_authorization_header(self, client):
|
||||
"""Test various malformed Authorization headers"""
|
||||
malformed_headers = [
|
||||
{"Authorization": "InvalidFormat token123"},
|
||||
{"Authorization": "Bearer"}, # Missing token
|
||||
{"Authorization": "bearer token123"}, # Wrong case
|
||||
{"Authorization": " Bearer token123"}, # Leading space
|
||||
{"Authorization": "Bearer token123"}, # Double space
|
||||
]
|
||||
|
||||
for headers in malformed_headers:
|
||||
response = client.get("/api/v1/vendor/auth/me", headers=headers)
|
||||
assert response.status_code == 401, \
|
||||
f"Should reject malformed header: {headers}"
|
||||
|
||||
def test_token_with_missing_claims(self, client, auth_manager):
|
||||
"""Test token missing required claims"""
|
||||
# Create token without 'role' claim
|
||||
invalid_payload = {
|
||||
"sub": "123",
|
||||
"username": "test",
|
||||
"exp": datetime.now(timezone.utc) + timedelta(hours=1),
|
||||
}
|
||||
|
||||
invalid_token = jwt.encode(
|
||||
invalid_payload,
|
||||
auth_manager.secret_key,
|
||||
algorithm=auth_manager.algorithm
|
||||
)
|
||||
|
||||
response = client.get(
|
||||
"/api/v1/vendor/auth/me",
|
||||
headers={"Authorization": f"Bearer {invalid_token}"}
|
||||
)
|
||||
|
||||
assert response.status_code == 401
|
||||
|
||||
# ========================================================================
|
||||
# Edge Cases
|
||||
# ========================================================================
|
||||
|
||||
def test_inactive_vendor_user(self, client, db, test_vendor_user, auth_manager):
|
||||
"""Test that inactive vendor users are rejected"""
|
||||
# Deactivate the vendor user
|
||||
test_vendor_user.is_active = False
|
||||
db.add(test_vendor_user)
|
||||
db.commit()
|
||||
|
||||
# Create token for inactive user
|
||||
token_data = auth_manager.create_access_token(test_vendor_user)
|
||||
headers = {"Authorization": f"Bearer {token_data['access_token']}"}
|
||||
|
||||
response = client.get("/api/v1/vendor/auth/me", headers=headers)
|
||||
|
||||
# Should fail because user is inactive
|
||||
assert response.status_code in [401, 403, 404]
|
||||
|
||||
# Reactivate for cleanup
|
||||
test_vendor_user.is_active = True
|
||||
db.add(test_vendor_user)
|
||||
db.commit()
|
||||
|
||||
def test_concurrent_requests_with_same_token(
|
||||
self, client, vendor_user_headers
|
||||
):
|
||||
"""Test that the same token can be used for multiple concurrent requests"""
|
||||
# Make multiple requests with the same token
|
||||
responses = []
|
||||
for _ in range(5):
|
||||
response = client.get("/api/v1/vendor/auth/me", headers=vendor_user_headers)
|
||||
responses.append(response)
|
||||
|
||||
# All should succeed
|
||||
for response in responses:
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_vendor_api_with_empty_authorization_header(self, client):
|
||||
"""Test vendor API with empty Authorization header value"""
|
||||
response = client.get(
|
||||
"/api/v1/vendor/auth/me",
|
||||
headers={"Authorization": ""}
|
||||
)
|
||||
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.api
|
||||
@pytest.mark.vendor
|
||||
class TestVendorAPIConsistency:
|
||||
"""Test that all vendor API endpoints use consistent authentication"""
|
||||
|
||||
def test_all_vendor_endpoints_require_header_auth(
|
||||
self, client, test_vendor_user
|
||||
):
|
||||
"""Verify all vendor API endpoints require Authorization header"""
|
||||
# Login to establish session
|
||||
client.post(
|
||||
"/api/v1/vendor/auth/login",
|
||||
json={
|
||||
"username": test_vendor_user.username,
|
||||
"password": "vendorpass123"
|
||||
}
|
||||
)
|
||||
|
||||
# All vendor API endpoints (excluding public endpoints like /info)
|
||||
vendor_api_endpoints = [
|
||||
("/api/v1/vendor/auth/me", "GET"),
|
||||
("/api/v1/vendor/dashboard/stats", "GET"),
|
||||
("/api/v1/vendor/profile", "GET"),
|
||||
("/api/v1/vendor/settings", "GET"),
|
||||
("/api/v1/vendor/products", "GET"),
|
||||
("/api/v1/vendor/orders", "GET"),
|
||||
("/api/v1/vendor/customers", "GET"),
|
||||
("/api/v1/vendor/inventory", "GET"),
|
||||
("/api/v1/vendor/analytics", "GET"),
|
||||
]
|
||||
|
||||
for endpoint, method in vendor_api_endpoints:
|
||||
if method == "GET":
|
||||
response = client.get(endpoint)
|
||||
elif method == "POST":
|
||||
response = client.post(endpoint, json={})
|
||||
|
||||
# All should reject cookie-only auth with 401
|
||||
assert response.status_code == 401, \
|
||||
f"Endpoint {endpoint} should require Authorization header (got {response.status_code})"
|
||||
|
||||
def test_vendor_endpoints_accept_vendor_token(
|
||||
self, client, vendor_user_headers, test_vendor_with_vendor_user
|
||||
):
|
||||
"""Verify all vendor API endpoints accept valid vendor tokens"""
|
||||
# Endpoints that should work with just vendor authentication
|
||||
# (may return 404 or other errors due to missing data, but not 401/403)
|
||||
vendor_api_endpoints = [
|
||||
"/api/v1/vendor/auth/me",
|
||||
"/api/v1/vendor/profile",
|
||||
"/api/v1/vendor/settings",
|
||||
]
|
||||
|
||||
for endpoint in vendor_api_endpoints:
|
||||
response = client.get(endpoint, headers=vendor_user_headers)
|
||||
|
||||
# Should not be authentication/authorization errors
|
||||
assert response.status_code not in [401, 403], \
|
||||
f"Endpoint {endpoint} should accept vendor token (got {response.status_code}: {response.text})"
|
||||
300
tests/integration/api/v1/vendor/test_dashboard.py
vendored
Normal file
300
tests/integration/api/v1/vendor/test_dashboard.py
vendored
Normal file
@@ -0,0 +1,300 @@
|
||||
# tests/integration/api/v1/test_vendor_api_dashboard.py
|
||||
"""
|
||||
Integration tests for vendor dashboard API endpoints.
|
||||
|
||||
Tests cover:
|
||||
1. Dashboard stats retrieval
|
||||
2. Vendor-specific data isolation
|
||||
3. Permission checks
|
||||
4. Data accuracy
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.api
|
||||
@pytest.mark.vendor
|
||||
class TestVendorDashboardAPI:
|
||||
"""Test vendor dashboard stats endpoint"""
|
||||
|
||||
def test_get_dashboard_stats_structure(
|
||||
self, client, vendor_user_headers, test_vendor_with_vendor_user, db
|
||||
):
|
||||
"""Test dashboard stats returns correct data structure"""
|
||||
response = client.get(
|
||||
"/api/v1/vendor/dashboard/stats",
|
||||
headers=vendor_user_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
||||
# Verify top-level structure
|
||||
assert "vendor" in data
|
||||
assert "products" in data
|
||||
assert "orders" in data
|
||||
assert "customers" in data
|
||||
assert "revenue" in data
|
||||
|
||||
# Verify vendor info
|
||||
assert "id" in data["vendor"]
|
||||
assert "name" in data["vendor"]
|
||||
assert "vendor_code" in data["vendor"]
|
||||
assert data["vendor"]["id"] == test_vendor_with_vendor_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_vendor_isolation(
|
||||
self, client, db, test_vendor_user, auth_manager
|
||||
):
|
||||
"""Test that dashboard stats only show data for the authenticated vendor"""
|
||||
from models.database.vendor import Vendor, VendorUser
|
||||
from models.database.product import Product
|
||||
from models.database.marketplace_product import MarketplaceProduct
|
||||
|
||||
# Create two separate vendors with different data
|
||||
vendor1 = Vendor(
|
||||
vendor_code="VENDOR1",
|
||||
subdomain="vendor1",
|
||||
name="Vendor One",
|
||||
owner_user_id=test_vendor_user.id,
|
||||
is_active=True,
|
||||
is_verified=True,
|
||||
)
|
||||
db.add(vendor1)
|
||||
db.commit()
|
||||
db.refresh(vendor1)
|
||||
|
||||
# Associate vendor1 with test_vendor_user
|
||||
vendor_user1 = VendorUser(
|
||||
vendor_id=vendor1.id,
|
||||
user_id=test_vendor_user.id,
|
||||
is_owner=True,
|
||||
is_active=True,
|
||||
)
|
||||
db.add(vendor_user1)
|
||||
db.commit()
|
||||
|
||||
# Create marketplace product for vendor1
|
||||
mp1 = MarketplaceProduct(
|
||||
gtin="1234567890123",
|
||||
title="Product for Vendor 1",
|
||||
is_active=True,
|
||||
)
|
||||
db.add(mp1)
|
||||
db.commit()
|
||||
|
||||
# Create products for vendor1
|
||||
for i in range(3):
|
||||
product = Product(
|
||||
vendor_id=vendor1.id,
|
||||
marketplace_product_id=mp1.id,
|
||||
price=10.0 + i,
|
||||
is_active=True,
|
||||
)
|
||||
db.add(product)
|
||||
db.commit()
|
||||
|
||||
# Get token for vendor1 user
|
||||
token_data = auth_manager.create_access_token(test_vendor_user)
|
||||
vendor1_headers = {"Authorization": f"Bearer {token_data['access_token']}"}
|
||||
|
||||
# Get stats for vendor1
|
||||
response = client.get(
|
||||
"/api/v1/vendor/dashboard/stats",
|
||||
headers=vendor1_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
||||
# Should show 3 products for vendor1
|
||||
assert data["vendor"]["id"] == vendor1.id
|
||||
assert data["products"]["total"] == 3
|
||||
|
||||
def test_dashboard_stats_without_vendor_association(
|
||||
self, client, db, auth_manager
|
||||
):
|
||||
"""Test dashboard stats for user not associated with any vendor"""
|
||||
from models.database.user import User
|
||||
|
||||
# Create vendor user without vendor association
|
||||
hashed_password = auth_manager.hash_password("testpass123")
|
||||
orphan_user = User(
|
||||
email="orphan@example.com",
|
||||
username="orphanvendor",
|
||||
hashed_password=hashed_password,
|
||||
role="vendor",
|
||||
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/vendor/dashboard/stats", headers=headers)
|
||||
|
||||
# Should fail - user not associated with vendor
|
||||
assert response.status_code == 403
|
||||
data = response.json()
|
||||
assert "not associated with any vendor" in data["message"]
|
||||
|
||||
def test_dashboard_stats_with_inactive_vendor(
|
||||
self, client, db, test_vendor_user, auth_manager
|
||||
):
|
||||
"""Test dashboard stats for inactive vendor"""
|
||||
from models.database.vendor import Vendor, VendorUser
|
||||
|
||||
# Create inactive vendor
|
||||
vendor = Vendor(
|
||||
vendor_code="INACTIVE",
|
||||
subdomain="inactive",
|
||||
name="Inactive Vendor",
|
||||
owner_user_id=test_vendor_user.id,
|
||||
is_active=False, # Inactive
|
||||
is_verified=True,
|
||||
)
|
||||
db.add(vendor)
|
||||
db.commit()
|
||||
|
||||
# Associate with user
|
||||
vendor_user = VendorUser(
|
||||
vendor_id=vendor.id,
|
||||
user_id=test_vendor_user.id,
|
||||
is_owner=True,
|
||||
is_active=True,
|
||||
)
|
||||
db.add(vendor_user)
|
||||
db.commit()
|
||||
|
||||
# Get token
|
||||
token_data = auth_manager.create_access_token(test_vendor_user)
|
||||
headers = {"Authorization": f"Bearer {token_data['access_token']}"}
|
||||
|
||||
# Try to get dashboard stats
|
||||
response = client.get("/api/v1/vendor/dashboard/stats", headers=headers)
|
||||
|
||||
# Should fail - vendor is inactive
|
||||
assert response.status_code == 404
|
||||
data = response.json()
|
||||
assert "not found or inactive" in data["message"]
|
||||
|
||||
def test_dashboard_stats_empty_vendor(
|
||||
self, client, vendor_user_headers, test_vendor_with_vendor_user
|
||||
):
|
||||
"""Test dashboard stats for vendor with no data"""
|
||||
response = client.get(
|
||||
"/api/v1/vendor/dashboard/stats",
|
||||
headers=vendor_user_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
||||
# Should return zeros for empty vendor
|
||||
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_with_products(
|
||||
self, client, db, vendor_user_headers, test_vendor_with_vendor_user
|
||||
):
|
||||
"""Test dashboard stats accuracy with actual products"""
|
||||
from models.database.product import Product
|
||||
from models.database.marketplace_product import MarketplaceProduct
|
||||
|
||||
# Create marketplace products
|
||||
mp = MarketplaceProduct(
|
||||
gtin="1234567890124",
|
||||
title="Test Product",
|
||||
is_active=True,
|
||||
)
|
||||
db.add(mp)
|
||||
db.commit()
|
||||
|
||||
# Create products (3 active, 2 inactive)
|
||||
for i in range(5):
|
||||
product = Product(
|
||||
vendor_id=test_vendor_with_vendor_user.id,
|
||||
marketplace_product_id=mp.id,
|
||||
price=10.0 + i,
|
||||
is_active=(i < 3), # First 3 are active
|
||||
)
|
||||
db.add(product)
|
||||
db.commit()
|
||||
|
||||
# Get stats
|
||||
response = client.get(
|
||||
"/api/v1/vendor/dashboard/stats",
|
||||
headers=vendor_user_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
||||
assert data["products"]["total"] == 5
|
||||
assert data["products"]["active"] == 3
|
||||
|
||||
def test_dashboard_stats_response_time(
|
||||
self, client, vendor_user_headers, test_vendor_with_vendor_user
|
||||
):
|
||||
"""Test that dashboard stats responds quickly"""
|
||||
import time
|
||||
|
||||
start_time = time.time()
|
||||
response = client.get(
|
||||
"/api/v1/vendor/dashboard/stats",
|
||||
headers=vendor_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, vendor_user_headers, test_vendor_with_vendor_user
|
||||
):
|
||||
"""Test that dashboard stats can be called multiple times"""
|
||||
# Make multiple requests
|
||||
responses = []
|
||||
for _ in range(3):
|
||||
response = client.get(
|
||||
"/api/v1/vendor/dashboard/stats",
|
||||
headers=vendor_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["vendor"]["id"] == data_list[0]["vendor"]["id"]
|
||||
assert data["products"]["total"] == data_list[0]["products"]["total"]
|
||||
Reference in New Issue
Block a user