# tests/integration/api/v1/test_pagination.py import pytest from models.database.marketplace_product import MarketplaceProduct from models.database.vendor import Vendor @pytest.mark.integration @pytest.mark.api @pytest.mark.database @pytest.mark.products class TestPagination: def test_product_pagination_success(self, client, auth_headers, db): """Test pagination for product listing successfully""" import uuid unique_suffix = str(uuid.uuid4())[:8] # Create multiple products products = [] for i in range(25): product = MarketplaceProduct( marketplace_product_id=f"PAGE{i:03d}_{unique_suffix}", title=f"Pagination Test MarketplaceProduct {i}", marketplace="PaginationTest", ) products.append(product) db.add_all(products) db.commit() # Test first page response = client.get( "/api/v1/marketplace/product?limit=10&skip=0", headers=auth_headers ) assert response.status_code == 200 data = response.json() assert len(data["products"]) == 10 assert data["total"] >= 25 # At least our test products assert data["skip"] == 0 assert data["limit"] == 10 # Test second page response = client.get( "/api/v1/marketplace/product?limit=10&skip=10", headers=auth_headers ) assert response.status_code == 200 data = response.json() assert len(data["products"]) == 10 assert data["skip"] == 10 # Test last page (should have remaining products) response = client.get( "/api/v1/marketplace/product?limit=10&skip=20", headers=auth_headers ) assert response.status_code == 200 data = response.json() assert len(data["products"]) >= 5 # At least 5 remaining from our test set def test_pagination_boundary_negative_skip_validation_error( self, client, auth_headers ): """Test negative skip parameter returns ValidationException""" response = client.get( "/api/v1/marketplace/product?skip=-1", headers=auth_headers ) assert response.status_code == 422 data = response.json() assert data["error_code"] == "VALIDATION_ERROR" assert data["status_code"] == 422 assert "Request validation failed" in data["message"] assert "validation_errors" in data["details"] def test_pagination_boundary_zero_limit_validation_error( self, client, auth_headers ): """Test zero limit parameter returns ValidationException""" response = client.get( "/api/v1/marketplace/product?limit=0", headers=auth_headers ) assert response.status_code == 422 data = response.json() assert data["error_code"] == "VALIDATION_ERROR" assert data["status_code"] == 422 assert "Request validation failed" in data["message"] def test_pagination_boundary_excessive_limit_validation_error( self, client, auth_headers ): """Test excessive limit parameter returns ValidationException""" response = client.get( "/api/v1/marketplace/product?limit=10000", headers=auth_headers ) assert response.status_code == 422 data = response.json() assert data["error_code"] == "VALIDATION_ERROR" assert data["status_code"] == 422 assert "Request validation failed" in data["message"] def test_pagination_beyond_available_records(self, client, auth_headers, db): """Test pagination beyond available records returns empty results""" # Test skip beyond available records response = client.get( "/api/v1/marketplace/product?skip=10000&limit=10", headers=auth_headers ) assert response.status_code == 200 data = response.json() assert data["products"] == [] assert data["skip"] == 10000 assert data["limit"] == 10 # total should still reflect actual count def test_pagination_with_filters(self, client, auth_headers, db): """Test pagination combined with filtering""" import uuid unique_suffix = str(uuid.uuid4())[:8] # Create products with same brand for filtering products = [] for i in range(15): product = MarketplaceProduct( marketplace_product_id=f"FILTPAGE{i:03d}_{unique_suffix}", title=f"Filter Page MarketplaceProduct {i}", brand="FilterBrand", marketplace="FilterMarket", ) products.append(product) db.add_all(products) db.commit() # Test first page with filter response = client.get( "/api/v1/marketplace/product?brand=FilterBrand&limit=5&skip=0", headers=auth_headers, ) assert response.status_code == 200 data = response.json() assert len(data["products"]) == 5 assert data["total"] >= 15 # At least our test products # Verify all products have the filtered brand test_products = [ p for p in data["products"] if p["marketplace_product_id"].endswith(unique_suffix) ] for product in test_products: assert product["brand"] == "FilterBrand" # Test second page with same filter response = client.get( "/api/v1/marketplace/product?brand=FilterBrand&limit=5&skip=5", headers=auth_headers, ) assert response.status_code == 200 data = response.json() assert len(data["products"]) == 5 assert data["skip"] == 5 def test_pagination_default_values(self, client, auth_headers): """Test pagination with default values""" response = client.get("/api/v1/marketplace/product", headers=auth_headers) assert response.status_code == 200 data = response.json() assert data["skip"] == 0 # Default skip assert data["limit"] == 100 # Default limit assert len(data["products"]) <= 100 # Should not exceed limit def test_pagination_consistency(self, client, auth_headers, db): """Test pagination consistency across multiple requests""" import uuid unique_suffix = str(uuid.uuid4())[:8] # Create products with predictable ordering products = [] for i in range(10): product = MarketplaceProduct( marketplace_product_id=f"CONSIST{i:03d}_{unique_suffix}", title=f"Consistent MarketplaceProduct {i:03d}", marketplace="ConsistentMarket", ) products.append(product) db.add_all(products) db.commit() # Get first page response1 = client.get( "/api/v1/marketplace/product?limit=5&skip=0", headers=auth_headers ) assert response1.status_code == 200 first_page_ids = [ p["marketplace_product_id"] for p in response1.json()["products"] ] # Get second page response2 = client.get( "/api/v1/marketplace/product?limit=5&skip=5", headers=auth_headers ) assert response2.status_code == 200 second_page_ids = [ p["marketplace_product_id"] for p in response2.json()["products"] ] # Verify no overlap between pages overlap = set(first_page_ids) & set(second_page_ids) assert len(overlap) == 0, "Pages should not have overlapping products" def test_vendor_pagination_success(self, client, admin_headers, db, test_user): """Test pagination for vendor listing successfully""" import uuid unique_suffix = str(uuid.uuid4())[:8] # Create multiple vendors for pagination testing vendors = [] for i in range(15): vendor = Vendor( vendor_code=f"PAGEVENDOR{i:03d}_{unique_suffix}", vendor_name=f"Pagination Vendor {i}", owner_user_id=test_user.id, is_active=True, ) vendors.append(vendor) db.add_all(vendors) db.commit() # Test first page (assuming admin endpoint exists) response = client.get("/api/v1/vendor?limit=5&skip=0", headers=admin_headers) assert response.status_code == 200 data = response.json() assert len(data["vendors"]) == 5 assert data["total"] >= 15 # At least our test vendors assert data["skip"] == 0 assert data["limit"] == 5 def test_inventory_pagination_success(self, client, auth_headers, db): """Test pagination for inventory listing successfully""" import uuid unique_suffix = str(uuid.uuid4())[:8] # Create multiple inventory entries from models.database.inventory import Inventory inventory_entries = [] for i in range(20): inventory = Inventory( gtin=f"123456789{i:04d}", location=f"LOC_{unique_suffix}_{i}", quantity=10 + i, ) inventory_entries.append(inventory) db.add_all(inventory_entries) db.commit() # Test first page response = client.get("/api/v1/inventory?limit=8&skip=0", headers=auth_headers) assert response.status_code == 200 data = response.json() assert len(data) == 8 # Test second page response = client.get("/api/v1/inventory?limit=8&skip=8", headers=auth_headers) assert response.status_code == 200 data = response.json() assert len(data) == 8 def test_pagination_performance_large_offset(self, client, auth_headers, db): """Test pagination performance with large offset values""" # Test with large skip value (should still be reasonable performance) import time start_time = time.time() response = client.get( "/api/v1/marketplace/product?skip=1000&limit=10", headers=auth_headers ) end_time = time.time() assert response.status_code == 200 assert end_time - start_time < 5.0 # Should complete within 5 seconds data = response.json() assert data["skip"] == 1000 assert data["limit"] == 10 def test_pagination_with_invalid_parameters_types(self, client, auth_headers): """Test pagination with invalid parameter types returns ValidationException""" # Test non-numeric skip response = client.get( "/api/v1/marketplace/product?skip=invalid", headers=auth_headers ) assert response.status_code == 422 data = response.json() assert data["error_code"] == "VALIDATION_ERROR" # Test non-numeric limit response = client.get( "/api/v1/marketplace/product?limit=invalid", headers=auth_headers ) assert response.status_code == 422 data = response.json() assert data["error_code"] == "VALIDATION_ERROR" # Test float values (should be converted or rejected) response = client.get( "/api/v1/marketplace/product?skip=10.5&limit=5.5", headers=auth_headers ) assert response.status_code in [200, 422] # Depends on implementation def test_empty_dataset_pagination(self, client, auth_headers): """Test pagination behavior with empty dataset""" # Use a filter that should return no results response = client.get( "/api/v1/marketplace/product?brand=NonexistentBrand999&limit=10&skip=0", headers=auth_headers, ) assert response.status_code == 200 data = response.json() assert data["products"] == [] assert data["total"] == 0 assert data["skip"] == 0 assert data["limit"] == 10 def test_exception_structure_in_pagination_errors(self, client, auth_headers): """Test that pagination validation errors follow consistent exception structure""" response = client.get( "/api/v1/marketplace/product?skip=-1", headers=auth_headers ) assert response.status_code == 422 data = response.json() # Verify exception structure matches WizamartException.to_dict() required_fields = ["error_code", "message", "status_code"] for field in required_fields: assert field in data, f"Missing required field: {field}" assert isinstance(data["error_code"], str) assert isinstance(data["message"], str) assert isinstance(data["status_code"], int) assert data["error_code"] == "VALIDATION_ERROR" assert data["status_code"] == 422 # Details should contain validation errors if "details" in data: assert isinstance(data["details"], dict) assert "validation_errors" in data["details"]