diff --git a/trade-hub/backend/app/inventory.py b/trade-hub/backend/app/inventory.py index e03716e..863ccd2 100644 --- a/trade-hub/backend/app/inventory.py +++ b/trade-hub/backend/app/inventory.py @@ -75,10 +75,35 @@ def _find(db: DbSession, user: User, class_name: str) -> InventoryEntry | None: ).scalar_one_or_none() +def _sort_key_name(item): + return (item["name"].lower(), item["class_name"]) + + +def _sort_key_category(item): + return ((item["category"] or "").lower(), item["name"].lower(), item["class_name"]) + + +def _sort_key_quantity(item): + return (-item["quantity"], item["name"].lower(), item["class_name"]) + + +def _sort_key_updated(item): + return (-item["updated_at"].timestamp(), item["name"].lower()) + + +_SORT_KEYS = { + "name": _sort_key_name, + "category": _sort_key_category, + "quantity": _sort_key_quantity, + "updated": _sort_key_updated, +} + + @router.get("", response_model=InventoryPage) def list_inventory( category: str | None = None, search: str | None = None, + sort: str = Query(default="name", pattern="^(name|category|quantity|updated)$"), page: int = Query(default=1, ge=1), page_size: int = Query(default=25, ge=1), user: User = Depends(get_current_user), @@ -102,7 +127,7 @@ def list_inventory( if needle in i["name"].lower() or needle in i["class_name"].lower() ] - items.sort(key=lambda i: (i["name"].lower(), i["class_name"])) + items.sort(key=_SORT_KEYS[sort]) total = len(items) total_pages = max(1, math.ceil(total / page_size)) start = (page - 1) * page_size diff --git a/trade-hub/backend/tests/test_inventory.py b/trade-hub/backend/tests/test_inventory.py index 56cfcc6..d8552c2 100644 --- a/trade-hub/backend/tests/test_inventory.py +++ b/trade-hub/backend/tests/test_inventory.py @@ -189,3 +189,27 @@ def test_catalog_is_not_mutated_by_inventory_operations(): carrack = client.get(f"/api/v1/items/{CARROCK}").json() assert "quantity" not in carrack assert "notes" not in carrack + + +def test_sort_by_category_groups_categories(): + client = authed_client() + add(client, CARROCK) + add(client, RIFLE) + items = client.get("/api/v1/me/inventory?sort=category").json()["items"] + assert [i["class_name"] for i in items] == [RIFLE, CARROCK] + + +def test_sort_by_quantity_is_descending(): + client = authed_client() + add(client, CARROCK, quantity=5) + add(client, RIFLE, quantity=1) + items = client.get("/api/v1/me/inventory?sort=quantity").json()["items"] + assert [i["class_name"] for i in items] == [CARROCK, RIFLE] + + +def test_sort_defaults_to_name_and_rejects_unknown_values(): + client = authed_client() + add(client, CARROCK) + listing = client.get("/api/v1/me/inventory").json() + assert [i["class_name"] for i in listing["items"]] == [CARROCK] + assert client.get("/api/v1/me/inventory?sort=bogus").status_code == 422