Files
orion/tests/unit/services/test_admin_service.py
Samir Boulahtit aad18c27ab
Some checks failed
CI / ruff (push) Successful in 11s
CI / validate (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / pytest (push) Has started running
refactor: remove all backward compatibility code across 70 files
Clean up 28 backward compatibility instances identified in the codebase.
The app is not live, so all shims are replaced with the target architecture:

- Remove legacy Inventory.location column (use bin_location exclusively)
- Remove dashboard _extract_metric_value helper (use flat metrics dict)
- Remove legacy stat field duplicates (total_stores, total_imports, etc.)
- Remove 13 re-export shims and class aliases across modules
- Remove module-enabling JSON fallback (use PlatformModule junction table)
- Remove menu_to_legacy_format() conversion (return dataclasses directly)
- Remove title/description from MarketplaceProductBase schema
- Clean billing convenience method docstrings
- Clean test fixtures and backward-compat comments
- Add PlatformModule seeding to init_production.py

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 13:20:29 +01:00

443 lines
16 KiB
Python

# tests/unit/services/test_admin_service.py
import pytest
from app.exceptions import ValidationException
from app.modules.analytics.services.stats_service import stats_service
from app.modules.tenancy.exceptions import (
AdminOperationException,
CannotModifySelfException,
MerchantNotFoundException,
StoreAlreadyExistsException,
StoreNotFoundException,
UserNotFoundException,
UserStatusChangeException,
)
from app.modules.tenancy.schemas.store import StoreCreate
from app.modules.tenancy.services.admin_service import AdminService
@pytest.mark.unit
@pytest.mark.admin
class TestAdminService:
"""Test suite for AdminService following the application's testing patterns"""
def setup_method(self):
"""Setup method following the same pattern as product service tests"""
self.service = AdminService()
# User Management Tests
def test_get_all_users(self, db, test_user, test_admin):
"""Test getting all users with pagination"""
users = self.service.get_all_users(db, skip=0, limit=10)
assert len(users) >= 2 # test_user + test_admin
user_ids = [user.id for user in users]
assert test_user.id in user_ids
assert test_admin.id in user_ids
def test_get_all_users_with_pagination(self, db, test_user, test_admin):
"""Test user pagination works correctly"""
users = self.service.get_all_users(db, skip=0, limit=1)
assert len(users) == 1
users_second_page = self.service.get_all_users(db, skip=1, limit=1)
assert len(users_second_page) == 1
assert users[0].id != users_second_page[0].id
def test_toggle_user_status_deactivate(self, db, test_user, test_admin):
"""Test deactivating a user"""
assert test_user.is_active is True
user, message = self.service.toggle_user_status(db, test_user.id, test_admin.id)
assert user.id == test_user.id
assert user.is_active is False
assert test_user.username in message
assert "deactivated" in message
def test_toggle_user_status_activate(self, db, test_user, test_admin):
"""Test activating a user"""
from app.modules.tenancy.models import User
# Re-query user to get fresh instance
user_to_deactivate = db.query(User).filter(User.id == test_user.id).first()
user_to_deactivate.is_active = False
db.commit()
user, message = self.service.toggle_user_status(db, test_user.id, test_admin.id)
assert user.id == test_user.id
assert user.is_active is True
assert "activated" in message
def test_toggle_user_status_user_not_found(self, db, test_admin):
"""Test toggle user status when user not found"""
with pytest.raises(UserNotFoundException) as exc_info:
self.service.toggle_user_status(db, 99999, test_admin.id)
exception = exc_info.value
assert exception.error_code == "USER_NOT_FOUND"
assert "99999" in exception.message
def test_toggle_user_status_cannot_modify_self(self, db, test_admin):
"""Test that admin cannot modify their own account"""
with pytest.raises(CannotModifySelfException) as exc_info:
self.service.toggle_user_status(db, test_admin.id, test_admin.id)
exception = exc_info.value
assert exception.error_code == "CANNOT_MODIFY_SELF"
assert "deactivate account" in exception.message
def test_toggle_user_status_cannot_modify_admin(
self, db, test_admin, another_admin
):
"""Test that admin cannot modify another admin"""
with pytest.raises(UserStatusChangeException) as exc_info:
self.service.toggle_user_status(db, another_admin.id, test_admin.id)
exception = exc_info.value
assert exception.error_code == "USER_STATUS_CHANGE_FAILED"
assert "Cannot modify another admin user" in exception.message
# Store Management Tests
def test_get_all_stores(self, db, test_store):
"""Test getting all stores with total count"""
stores, total = self.service.get_all_stores(db, skip=0, limit=10)
assert total >= 1
assert len(stores) >= 1
store_codes = [store.store_code for store in stores]
assert test_store.store_code in store_codes
def test_get_all_stores_with_pagination(self, db, test_store, verified_store):
"""Test store pagination works correctly"""
stores, total = self.service.get_all_stores(db, skip=0, limit=1)
assert total >= 2
assert len(stores) == 1
stores_second_page, _ = self.service.get_all_stores(db, skip=1, limit=1)
assert len(stores_second_page) >= 0
if len(stores_second_page) > 0:
assert stores[0].id != stores_second_page[0].id
def test_verify_store_mark_verified(self, db, test_store):
"""Test marking store as verified"""
from app.modules.tenancy.models import Store
# Re-query store to get fresh instance
store_to_unverify = (
db.query(Store).filter(Store.id == test_store.id).first()
)
store_to_unverify.is_verified = False
db.commit()
store, message = self.service.verify_store(db, test_store.id)
assert store.id == test_store.id
assert store.is_verified is True
assert "verified" in message
def test_verify_store_mark_unverified(self, db, verified_store):
"""Test marking verified store as unverified"""
store, message = self.service.verify_store(db, verified_store.id)
assert store.id == verified_store.id
assert store.is_verified is False
assert verified_store.store_code in message
assert "unverified" in message
def test_verify_store_not_found(self, db):
"""Test verify store when store not found"""
with pytest.raises(StoreNotFoundException) as exc_info:
self.service.verify_store(db, 99999)
exception = exc_info.value
assert exception.error_code == "STORE_NOT_FOUND"
assert "99999" in exception.message
def test_toggle_store_status_deactivate(self, db, test_store):
"""Test deactivating a store"""
original_status = test_store.is_active
store, message = self.service.toggle_store_status(db, test_store.id)
assert store.id == test_store.id
assert store.is_active != original_status
assert test_store.store_code in message
if original_status:
assert "deactivated" in message
else:
assert "activated" in message
def test_toggle_store_status_not_found(self, db):
"""Test toggle store status when store not found"""
with pytest.raises(StoreNotFoundException) as exc_info:
self.service.toggle_store_status(db, 99999)
exception = exc_info.value
assert exception.error_code == "STORE_NOT_FOUND"
# NOTE: Marketplace Import Jobs tests have been moved to the marketplace module.
# See tests/unit/services/test_marketplace_import_job_service.py
# Statistics Tests
def test_get_user_statistics(self, db, test_user, test_admin):
"""Test getting user statistics"""
stats = stats_service.get_user_statistics(db)
assert "total_users" in stats
assert "active_users" in stats
assert "inactive_users" in stats
assert "activation_rate" in stats
assert isinstance(stats["total_users"], int)
assert isinstance(stats["active_users"], int)
assert isinstance(stats["inactive_users"], int)
assert isinstance(stats["activation_rate"], int | float)
assert stats["total_users"] >= 2 # test_user + test_admin
assert stats["active_users"] + stats["inactive_users"] == stats["total_users"]
def test_get_store_statistics(self, db, test_store):
"""Test getting store statistics"""
stats = stats_service.get_store_statistics(db)
assert "total" in stats
assert "verified" in stats
assert "pending" in stats
assert "inactive" in stats
assert "verification_rate" in stats
assert isinstance(stats["total"], int)
assert isinstance(stats["verified"], int)
assert isinstance(stats["verification_rate"], int | float)
assert stats["total"] >= 1
# Error Handling Tests
def test_get_all_users_database_error(self, db_with_error, test_admin):
"""Test handling database errors in get_all_users"""
with pytest.raises(AdminOperationException) as exc_info:
self.service.get_all_users(db_with_error, skip=0, limit=10)
exception = exc_info.value
assert exception.error_code == "ADMIN_OPERATION_FAILED"
assert "get_all_users" in exception.message
def test_get_all_stores_database_error(self, db_with_error):
"""Test handling database errors in get_all_stores"""
with pytest.raises(AdminOperationException) as exc_info:
self.service.get_all_stores(db_with_error, skip=0, limit=10)
exception = exc_info.value
assert exception.error_code == "ADMIN_OPERATION_FAILED"
assert "get_all_stores" in exception.message
# Edge Cases
def test_get_all_users_empty_database(self, empty_db):
"""Test getting users when database is empty"""
users = self.service.get_all_users(empty_db, skip=0, limit=10)
assert len(users) == 0
def test_get_all_stores_empty_database(self, empty_db):
"""Test getting stores when database is empty"""
stores, total = self.service.get_all_stores(empty_db, skip=0, limit=10)
assert len(stores) == 0
assert total == 0
def test_user_statistics_empty_database(self, empty_db):
"""Test user statistics when no users exist"""
stats = stats_service.get_user_statistics(empty_db)
assert stats["total_users"] == 0
assert stats["active_users"] == 0
assert stats["inactive_users"] == 0
assert stats["activation_rate"] == 0
def test_store_statistics_empty_database(self, empty_db):
"""Test store statistics when no stores exist"""
stats = stats_service.get_store_statistics(empty_db)
assert stats["total"] == 0
assert stats["verified"] == 0
assert stats["inactive"] == 0
assert stats["verification_rate"] == 0
@pytest.mark.unit
@pytest.mark.admin
class TestAdminServiceStoreCreation:
"""Test suite for AdminService.create_store with platform assignments."""
def setup_method(self):
"""Setup method following the same pattern as other tests."""
self.service = AdminService()
def test_create_store_without_platforms(self, db, test_merchant):
"""Test creating a store without platform assignments."""
import uuid
unique_id = str(uuid.uuid4())[:8]
store_data = StoreCreate(
merchant_id=test_merchant.id,
store_code=f"NOPLATFORM_{unique_id}",
subdomain=f"noplatform{unique_id}",
name=f"No Platform Store {unique_id}",
)
store = self.service.create_store(db, store_data)
db.commit()
assert store is not None
assert store.store_code == store_data.store_code.upper()
assert store.merchant_id == test_merchant.id
assert store.is_active is True
def test_create_store_with_single_platform(
self, db, test_merchant, test_platform
):
"""Test creating a store with one platform assignment."""
import uuid
unique_id = str(uuid.uuid4())[:8]
store_data = StoreCreate(
merchant_id=test_merchant.id,
store_code=f"SINGLEPLAT_{unique_id}",
subdomain=f"singleplat{unique_id}",
name=f"Single Platform Store {unique_id}",
platform_ids=[test_platform.id],
)
store = self.service.create_store(db, store_data)
db.commit()
db.refresh(store)
assert store is not None
assert store.store_code == store_data.store_code.upper()
# Verify platform assignment
from app.modules.tenancy.models import StorePlatform
assignment = (
db.query(StorePlatform)
.filter(
StorePlatform.store_id == store.id,
StorePlatform.platform_id == test_platform.id,
)
.first()
)
assert assignment is not None
assert assignment.is_active is True
def test_create_store_with_multiple_platforms(
self, db, test_merchant, test_platform, another_platform
):
"""Test creating a store with multiple platform assignments."""
import uuid
unique_id = str(uuid.uuid4())[:8]
store_data = StoreCreate(
merchant_id=test_merchant.id,
store_code=f"MULTIPLAT_{unique_id}",
subdomain=f"multiplat{unique_id}",
name=f"Multi Platform Store {unique_id}",
platform_ids=[test_platform.id, another_platform.id],
)
store = self.service.create_store(db, store_data)
db.commit()
db.refresh(store)
assert store is not None
# Verify both platform assignments
from app.modules.tenancy.models import StorePlatform
assignments = (
db.query(StorePlatform)
.filter(StorePlatform.store_id == store.id)
.all()
)
assert len(assignments) == 2
platform_ids = [a.platform_id for a in assignments]
assert test_platform.id in platform_ids
assert another_platform.id in platform_ids
def test_create_store_with_invalid_platform_id(self, db, test_merchant):
"""Test creating a store with non-existent platform ID (should ignore)."""
import uuid
unique_id = str(uuid.uuid4())[:8]
store_data = StoreCreate(
merchant_id=test_merchant.id,
store_code=f"INVALIDPLAT_{unique_id}",
subdomain=f"invalidplat{unique_id}",
name=f"Invalid Platform Store {unique_id}",
platform_ids=[99999], # Non-existent platform
)
# Should succeed but not create assignment for invalid platform
store = self.service.create_store(db, store_data)
db.commit()
db.refresh(store)
assert store is not None
# Verify no platform assignments created
from app.modules.tenancy.models import StorePlatform
assignments = (
db.query(StorePlatform)
.filter(StorePlatform.store_id == store.id)
.all()
)
assert len(assignments) == 0
def test_create_store_duplicate_code_fails(self, db, test_merchant, test_store):
"""Test creating a store with duplicate store code fails."""
store_data = StoreCreate(
merchant_id=test_merchant.id,
store_code=test_store.store_code, # Duplicate
subdomain="uniquesubdomain",
name="Duplicate Code Store",
)
with pytest.raises(StoreAlreadyExistsException):
self.service.create_store(db, store_data)
def test_create_store_duplicate_subdomain_fails(self, db, test_merchant, test_store):
"""Test creating a store with duplicate subdomain fails."""
import uuid
unique_id = str(uuid.uuid4())[:8]
store_data = StoreCreate(
merchant_id=test_merchant.id,
store_code=f"UNIQUECODE_{unique_id}",
subdomain=test_store.subdomain, # Duplicate
name="Duplicate Subdomain Store",
)
with pytest.raises(ValidationException) as exc_info:
self.service.create_store(db, store_data)
assert "already taken" in str(exc_info.value)
def test_create_store_invalid_merchant_fails(self, db):
"""Test creating a store with non-existent merchant fails."""
import uuid
unique_id = str(uuid.uuid4())[:8]
store_data = StoreCreate(
merchant_id=99999, # Non-existent
store_code=f"NOMERCHANT_{unique_id}",
subdomain=f"nomerchant{unique_id}",
name="No Merchant Store",
)
with pytest.raises(MerchantNotFoundException) as exc_info:
self.service.create_store(db, store_data)
assert "not found" in str(exc_info.value)