Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion backend/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ def get_products_api(
stmt = (
stmt.join(ProductDeliveryLink)
.join(DeliveryOption)
.distinct(cast(Any, Product.id))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical Bug: When using DISTINCT ON with PostgreSQL, the expression in distinct() must match the first column(s) in order_by(). Here, distinct(Product.id) is used but order_by() starts with DeliveryOption.estimated_days_min. This will cause a database error.

The correct pattern is:

.distinct(Product.id)
.order_by(Product.id, DeliveryOption.estimated_days_min.asc(), ...)

This same issue appears in all three locations where .distinct() was added (lines 228, 240, 254).

.order_by(
cast(
ColumnElement[int], DeliveryOption.estimated_days_min
Expand All @@ -236,6 +237,7 @@ def get_products_api(
stmt = (
stmt.join(ProductDeliveryLink)
.join(DeliveryOption)
.distinct(cast(Any, Product.id))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical Bug: Same DISTINCT ON issue as line 228. The distinct(Product.id) must be followed by order_by(Product.id, ...) as the first ordering column.

.order_by(
cast(
ColumnElement[int], DeliveryOption.estimated_days_min
Expand All @@ -244,11 +246,12 @@ def get_products_api(
)
)
else:
stmt = stmt.order_by(cast(ColumnElement, Product.created_at).desc())
stmt = stmt.order_by(cast(ColumnElement[Any], Product.created_at).desc())
else:
stmt = (
stmt.join(ProductDeliveryLink)
.join(DeliveryOption)
.distinct(cast(Any, Product.id))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical Bug: Same DISTINCT ON issue as lines 228 and 240. The distinct(Product.id) must be followed by order_by(Product.id, ...) as the first ordering column.

.order_by(
cast(ColumnElement[int], DeliveryOption.estimated_days_min).asc(),
cast(ColumnElement[float], Product.price).asc(),
Expand Down
67 changes: 67 additions & 0 deletions backend/tests/api/test_delivery_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,3 +229,70 @@ def test_product_with_minimum_order_amount_api_response(
# Verify minimum order amounts are correctly returned
assert standard["min_order_amount"] == 25.0
assert premium["min_order_amount"] is None


def test_fastest_delivery_sort_no_duplicates(client: TestClient, session: Session):
"""Test that fastest delivery sort does not return duplicate products (issue #35)"""
# Create a product with multiple delivery options
product = create_test_product(
session, title="Multi-Option Product", price=50.0
)
delivery_options = create_standard_delivery_options(session)
product.delivery_options = delivery_options
session.add(product)
session.commit()

# Fetch products with fastest delivery sort
response = client.get("/products?sort=delivery_fastest")
assert response.status_code == 200

products = response.json()

# Count occurrences of our product
product_count = sum(
1 for p in products if p["id"] == product.id
)

# Product should appear exactly once, not multiple times (one per delivery option)
assert product_count == 1, f"Product appeared {product_count} times, expected 1"


def test_fastest_delivery_sort_with_category_filter_no_duplicates(
client: TestClient, session: Session
):
"""Test that fastest delivery sort with category filter does not return duplicates"""
# Create a unique category
from tests.factories import create_test_category
import uuid
unique_name = f"TestCategory-{uuid.uuid4().hex[:8]}"
category = create_test_category(session, name=unique_name)

# Create products in that category with multiple delivery options
product1 = create_test_product(
session, title="Product 1", price=30.0, category_id=category.id
)
product2 = create_test_product(
session, title="Product 2", price=40.0, category_id=category.id
)

delivery_options = create_standard_delivery_options(session)
product1.delivery_options = delivery_options
product2.delivery_options = delivery_options
session.add_all([product1, product2])
session.commit()

# Fetch products with fastest delivery sort and category filter
response = client.get(
f"/products?sort=delivery_fastest&categoryId={category.id}"
)
assert response.status_code == 200

products = response.json()

# Count occurrences of each product
product1_count = sum(1 for p in products if p["id"] == product1.id)
product2_count = sum(1 for p in products if p["id"] == product2.id)

# Each product should appear exactly once
assert product1_count == 1, f"Product 1 appeared {product1_count} times"
assert product2_count == 1, f"Product 2 appeared {product2_count} times"
Loading