Files
orion/tests/unit/services/test_marketplace_service.py
Samir Boulahtit 238c1ec9b8 refactor: modernize code quality tooling with Ruff
- Replace black, isort, and flake8 with Ruff (all-in-one linter and formatter)
- Add comprehensive pyproject.toml configuration
- Simplify Makefile code quality targets
- Configure exclusions for venv/.venv in pyproject.toml
- Auto-fix 1,359 linting issues across codebase

Benefits:
- Much faster builds (Ruff is written in Rust)
- Single tool replaces multiple tools
- More comprehensive rule set (UP, B, C4, SIM, PIE, RET, Q)
- All configuration centralized in pyproject.toml
- Better import sorting and formatting consistency

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-28 19:37:38 +01:00

494 lines
18 KiB
Python

# tests/test_marketplace_service.py
import uuid
import pytest
from app.exceptions.base import ValidationException
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.schema.marketplace_import_job import MarketplaceImportJobRequest
@pytest.mark.unit
@pytest.mark.marketplace
class TestMarketplaceService:
def setup_method(self):
self.service = MarketplaceImportJobService()
def test_validate_vendor_access_success(self, db, test_vendor, test_user):
"""Test successful vendor access validation"""
# Set the vendor owner to the test user
test_vendor.owner_user_id = test_user.id
db.commit()
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
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
)
assert result.vendor_code == test_vendor.vendor_code
def test_validate_vendor_access_vendor_not_found(self, db, test_user):
"""Test vendor access validation when vendor doesn't exist"""
with pytest.raises(VendorNotFoundException) as exc_info:
self.service.validate_vendor_access(db, "NONEXISTENT", test_user)
exception = exc_info.value
assert exception.error_code == "VENDOR_NOT_FOUND"
assert exception.status_code == 404
assert "NONEXISTENT" in exception.message
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"""
# Set the vendor owner to a different user
test_vendor.owner_user_id = other_user.id
db.commit()
with pytest.raises(UnauthorizedVendorAccessException) as exc_info:
self.service.validate_vendor_access(db, test_vendor.vendor_code, test_user)
exception = exc_info.value
assert exception.error_code == "UNAUTHORIZED_VENDOR_ACCESS"
assert exception.status_code == 403
assert test_vendor.vendor_code in exception.message
def test_create_import_job_success(self, db, test_vendor, test_user):
"""Test successful creation of import job"""
# Set the vendor owner to the test user
test_vendor.owner_user_id = test_user.id
db.commit()
request = MarketplaceImportJobRequest(
url="https://example.com/products.csv",
marketplace="Amazon",
vendor_code=test_vendor.vendor_code,
batch_size=1000,
)
result = self.service.create_import_job(db, request, test_user)
assert result.marketplace == "Amazon"
assert result.vendor_id == test_vendor.id
assert result.user_id == test_user.id
assert result.status == "pending"
assert result.source_url == "https://example.com/products.csv"
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"""
request = MarketplaceImportJobRequest(
url="https://example.com/products.csv",
marketplace="Amazon",
vendor_code="INVALID_VENDOR",
batch_size=1000,
)
with pytest.raises(VendorNotFoundException) as exc_info:
self.service.create_import_job(db, request, test_user)
exception = exc_info.value
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
):
"""Test import job creation with unauthorized vendor access"""
# Set the vendor owner to a different user
test_vendor.owner_user_id = other_user.id
db.commit()
request = MarketplaceImportJobRequest(
url="https://example.com/products.csv",
marketplace="Amazon",
vendor_code=test_vendor.vendor_code,
batch_size=1000,
)
with pytest.raises(UnauthorizedVendorAccessException) as exc_info:
self.service.create_import_job(db, request, test_user)
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
):
"""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
)
assert result.id == test_marketplace_import_job.id
assert result.user_id == test_user.id
def test_get_import_job_by_id_admin_access(
self, db, test_marketplace_import_job, test_admin
):
"""Test that admin can access any import job"""
result = self.service.get_import_job_by_id(
db, test_marketplace_import_job.id, test_admin
)
assert result.id == test_marketplace_import_job.id
def test_get_import_job_by_id_not_found(self, db, test_user):
"""Test getting non-existent import job"""
with pytest.raises(ImportJobNotFoundException) as exc_info:
self.service.get_import_job_by_id(db, 99999, test_user)
exception = exc_info.value
assert exception.error_code == "IMPORT_JOB_NOT_FOUND"
assert exception.status_code == 404
assert "99999" in exception.message
def test_get_import_job_by_id_access_denied(
self, db, test_marketplace_import_job, other_user
):
"""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
)
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
):
"""Test getting import jobs filtered by user"""
jobs = self.service.get_import_jobs(db, test_user)
assert len(jobs) >= 1
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
):
"""Test that admin sees all import jobs"""
jobs = self.service.get_import_jobs(db, test_admin)
assert len(jobs) >= 1
assert any(job.id == test_marketplace_import_job.id for job in jobs)
def test_get_import_jobs_with_marketplace_filter(
self, db, test_marketplace_import_job, test_user
):
"""Test getting import jobs with marketplace filter"""
jobs = self.service.get_import_jobs(
db, test_user, marketplace=test_marketplace_import_job.marketplace
)
assert len(jobs) >= 1
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"""
unique_id = str(uuid.uuid4())[:8]
# Create multiple import jobs
for i in range(5):
job = MarketplaceImportJob(
status="completed",
marketplace=f"Marketplace_{unique_id}_{i}",
vendor_name=f"Test_vendor_{unique_id}_{i}",
user_id=test_user.id,
vendor_id=test_vendor.id,
source_url=f"https://test-{i}.example.com/import",
imported_count=0,
updated_count=0,
total_processed=0,
error_count=0,
)
db.add(job)
db.commit()
jobs = self.service.get_import_jobs(db, test_user, skip=2, limit=2)
assert len(jobs) <= 2 # Should be at most 2
def test_get_import_jobs_database_error(self, db_with_error, test_user):
"""Test getting import jobs handles database errors"""
with pytest.raises(ValidationException) as exc_info:
self.service.get_import_jobs(db_with_error, test_user)
exception = exc_info.value
assert exception.error_code == "VALIDATION_ERROR"
assert "Failed to retrieve import jobs" in exception.message
def test_update_job_status_success(self, db, test_marketplace_import_job):
"""Test updating job status"""
result = self.service.update_job_status(
db,
test_marketplace_import_job.id,
"completed",
imported_count=100,
total_processed=100,
)
assert result.status == "completed"
assert result.imported_count == 100
assert result.total_processed == 100
def test_update_job_status_not_found(self, db):
"""Test updating non-existent job status"""
with pytest.raises(ImportJobNotFoundException) as exc_info:
self.service.update_job_status(db, 99999, "completed")
exception = exc_info.value
assert exception.error_code == "IMPORT_JOB_NOT_FOUND"
assert "99999" in exception.message
def test_update_job_status_database_error(self, db_with_error):
"""Test updating job status handles database errors"""
with pytest.raises(ValidationException) as exc_info:
self.service.update_job_status(db_with_error, 1, "completed")
exception = exc_info.value
assert exception.error_code == "VALIDATION_ERROR"
assert "Failed to update job status" in exception.message
def test_get_job_stats_user(self, db, test_marketplace_import_job, test_user):
"""Test getting job statistics for user"""
stats = self.service.get_job_stats(db, test_user)
assert stats["total_jobs"] >= 1
assert "pending_jobs" in stats
assert "running_jobs" in stats
assert "completed_jobs" in stats
assert "failed_jobs" in stats
assert isinstance(stats["total_jobs"], int)
def test_get_job_stats_admin(self, db, test_marketplace_import_job, test_admin):
"""Test getting job statistics for admin"""
stats = self.service.get_job_stats(db, test_admin)
assert stats["total_jobs"] >= 1
assert isinstance(stats["total_jobs"], int)
def test_get_job_stats_database_error(self, db_with_error, test_user):
"""Test getting job stats handles database errors"""
with pytest.raises(ValidationException) as exc_info:
self.service.get_job_stats(db_with_error, test_user)
exception = exc_info.value
assert exception.error_code == "VALIDATION_ERROR"
assert "Failed to retrieve job statistics" in exception.message
def test_convert_to_response_model(self, test_marketplace_import_job):
"""Test converting database model to response model"""
response = self.service.convert_to_response_model(test_marketplace_import_job)
assert response.job_id == test_marketplace_import_job.id
assert response.status == test_marketplace_import_job.status
assert response.marketplace == test_marketplace_import_job.marketplace
assert response.imported == (test_marketplace_import_job.imported_count or 0)
def test_cancel_import_job_success(self, db, test_user, test_vendor):
"""Test cancelling a pending import job"""
unique_id = str(uuid.uuid4())[:8]
# Create a pending job
job = MarketplaceImportJob(
status="pending",
marketplace="Amazon",
vendor_name=f"TEST_VENDOR_{unique_id}",
user_id=test_user.id,
vendor_id=test_vendor.id,
source_url="https://test.example.com/import",
imported_count=0,
updated_count=0,
total_processed=0,
error_count=0,
)
db.add(job)
db.commit()
db.refresh(job)
result = self.service.cancel_import_job(db, job.id, test_user)
assert result.status == "cancelled"
assert result.completed_at is not None
def test_cancel_import_job_not_found(self, db, test_user):
"""Test cancelling non-existent import job"""
with pytest.raises(ImportJobNotFoundException) as exc_info:
self.service.cancel_import_job(db, 99999, test_user)
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
):
"""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
)
exception = exc_info.value
assert exception.error_code == "IMPORT_JOB_NOT_OWNED"
def test_cancel_import_job_invalid_status(
self, db, test_marketplace_import_job, test_user
):
"""Test cancelling a job that can't be cancelled"""
# Set job status to completed
test_marketplace_import_job.status = "completed"
db.commit()
with pytest.raises(ImportJobCannotBeCancelledException) as exc_info:
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"
assert exception.status_code == 400
assert "completed" in exception.message
def test_delete_import_job_success(self, db, test_user, test_vendor):
"""Test deleting a completed import job"""
unique_id = str(uuid.uuid4())[:8]
# Create a completed job
job = MarketplaceImportJob(
status="completed",
marketplace="Amazon",
vendor_name=f"TEST_VENDOR_{unique_id}",
user_id=test_user.id,
vendor_id=test_vendor.id,
source_url="https://test.example.com/import",
imported_count=0,
updated_count=0,
total_processed=0,
error_count=0,
)
db.add(job)
db.commit()
db.refresh(job)
job_id = job.id
result = self.service.delete_import_job(db, job_id, test_user)
assert result is True
# Verify the job is actually deleted
deleted_job = (
db.query(MarketplaceImportJob)
.filter(MarketplaceImportJob.id == job_id)
.first()
)
assert deleted_job is None
def test_delete_import_job_not_found(self, db, test_user):
"""Test deleting non-existent import job"""
with pytest.raises(ImportJobNotFoundException) as exc_info:
self.service.delete_import_job(db, 99999, test_user)
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
):
"""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
)
exception = exc_info.value
assert exception.error_code == "IMPORT_JOB_NOT_OWNED"
def test_delete_import_job_invalid_status(self, db, test_user, test_vendor):
"""Test deleting a job that can't be deleted"""
unique_id = str(uuid.uuid4())[:8]
# Create a pending job
job = MarketplaceImportJob(
status="pending",
marketplace="Amazon",
vendor_name=f"TEST_VENDOR_{unique_id}",
user_id=test_user.id,
vendor_id=test_vendor.id,
source_url="https://test.example.com/import",
imported_count=0,
updated_count=0,
total_processed=0,
error_count=0,
)
db.add(job)
db.commit()
db.refresh(job)
with pytest.raises(ImportJobCannotBeDeletedException) as exc_info:
self.service.delete_import_job(db, job.id, test_user)
exception = exc_info.value
assert exception.error_code == "IMPORT_JOB_CANNOT_BE_DELETED"
assert exception.status_code == 400
assert "pending" in exception.message
# Test edge cases and error scenarios
def test_validate_vendor_access_case_insensitive(self, db, test_vendor, test_user):
"""Test vendor access validation is case insensitive"""
test_vendor.owner_user_id = test_user.id
db.commit()
# Test with lowercase vendor code
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
)
assert result.vendor_code == test_vendor.vendor_code
def test_create_import_job_database_error(self, db_with_error, test_user):
"""Test import job creation handles database errors"""
request = MarketplaceImportJobRequest(
url="https://example.com/products.csv",
marketplace="Amazon",
vendor_code="TEST_VENDOR",
batch_size=1000,
)
with pytest.raises(ValidationException) as exc_info:
self.service.create_import_job(db_with_error, request, test_user)
exception = exc_info.value
assert exception.error_code == "VALIDATION_ERROR"