# 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"