style: apply black and isort formatting across entire codebase

- Standardize quote style (single to double quotes)
- Reorder and group imports alphabetically
- Fix line breaks and indentation for consistency
- Apply PEP 8 formatting standards

Also updated Makefile to exclude both venv and .venv from code quality checks.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-28 19:30:17 +01:00
parent 13f0094743
commit 21c13ca39b
236 changed files with 8450 additions and 6545 deletions

View File

@@ -1,14 +1,10 @@
# tests/unit/services/test_admin_service.py
import pytest
from app.exceptions import (
UserNotFoundException,
UserStatusChangeException,
CannotModifySelfException,
VendorNotFoundException,
VendorVerificationException,
AdminOperationException,
)
from app.exceptions import (AdminOperationException, CannotModifySelfException,
UserNotFoundException, UserStatusChangeException,
VendorNotFoundException,
VendorVerificationException)
from app.services.admin_service import AdminService
from app.services.stats_service import stats_service
from models.database.marketplace_import_job import MarketplaceImportJob
@@ -85,7 +81,9 @@ class TestAdminService:
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):
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)
@@ -148,7 +146,7 @@ class TestAdminService:
assert "99999" in exception.message
def test_toggle_vendor_status_deactivate(self, db, test_vendor):
"""Test deactivating a vendor """
"""Test deactivating a vendor"""
original_status = test_vendor.is_active
vendor, message = self.service.toggle_vendor_status(db, test_vendor.id)
@@ -170,21 +168,26 @@ class TestAdminService:
assert exception.error_code == "VENDOR_NOT_FOUND"
# Marketplace Import Jobs Tests
def test_get_marketplace_import_jobs_no_filters(self, db, test_marketplace_import_job):
def test_get_marketplace_import_jobs_no_filters(
self, db, test_marketplace_import_job
):
"""Test getting marketplace import jobs without filters"""
result = self.service.get_marketplace_import_jobs(db, skip=0, limit=10)
assert len(result) >= 1
# Find our test job in the results
test_job = next(
(job for job in result if job.job_id == test_marketplace_import_job.id), None
(job for job in result if job.job_id == test_marketplace_import_job.id),
None,
)
assert test_job is not None
assert test_job.marketplace == test_marketplace_import_job.marketplace
assert test_job.vendor_name == test_marketplace_import_job.name
assert test_job.status == test_marketplace_import_job.status
def test_get_marketplace_import_jobs_with_marketplace_filter(self, db, test_marketplace_import_job):
def test_get_marketplace_import_jobs_with_marketplace_filter(
self, db, test_marketplace_import_job
):
"""Test filtering marketplace import jobs by marketplace"""
result = self.service.get_marketplace_import_jobs(
db, marketplace=test_marketplace_import_job.marketplace, skip=0, limit=10
@@ -192,9 +195,14 @@ class TestAdminService:
assert len(result) >= 1
for job in result:
assert test_marketplace_import_job.marketplace.lower() in job.marketplace.lower()
assert (
test_marketplace_import_job.marketplace.lower()
in job.marketplace.lower()
)
def test_get_marketplace_import_jobs_with_vendor_filter(self, db, test_marketplace_import_job):
def test_get_marketplace_import_jobs_with_vendor_filter(
self, db, test_marketplace_import_job
):
"""Test filtering marketplace import jobs by vendor name"""
result = self.service.get_marketplace_import_jobs(
db, vendor_name=test_marketplace_import_job.name, skip=0, limit=10
@@ -204,7 +212,9 @@ class TestAdminService:
for job in result:
assert test_marketplace_import_job.name.lower() in job.vendor_name.lower()
def test_get_marketplace_import_jobs_with_status_filter(self, db, test_marketplace_import_job):
def test_get_marketplace_import_jobs_with_status_filter(
self, db, test_marketplace_import_job
):
"""Test filtering marketplace import jobs by status"""
result = self.service.get_marketplace_import_jobs(
db, status=test_marketplace_import_job.status, skip=0, limit=10
@@ -214,7 +224,9 @@ class TestAdminService:
for job in result:
assert job.status == test_marketplace_import_job.status
def test_get_marketplace_import_jobs_pagination(self, db, test_marketplace_import_job):
def test_get_marketplace_import_jobs_pagination(
self, db, test_marketplace_import_job
):
"""Test marketplace import jobs pagination"""
result_page1 = self.service.get_marketplace_import_jobs(db, skip=0, limit=1)
result_page2 = self.service.get_marketplace_import_jobs(db, skip=1, limit=1)

View File

@@ -1,11 +1,9 @@
# tests/test_auth_service.py
import pytest
from app.exceptions.auth import (
UserAlreadyExistsException,
InvalidCredentialsException,
UserNotActiveException,
)
from app.exceptions.auth import (InvalidCredentialsException,
UserAlreadyExistsException,
UserNotActiveException)
from app.exceptions.base import ValidationException
from app.services.auth_service import AuthService
from models.schema.auth import UserLogin, UserRegister
@@ -218,11 +216,14 @@ class TestAuthService:
def test_create_access_token_failure(self, test_user, monkeypatch):
"""Test creating access token handles failures"""
# Mock the auth_manager to raise an exception
def mock_create_token(*args, **kwargs):
raise Exception("Token creation failed")
monkeypatch.setattr(self.service.auth_manager, "create_access_token", mock_create_token)
monkeypatch.setattr(
self.service.auth_manager, "create_access_token", mock_create_token
)
with pytest.raises(ValidationException) as exc_info:
self.service.create_access_token(test_user)
@@ -250,11 +251,14 @@ class TestAuthService:
def test_hash_password_failure(self, monkeypatch):
"""Test password hashing handles failures"""
# Mock the auth_manager to raise an exception
def mock_hash_password(*args, **kwargs):
raise Exception("Hashing failed")
monkeypatch.setattr(self.service.auth_manager, "hash_password", mock_hash_password)
monkeypatch.setattr(
self.service.auth_manager, "hash_password", mock_hash_password
)
with pytest.raises(ValidationException) as exc_info:
self.service.hash_password("testpassword")
@@ -267,9 +271,7 @@ class TestAuthService:
def test_register_user_database_error(self, db_with_error):
"""Test user registration handles database errors"""
user_data = UserRegister(
email="test@example.com",
username="testuser",
password="password123"
email="test@example.com", username="testuser", password="password123"
)
with pytest.raises(ValidationException) as exc_info:

View File

@@ -3,19 +3,17 @@ import uuid
import pytest
from app.exceptions import (InsufficientInventoryException,
InvalidInventoryOperationException,
InvalidQuantityException,
InventoryNotFoundException,
InventoryValidationException,
NegativeInventoryException, ValidationException)
from app.services.inventory_service import InventoryService
from app.exceptions import (
InventoryNotFoundException,
InsufficientInventoryException,
InvalidInventoryOperationException,
InventoryValidationException,
NegativeInventoryException,
InvalidQuantityException,
ValidationException,
)
from models.schema.inventory import InventoryAdd, InventoryCreate, InventoryUpdate
from models.database.marketplace_product import MarketplaceProduct
from models.database.inventory import Inventory
from models.database.marketplace_product import MarketplaceProduct
from models.schema.inventory import (InventoryAdd, InventoryCreate,
InventoryUpdate)
@pytest.mark.unit
@@ -40,10 +38,14 @@ class TestInventoryService:
def test_normalize_gtin_valid(self):
"""Test GTIN normalization with valid GTINs."""
# Test various valid GTIN formats - these should remain unchanged
assert self.service._normalize_gtin("1234567890123") == "1234567890123" # EAN-13
assert (
self.service._normalize_gtin("1234567890123") == "1234567890123"
) # EAN-13
assert self.service._normalize_gtin("123456789012") == "123456789012" # UPC-A
assert self.service._normalize_gtin("12345678") == "12345678" # EAN-8
assert self.service._normalize_gtin("12345678901234") == "12345678901234" # GTIN-14
assert (
self.service._normalize_gtin("12345678901234") == "12345678901234"
) # GTIN-14
# Test with decimal points (should be removed)
assert self.service._normalize_gtin("1234567890123.0") == "1234567890123"
@@ -52,11 +54,17 @@ class TestInventoryService:
assert self.service._normalize_gtin(" 1234567890123 ") == "1234567890123"
# Test short GTINs being padded
assert self.service._normalize_gtin("123") == "0000000000123" # Padded to EAN-13
assert self.service._normalize_gtin("12345") == "0000000012345" # Padded to EAN-13
assert (
self.service._normalize_gtin("123") == "0000000000123"
) # Padded to EAN-13
assert (
self.service._normalize_gtin("12345") == "0000000012345"
) # Padded to EAN-13
# Test long GTINs being truncated
assert self.service._normalize_gtin("123456789012345") == "3456789012345" # Truncated to 13
assert (
self.service._normalize_gtin("123456789012345") == "3456789012345"
) # Truncated to 13
def test_normalize_gtin_edge_cases(self):
"""Test GTIN normalization edge cases."""
@@ -65,9 +73,15 @@ class TestInventoryService:
assert self.service._normalize_gtin(123) == "0000000000123"
# Test mixed valid/invalid characters
assert self.service._normalize_gtin("123-456-789-012") == "123456789012" # Dashes removed
assert self.service._normalize_gtin("123 456 789 012") == "123456789012" # Spaces removed
assert self.service._normalize_gtin("ABC123456789012DEF") == "123456789012" # Letters removed
assert (
self.service._normalize_gtin("123-456-789-012") == "123456789012"
) # Dashes removed
assert (
self.service._normalize_gtin("123 456 789 012") == "123456789012"
) # Spaces removed
assert (
self.service._normalize_gtin("ABC123456789012DEF") == "123456789012"
) # Letters removed
def test_set_inventory_new_entry_success(self, db):
"""Test setting inventory for a new GTIN/location combination successfully."""
@@ -162,7 +176,9 @@ class TestInventoryService:
def test_add_inventory_invalid_gtin_validation_error(self, db):
"""Test adding inventory with invalid GTIN returns InventoryValidationException."""
inventory_data = InventoryAdd(gtin="invalid_gtin", location="WAREHOUSE_A", quantity=50)
inventory_data = InventoryAdd(
gtin="invalid_gtin", location="WAREHOUSE_A", quantity=50
)
with pytest.raises(InventoryValidationException) as exc_info:
self.service.add_inventory(db, inventory_data)
@@ -180,11 +196,12 @@ class TestInventoryService:
assert exc_info.value.error_code == "INVALID_QUANTITY"
assert "Quantity must be positive" in str(exc_info.value)
def test_remove_inventory_success(self, db, test_inventory):
"""Test removing inventory successfully."""
original_quantity = test_inventory.quantity
remove_quantity = min(10, original_quantity) # Ensure we don't remove more than available
remove_quantity = min(
10, original_quantity
) # Ensure we don't remove more than available
inventory_data = InventoryAdd(
gtin=test_inventory.gtin,
@@ -212,7 +229,9 @@ class TestInventoryService:
assert exc_info.value.error_code == "INSUFFICIENT_INVENTORY"
assert exc_info.value.details["gtin"] == test_inventory.gtin
assert exc_info.value.details["location"] == test_inventory.location
assert exc_info.value.details["requested_quantity"] == test_inventory.quantity + 10
assert (
exc_info.value.details["requested_quantity"] == test_inventory.quantity + 10
)
assert exc_info.value.details["available_quantity"] == test_inventory.quantity
def test_remove_inventory_nonexistent_entry_not_found(self, db):
@@ -231,7 +250,9 @@ class TestInventoryService:
def test_remove_inventory_invalid_gtin_validation_error(self, db):
"""Test removing inventory with invalid GTIN returns InventoryValidationException."""
inventory_data = InventoryAdd(gtin="invalid_gtin", location="WAREHOUSE_A", quantity=10)
inventory_data = InventoryAdd(
gtin="invalid_gtin", location="WAREHOUSE_A", quantity=10
)
with pytest.raises(InventoryValidationException) as exc_info:
self.service.remove_inventory(db, inventory_data)
@@ -254,7 +275,9 @@ class TestInventoryService:
# The service prevents negative inventory through InsufficientInventoryException
assert exc_info.value.error_code == "INSUFFICIENT_INVENTORY"
def test_get_inventory_by_gtin_success(self, db, test_inventory, test_marketplace_product):
def test_get_inventory_by_gtin_success(
self, db, test_inventory, test_marketplace_product
):
"""Test getting inventory summary by GTIN successfully."""
result = self.service.get_inventory_by_gtin(db, test_inventory.gtin)
@@ -265,14 +288,20 @@ class TestInventoryService:
assert result.locations[0].quantity == test_inventory.quantity
assert result.product_title == test_marketplace_product.title
def test_get_inventory_by_gtin_multiple_locations_success(self, db, test_marketplace_product):
def test_get_inventory_by_gtin_multiple_locations_success(
self, db, test_marketplace_product
):
"""Test getting inventory summary with multiple locations successfully."""
unique_gtin = test_marketplace_product.gtin
unique_id = str(uuid.uuid4())[:8]
# Create multiple inventory entries for the same GTIN with unique locations
inventory1 = Inventory(gtin=unique_gtin, location=f"WAREHOUSE_A_{unique_id}", quantity=50)
inventory2 = Inventory(gtin=unique_gtin, location=f"WAREHOUSE_B_{unique_id}", quantity=30)
inventory1 = Inventory(
gtin=unique_gtin, location=f"WAREHOUSE_A_{unique_id}", quantity=50
)
inventory2 = Inventory(
gtin=unique_gtin, location=f"WAREHOUSE_B_{unique_id}", quantity=30
)
db.add(inventory1)
db.add(inventory2)
@@ -301,7 +330,9 @@ class TestInventoryService:
assert exc_info.value.error_code == "INVENTORY_VALIDATION_FAILED"
assert "Invalid GTIN format" in str(exc_info.value)
def test_get_total_inventory_success(self, db, test_inventory, test_marketplace_product):
def test_get_total_inventory_success(
self, db, test_inventory, test_marketplace_product
):
"""Test getting total inventory for a GTIN successfully."""
result = self.service.get_total_inventory(db, test_inventory.gtin)
@@ -364,7 +395,9 @@ class TestInventoryService:
result = self.service.get_all_inventory(db, skip=2, limit=2)
assert len(result) <= 2 # Should be at most 2, might be less if other records exist
assert (
len(result) <= 2
) # Should be at most 2, might be less if other records exist
def test_update_inventory_success(self, db, test_inventory):
"""Test updating inventory quantity successfully."""
@@ -404,7 +437,9 @@ class TestInventoryService:
assert result is True
# Verify the inventory is actually deleted
deleted_inventory = db.query(Inventory).filter(Inventory.id == inventory_id).first()
deleted_inventory = (
db.query(Inventory).filter(Inventory.id == inventory_id).first()
)
assert deleted_inventory is None
def test_delete_inventory_not_found_error(self, db):
@@ -415,7 +450,9 @@ class TestInventoryService:
assert exc_info.value.error_code == "INVENTORY_NOT_FOUND"
assert "99999" in str(exc_info.value)
def test_get_low_inventory_items_success(self, db, test_inventory, test_marketplace_product):
def test_get_low_inventory_items_success(
self, db, test_inventory, test_marketplace_product
):
"""Test getting low inventory items successfully."""
# Set inventory to a low value
test_inventory.quantity = 5
@@ -424,7 +461,9 @@ class TestInventoryService:
result = self.service.get_low_inventory_items(db, threshold=10)
assert len(result) >= 1
low_inventory_item = next((item for item in result if item["gtin"] == test_inventory.gtin), None)
low_inventory_item = next(
(item for item in result if item["gtin"] == test_inventory.gtin), None
)
assert low_inventory_item is not None
assert low_inventory_item["current_quantity"] == 5
assert low_inventory_item["location"] == test_inventory.location
@@ -440,9 +479,13 @@ class TestInventoryService:
def test_get_inventory_summary_by_location_success(self, db, test_inventory):
"""Test getting inventory summary by location successfully."""
result = self.service.get_inventory_summary_by_location(db, test_inventory.location)
result = self.service.get_inventory_summary_by_location(
db, test_inventory.location
)
assert result["location"] == test_inventory.location.upper() # Service normalizes to uppercase
assert (
result["location"] == test_inventory.location.upper()
) # Service normalizes to uppercase
assert result["total_items"] >= 1
assert result["total_quantity"] >= test_inventory.quantity
assert result["unique_gtins"] >= 1
@@ -450,7 +493,9 @@ class TestInventoryService:
def test_get_inventory_summary_by_location_empty_result(self, db):
"""Test getting inventory summary for location with no inventory."""
unique_id = str(uuid.uuid4())[:8]
result = self.service.get_inventory_summary_by_location(db, f"EMPTY_LOCATION_{unique_id}")
result = self.service.get_inventory_summary_by_location(
db, f"EMPTY_LOCATION_{unique_id}"
)
assert result["total_items"] == 0
assert result["total_quantity"] == 0
@@ -459,12 +504,16 @@ class TestInventoryService:
def test_validate_quantity_edge_cases(self, db):
"""Test quantity validation with edge cases."""
# Test zero quantity with allow_zero=True (should succeed)
inventory_data = InventoryCreate(gtin="1234567890123", location="WAREHOUSE_A", quantity=0)
inventory_data = InventoryCreate(
gtin="1234567890123", location="WAREHOUSE_A", quantity=0
)
result = self.service.set_inventory(db, inventory_data)
assert result.quantity == 0
# Test zero quantity with add_inventory (should fail - doesn't allow zero)
inventory_data_add = InventoryAdd(gtin="1234567890123", location="WAREHOUSE_B", quantity=0)
inventory_data_add = InventoryAdd(
gtin="1234567890123", location="WAREHOUSE_B", quantity=0
)
with pytest.raises(InvalidQuantityException):
self.service.add_inventory(db, inventory_data_add)
@@ -477,10 +526,10 @@ class TestInventoryService:
exception = exc_info.value
# Verify exception structure matches WizamartException.to_dict()
assert hasattr(exception, 'error_code')
assert hasattr(exception, 'message')
assert hasattr(exception, 'status_code')
assert hasattr(exception, 'details')
assert hasattr(exception, "error_code")
assert hasattr(exception, "message")
assert hasattr(exception, "status_code")
assert hasattr(exception, "details")
assert isinstance(exception.error_code, str)
assert isinstance(exception.message, str)

View File

@@ -4,19 +4,18 @@ from datetime import datetime
import pytest
from app.exceptions.marketplace_import_job import (
ImportJobNotFoundException,
ImportJobNotOwnedException,
ImportJobCannotBeCancelledException,
ImportJobCannotBeDeletedException,
)
from app.exceptions.vendor import VendorNotFoundException, UnauthorizedVendorAccessException
from app.exceptions.base import ValidationException
from app.services.marketplace_import_job_service import MarketplaceImportJobService
from models.schema.marketplace_import_job import MarketplaceImportJobRequest
from app.exceptions.marketplace_import_job import (
ImportJobCannotBeCancelledException, ImportJobCannotBeDeletedException,
ImportJobNotFoundException, ImportJobNotOwnedException)
from app.exceptions.vendor import (UnauthorizedVendorAccessException,
VendorNotFoundException)
from app.services.marketplace_import_job_service import \
MarketplaceImportJobService
from models.database.marketplace_import_job import MarketplaceImportJob
from models.database.vendor import Vendor
from models.database.user import User
from models.database.vendor import Vendor
from models.schema.marketplace_import_job import MarketplaceImportJobRequest
@pytest.mark.unit
@@ -31,7 +30,9 @@ class TestMarketplaceService:
test_vendor.owner_user_id = test_user.id
db.commit()
result = self.service.validate_vendor_access(db, test_vendor.vendor_code, test_user)
result = self.service.validate_vendor_access(
db, test_vendor.vendor_code, test_user
)
assert result.vendor_code == test_vendor.vendor_code
assert result.owner_user_id == test_user.id
@@ -39,8 +40,10 @@ class TestMarketplaceService:
def test_validate_vendor_access_admin_can_access_any_vendor(
self, db, test_vendor, test_admin
):
"""Test that admin users can access any vendor """
result = self.service.validate_vendor_access(db, test_vendor.vendor_code, test_admin)
"""Test that admin users can access any vendor"""
result = self.service.validate_vendor_access(
db, test_vendor.vendor_code, test_admin
)
assert result.vendor_code == test_vendor.vendor_code
@@ -57,7 +60,7 @@ class TestMarketplaceService:
def test_validate_vendor_access_permission_denied(
self, db, test_vendor, test_user, other_user
):
"""Test vendor access validation when user doesn't own the vendor """
"""Test vendor access validation when user doesn't own the vendor"""
# Set the vendor owner to a different user
test_vendor.owner_user_id = other_user.id
db.commit()
@@ -93,7 +96,7 @@ class TestMarketplaceService:
assert result.vendor_name == test_vendor.name
def test_create_import_job_invalid_vendor(self, db, test_user):
"""Test import job creation with invalid vendor """
"""Test import job creation with invalid vendor"""
request = MarketplaceImportJobRequest(
url="https://example.com/products.csv",
marketplace="Amazon",
@@ -108,7 +111,9 @@ class TestMarketplaceService:
assert exception.error_code == "VENDOR_NOT_FOUND"
assert "INVALID_VENDOR" in exception.message
def test_create_import_job_unauthorized_access(self, db, test_vendor, test_user, other_user):
def test_create_import_job_unauthorized_access(
self, db, test_vendor, test_user, other_user
):
"""Test import job creation with unauthorized vendor access"""
# Set the vendor owner to a different user
test_vendor.owner_user_id = other_user.id
@@ -127,7 +132,9 @@ class TestMarketplaceService:
exception = exc_info.value
assert exception.error_code == "UNAUTHORIZED_VENDOR_ACCESS"
def test_get_import_job_by_id_success(self, db, test_marketplace_import_job, test_user):
def test_get_import_job_by_id_success(
self, db, test_marketplace_import_job, test_user
):
"""Test getting import job by ID for job owner"""
result = self.service.get_import_job_by_id(
db, test_marketplace_import_job.id, test_user
@@ -161,14 +168,18 @@ class TestMarketplaceService:
):
"""Test access denied when user doesn't own the job"""
with pytest.raises(ImportJobNotOwnedException) as exc_info:
self.service.get_import_job_by_id(db, test_marketplace_import_job.id, other_user)
self.service.get_import_job_by_id(
db, test_marketplace_import_job.id, other_user
)
exception = exc_info.value
assert exception.error_code == "IMPORT_JOB_NOT_OWNED"
assert exception.status_code == 403
assert str(test_marketplace_import_job.id) in exception.message
def test_get_import_jobs_user_filter(self, db, test_marketplace_import_job, test_user):
def test_get_import_jobs_user_filter(
self, db, test_marketplace_import_job, test_user
):
"""Test getting import jobs filtered by user"""
jobs = self.service.get_import_jobs(db, test_user)
@@ -176,7 +187,9 @@ class TestMarketplaceService:
assert any(job.id == test_marketplace_import_job.id for job in jobs)
assert test_marketplace_import_job.user_id == test_user.id
def test_get_import_jobs_admin_sees_all(self, db, test_marketplace_import_job, test_admin):
def test_get_import_jobs_admin_sees_all(
self, db, test_marketplace_import_job, test_admin
):
"""Test that admin sees all import jobs"""
jobs = self.service.get_import_jobs(db, test_admin)
@@ -192,7 +205,9 @@ class TestMarketplaceService:
)
assert len(jobs) >= 1
assert any(job.marketplace == test_marketplace_import_job.marketplace for job in jobs)
assert any(
job.marketplace == test_marketplace_import_job.marketplace for job in jobs
)
def test_get_import_jobs_with_pagination(self, db, test_user, test_vendor):
"""Test getting import jobs with pagination"""
@@ -330,10 +345,14 @@ class TestMarketplaceService:
exception = exc_info.value
assert exception.error_code == "IMPORT_JOB_NOT_FOUND"
def test_cancel_import_job_access_denied(self, db, test_marketplace_import_job, other_user):
def test_cancel_import_job_access_denied(
self, db, test_marketplace_import_job, other_user
):
"""Test cancelling import job without access"""
with pytest.raises(ImportJobNotOwnedException) as exc_info:
self.service.cancel_import_job(db, test_marketplace_import_job.id, other_user)
self.service.cancel_import_job(
db, test_marketplace_import_job.id, other_user
)
exception = exc_info.value
assert exception.error_code == "IMPORT_JOB_NOT_OWNED"
@@ -347,7 +366,9 @@ class TestMarketplaceService:
db.commit()
with pytest.raises(ImportJobCannotBeCancelledException) as exc_info:
self.service.cancel_import_job(db, test_marketplace_import_job.id, test_user)
self.service.cancel_import_job(
db, test_marketplace_import_job.id, test_user
)
exception = exc_info.value
assert exception.error_code == "IMPORT_JOB_CANNOT_BE_CANCELLED"
@@ -396,10 +417,14 @@ class TestMarketplaceService:
exception = exc_info.value
assert exception.error_code == "IMPORT_JOB_NOT_FOUND"
def test_delete_import_job_access_denied(self, db, test_marketplace_import_job, other_user):
def test_delete_import_job_access_denied(
self, db, test_marketplace_import_job, other_user
):
"""Test deleting import job without access"""
with pytest.raises(ImportJobNotOwnedException) as exc_info:
self.service.delete_import_job(db, test_marketplace_import_job.id, other_user)
self.service.delete_import_job(
db, test_marketplace_import_job.id, other_user
)
exception = exc_info.value
assert exception.error_code == "IMPORT_JOB_NOT_OWNED"
@@ -440,11 +465,15 @@ class TestMarketplaceService:
db.commit()
# Test with lowercase vendor code
result = self.service.validate_vendor_access(db, test_vendor.vendor_code.lower(), test_user)
result = self.service.validate_vendor_access(
db, test_vendor.vendor_code.lower(), test_user
)
assert result.vendor_code == test_vendor.vendor_code
# Test with uppercase vendor code
result = self.service.validate_vendor_access(db, test_vendor.vendor_code.upper(), test_user)
result = self.service.validate_vendor_access(
db, test_vendor.vendor_code.upper(), test_user
)
assert result.vendor_code == test_vendor.vendor_code
def test_create_import_job_database_error(self, db_with_error, test_user):

View File

@@ -1,16 +1,15 @@
# tests/test_product_service.py
import pytest
from app.exceptions import (InvalidMarketplaceProductDataException,
MarketplaceProductAlreadyExistsException,
MarketplaceProductNotFoundException,
MarketplaceProductValidationException,
ValidationException)
from app.services.marketplace_product_service import MarketplaceProductService
from app.exceptions import (
MarketplaceProductNotFoundException,
MarketplaceProductAlreadyExistsException,
InvalidMarketplaceProductDataException,
MarketplaceProductValidationException,
ValidationException,
)
from models.schema.marketplace_product import MarketplaceProductCreate, MarketplaceProductUpdate
from models.database.marketplace_product import MarketplaceProduct
from models.schema.marketplace_product import (MarketplaceProductCreate,
MarketplaceProductUpdate)
@pytest.mark.unit
@@ -98,7 +97,10 @@ class TestProductService:
assert exc_info.value.error_code == "PRODUCT_ALREADY_EXISTS"
assert test_marketplace_product.marketplace_product_id in str(exc_info.value)
assert exc_info.value.status_code == 409
assert exc_info.value.details.get("marketplace_product_id") == test_marketplace_product.marketplace_product_id
assert (
exc_info.value.details.get("marketplace_product_id")
== test_marketplace_product.marketplace_product_id
)
def test_create_product_invalid_price(self, db):
"""Test product creation with invalid price raises InvalidMarketplaceProductDataException"""
@@ -117,9 +119,14 @@ class TestProductService:
def test_get_product_by_id_or_raise_success(self, db, test_marketplace_product):
"""Test successful product retrieval by ID"""
product = self.service.get_product_by_id_or_raise(db, test_marketplace_product.marketplace_product_id)
product = self.service.get_product_by_id_or_raise(
db, test_marketplace_product.marketplace_product_id
)
assert product.marketplace_product_id == test_marketplace_product.marketplace_product_id
assert (
product.marketplace_product_id
== test_marketplace_product.marketplace_product_id
)
assert product.title == test_marketplace_product.title
def test_get_product_by_id_or_raise_not_found(self, db):
@@ -152,21 +159,35 @@ class TestProductService:
assert total >= 1
assert len(products) >= 1
# Verify search worked by checking that title contains search term
found_product = next((p for p in products if p.marketplace_product_id == test_marketplace_product.marketplace_product_id), None)
found_product = next(
(
p
for p in products
if p.marketplace_product_id
== test_marketplace_product.marketplace_product_id
),
None,
)
assert found_product is not None
def test_update_product_success(self, db, test_marketplace_product):
"""Test successful product update"""
update_data = MarketplaceProductUpdate(
title="Updated MarketplaceProduct Title",
price="39.99"
title="Updated MarketplaceProduct Title", price="39.99"
)
updated_product = self.service.update_product(db, test_marketplace_product.marketplace_product_id, update_data)
updated_product = self.service.update_product(
db, test_marketplace_product.marketplace_product_id, update_data
)
assert updated_product.title == "Updated MarketplaceProduct Title"
assert updated_product.price == "39.99" # Price is stored as string after processing
assert updated_product.marketplace_product_id == test_marketplace_product.marketplace_product_id # ID unchanged
assert (
updated_product.price == "39.99"
) # Price is stored as string after processing
assert (
updated_product.marketplace_product_id
== test_marketplace_product.marketplace_product_id
) # ID unchanged
def test_update_product_not_found(self, db):
"""Test updating non-existent product raises MarketplaceProductNotFoundException"""
@@ -183,7 +204,9 @@ class TestProductService:
update_data = MarketplaceProductUpdate(gtin="invalid_gtin")
with pytest.raises(InvalidMarketplaceProductDataException) as exc_info:
self.service.update_product(db, test_marketplace_product.marketplace_product_id, update_data)
self.service.update_product(
db, test_marketplace_product.marketplace_product_id, update_data
)
assert exc_info.value.error_code == "INVALID_PRODUCT_DATA"
assert "Invalid GTIN format" in str(exc_info.value)
@@ -194,7 +217,9 @@ class TestProductService:
update_data = MarketplaceProductUpdate(title="")
with pytest.raises(MarketplaceProductValidationException) as exc_info:
self.service.update_product(db, test_marketplace_product.marketplace_product_id, update_data)
self.service.update_product(
db, test_marketplace_product.marketplace_product_id, update_data
)
assert exc_info.value.error_code == "PRODUCT_VALIDATION_FAILED"
assert "MarketplaceProduct title cannot be empty" in str(exc_info.value)
@@ -205,7 +230,9 @@ class TestProductService:
update_data = MarketplaceProductUpdate(price="invalid_price")
with pytest.raises(InvalidMarketplaceProductDataException) as exc_info:
self.service.update_product(db, test_marketplace_product.marketplace_product_id, update_data)
self.service.update_product(
db, test_marketplace_product.marketplace_product_id, update_data
)
assert exc_info.value.error_code == "INVALID_PRODUCT_DATA"
assert "Invalid price format" in str(exc_info.value)
@@ -213,12 +240,16 @@ class TestProductService:
def test_delete_product_success(self, db, test_marketplace_product):
"""Test successful product deletion"""
result = self.service.delete_product(db, test_marketplace_product.marketplace_product_id)
result = self.service.delete_product(
db, test_marketplace_product.marketplace_product_id
)
assert result is True
# Verify product is deleted
deleted_product = self.service.get_product_by_id(db, test_marketplace_product.marketplace_product_id)
deleted_product = self.service.get_product_by_id(
db, test_marketplace_product.marketplace_product_id
)
assert deleted_product is None
def test_delete_product_not_found(self, db):
@@ -229,10 +260,14 @@ class TestProductService:
assert exc_info.value.error_code == "PRODUCT_NOT_FOUND"
assert "NONEXISTENT" in str(exc_info.value)
def test_get_inventory_info_success(self, db, test_marketplace_product_with_inventory):
def test_get_inventory_info_success(
self, db, test_marketplace_product_with_inventory
):
"""Test getting inventory info for product with inventory"""
# Extract the product from the dictionary
marketplace_product = test_marketplace_product_with_inventory['marketplace_product']
marketplace_product = test_marketplace_product_with_inventory[
"marketplace_product"
]
inventory_info = self.service.get_inventory_info(db, marketplace_product.gtin)
@@ -243,13 +278,17 @@ class TestProductService:
def test_get_inventory_info_no_inventory(self, db, test_marketplace_product):
"""Test getting inventory info for product without inventory"""
inventory_info = self.service.get_inventory_info(db, test_marketplace_product.gtin or "1234567890123")
inventory_info = self.service.get_inventory_info(
db, test_marketplace_product.gtin or "1234567890123"
)
assert inventory_info is None
def test_product_exists_true(self, db, test_marketplace_product):
"""Test product_exists returns True for existing product"""
exists = self.service.product_exists(db, test_marketplace_product.marketplace_product_id)
exists = self.service.product_exists(
db, test_marketplace_product.marketplace_product_id
)
assert exists is True
def test_product_exists_false(self, db):
@@ -265,7 +304,9 @@ class TestProductService:
csv_lines = list(csv_generator)
assert len(csv_lines) > 1 # Header + at least one data row
assert csv_lines[0].startswith("marketplace_product_id,title,description") # Check header
assert csv_lines[0].startswith(
"marketplace_product_id,title,description"
) # Check header
# Check that test product appears in CSV
csv_content = "".join(csv_lines)
@@ -274,8 +315,7 @@ class TestProductService:
def test_generate_csv_export_with_filters(self, db, test_marketplace_product):
"""Test CSV export with marketplace filter"""
csv_generator = self.service.generate_csv_export(
db,
marketplace=test_marketplace_product.marketplace
db, marketplace=test_marketplace_product.marketplace
)
csv_lines = list(csv_generator)

View File

@@ -2,8 +2,8 @@
import pytest
from app.services.stats_service import StatsService
from models.database.marketplace_product import MarketplaceProduct
from models.database.inventory import Inventory
from models.database.marketplace_product import MarketplaceProduct
@pytest.mark.unit
@@ -15,7 +15,9 @@ class TestStatsService:
"""Setup method following the same pattern as other service tests"""
self.service = StatsService()
def test_get_comprehensive_stats_basic(self, db, test_marketplace_product, test_inventory):
def test_get_comprehensive_stats_basic(
self, db, test_marketplace_product, test_inventory
):
"""Test getting comprehensive stats with basic data"""
stats = self.service.get_comprehensive_stats(db)
@@ -31,7 +33,9 @@ class TestStatsService:
assert stats["total_inventory_entries"] >= 1
assert stats["total_inventory_quantity"] >= 10 # test_inventory has quantity 10
def test_get_comprehensive_stats_multiple_products(self, db, test_marketplace_product):
def test_get_comprehensive_stats_multiple_products(
self, db, test_marketplace_product
):
"""Test comprehensive stats with multiple products across different dimensions"""
# Create products with different brands, categories, marketplaces
additional_products = [
@@ -87,7 +91,7 @@ class TestStatsService:
brand=None, # Null brand
google_product_category=None, # Null category
marketplace=None, # Null marketplace
vendor_name=None, # Null vendor
vendor_name=None, # Null vendor
price="10.00",
currency="EUR",
),
@@ -97,7 +101,7 @@ class TestStatsService:
brand="", # Empty brand
google_product_category="", # Empty category
marketplace="", # Empty marketplace
vendor_name="", # Empty vendor
vendor_name="", # Empty vendor
price="15.00",
currency="EUR",
),
@@ -124,7 +128,11 @@ class TestStatsService:
# Find our test marketplace in the results
test_marketplace_stat = next(
(stat for stat in stats if stat["marketplace"] == test_marketplace_product.marketplace),
(
stat
for stat in stats
if stat["marketplace"] == test_marketplace_product.marketplace
),
None,
)
assert test_marketplace_stat is not None
@@ -309,7 +317,9 @@ class TestStatsService:
count = self.service._get_unique_marketplaces_count(db)
assert count >= 2 # At least Amazon and eBay, plus test_marketplace_product marketplace
assert (
count >= 2
) # At least Amazon and eBay, plus test_marketplace_product marketplace
assert isinstance(count, int)
def test_get_unique_vendors_count(self, db, test_marketplace_product):
@@ -338,7 +348,9 @@ class TestStatsService:
count = self.service._get_unique_vendors_count(db)
assert count >= 2 # At least VendorA and VendorB, plus test_marketplace_product vendor
assert (
count >= 2
) # At least VendorA and VendorB, plus test_marketplace_product vendor
assert isinstance(count, int)
def test_get_inventory_statistics(self, db, test_inventory):
@@ -438,7 +450,7 @@ class TestStatsService:
db.add_all(marketplace_products)
db.commit()
vendors =self.service._get_vendors_by_marketplace(db, "TestMarketplace")
vendors = self.service._get_vendors_by_marketplace(db, "TestMarketplace")
assert len(vendors) == 2
assert "TestVendor1" in vendors
@@ -482,7 +494,9 @@ class TestStatsService:
def test_get_products_by_marketplace_not_found(self, db):
"""Test getting product count for non-existent marketplace"""
count = self.service._get_products_by_marketplace_count(db, "NonExistentMarketplace")
count = self.service._get_products_by_marketplace_count(
db, "NonExistentMarketplace"
)
assert count == 0

View File

@@ -1,19 +1,16 @@
# tests/test_vendor_service.py (updated to use custom exceptions)
import pytest
from app.exceptions import (InvalidVendorDataException,
MarketplaceProductNotFoundException,
MaxVendorsReachedException,
ProductAlreadyExistsException,
UnauthorizedVendorAccessException,
ValidationException, VendorAlreadyExistsException,
VendorNotFoundException)
from app.services.vendor_service import VendorService
from app.exceptions import (
VendorNotFoundException,
VendorAlreadyExistsException,
UnauthorizedVendorAccessException,
InvalidVendorDataException,
MarketplaceProductNotFoundException,
ProductAlreadyExistsException,
MaxVendorsReachedException,
ValidationException,
)
from models.schema.vendor import VendorCreate
from models.schema.product import ProductCreate
from models.schema.vendor import VendorCreate
@pytest.mark.unit
@@ -38,15 +35,17 @@ class TestVendorService:
assert vendor is not None
assert vendor.vendor_code == "NEWVENDOR"
assert vendor.owner_user_id == test_user.id
assert vendor.is_verified is False # Regular user creates unverified vendor
assert vendor.is_verified is False # Regular user creates unverified vendor
def test_create_vendor_admin_auto_verify(self, db, test_admin, vendor_factory):
"""Test admin creates verified vendor automatically"""
vendor_data = VendorCreate(vendor_code="ADMINVENDOR", vendor_name="Admin Test Vendor")
vendor_data = VendorCreate(
vendor_code="ADMINVENDOR", vendor_name="Admin Test Vendor"
)
vendor = self.service.create_vendor(db, vendor_data, test_admin)
assert vendor.is_verified is True # Admin creates verified vendor
assert vendor.is_verified is True # Admin creates verified vendor
def test_create_vendor_duplicate_code(self, db, test_user, test_vendor):
"""Test vendor creation fails with duplicate vendor code"""
@@ -88,7 +87,9 @@ class TestVendorService:
def test_create_vendor_invalid_code_format(self, db, test_user):
"""Test vendor creation fails with invalid vendor code format"""
vendor_data = VendorCreate(vendor_code="INVALID@CODE!", vendor_name="Test Vendor")
vendor_data = VendorCreate(
vendor_code="INVALID@CODE!", vendor_name="Test Vendor"
)
with pytest.raises(InvalidVendorDataException) as exc_info:
self.service.create_vendor(db, vendor_data, test_user)
@@ -105,7 +106,9 @@ class TestVendorService:
def mock_check_vendor_limit(self, db, user):
raise MaxVendorsReachedException(max_vendors=5, user_id=user.id)
monkeypatch.setattr(VendorService, "_check_vendor_limit", mock_check_vendor_limit)
monkeypatch.setattr(
VendorService, "_check_vendor_limit", mock_check_vendor_limit
)
vendor_data = VendorCreate(vendor_code="NEWVENDOR", vendor_name="New Vendor")
@@ -118,7 +121,9 @@ class TestVendorService:
assert exception.details["max_vendors"] == 5
assert exception.details["user_id"] == test_user.id
def test_get_vendors_regular_user(self, db, test_user, test_vendor, inactive_vendor):
def test_get_vendors_regular_user(
self, db, test_user, test_vendor, inactive_vendor
):
"""Test regular user can only see active verified vendors and own vendors"""
vendors, total = self.service.get_vendors(db, test_user, skip=0, limit=10)
@@ -127,7 +132,7 @@ class TestVendorService:
assert inactive_vendor.vendor_code not in vendor_codes
def test_get_vendors_admin_user(
self, db, test_admin, test_vendor, inactive_vendor, verified_vendor
self, db, test_admin, test_vendor, inactive_vendor, verified_vendor
):
"""Test admin user can see all vendors with filters"""
vendors, total = self.service.get_vendors(
@@ -140,14 +145,16 @@ class TestVendorService:
assert verified_vendor.vendor_code in vendor_codes
def test_get_vendor_by_code_owner_access(self, db, test_user, test_vendor):
"""Test vendor owner can access their own vendor """
vendor = self.service.get_vendor_by_code(db, test_vendor.vendor_code.lower(), test_user)
"""Test vendor owner can access their own vendor"""
vendor = self.service.get_vendor_by_code(
db, test_vendor.vendor_code.lower(), test_user
)
assert vendor is not None
assert vendor.id == test_vendor.id
def test_get_vendor_by_code_admin_access(self, db, test_admin, test_vendor):
"""Test admin can access any vendor """
"""Test admin can access any vendor"""
vendor = self.service.get_vendor_by_code(
db, test_vendor.vendor_code.lower(), test_admin
)
@@ -178,16 +185,14 @@ class TestVendorService:
assert exception.details["user_id"] == test_user.id
def test_add_product_to_vendor_success(self, db, test_vendor, unique_product):
"""Test successfully adding product to vendor """
"""Test successfully adding product to vendor"""
product_data = ProductCreate(
marketplace_product_id=unique_product.marketplace_product_id,
price="15.99",
is_featured=True,
)
product = self.service.add_product_to_catalog(
db, test_vendor, product_data
)
product = self.service.add_product_to_catalog(db, test_vendor, product_data)
assert product is not None
assert product.vendor_id == test_vendor.id
@@ -195,7 +200,9 @@ class TestVendorService:
def test_add_product_to_vendor_product_not_found(self, db, test_vendor):
"""Test adding non-existent product to vendor fails"""
product_data = ProductCreate(marketplace_product_id="NONEXISTENT", price="15.99")
product_data = ProductCreate(
marketplace_product_id="NONEXISTENT", price="15.99"
)
with pytest.raises(MarketplaceProductNotFoundException) as exc_info:
self.service.add_product_to_catalog(db, test_vendor, product_data)
@@ -209,7 +216,8 @@ class TestVendorService:
def test_add_product_to_vendor_already_exists(self, db, test_vendor, test_product):
"""Test adding product that's already in vendor fails"""
product_data = ProductCreate(
marketplace_product_id=test_product.marketplace_product.marketplace_product_id, price="15.99"
marketplace_product_id=test_product.marketplace_product.marketplace_product_id,
price="15.99",
)
with pytest.raises(ProductAlreadyExistsException) as exc_info:
@@ -219,11 +227,12 @@ class TestVendorService:
assert exception.status_code == 409
assert exception.error_code == "PRODUCT_ALREADY_EXISTS"
assert exception.details["vendor_code"] == test_vendor.vendor_code
assert exception.details["marketplace_product_id"] == test_product.marketplace_product.marketplace_product_id
assert (
exception.details["marketplace_product_id"]
== test_product.marketplace_product.marketplace_product_id
)
def test_get_products_owner_access(
self, db, test_user, test_vendor, test_product
):
def test_get_products_owner_access(self, db, test_user, test_vendor, test_product):
"""Test vendor owner can get vendor products"""
products, total = self.service.get_products(db, test_vendor, test_user)
@@ -291,7 +300,9 @@ class TestVendorService:
assert exception.error_code == "VALIDATION_ERROR"
assert "Failed to retrieve vendors" in exception.message
def test_add_product_database_error(self, db, test_vendor, unique_product, monkeypatch):
def test_add_product_database_error(
self, db, test_vendor, unique_product, monkeypatch
):
"""Test add product handles database errors gracefully"""
def mock_commit():