# tests/integration/api/v1/admin/test_inventory.py """ Integration tests for admin inventory management endpoints. Tests the /api/v1/admin/inventory/* endpoints. All endpoints require admin JWT authentication. """ import pytest @pytest.mark.integration @pytest.mark.api @pytest.mark.admin @pytest.mark.inventory class TestAdminInventoryAPI: """Tests for admin inventory management endpoints.""" # ======================================================================== # List & Statistics Tests # ======================================================================== def test_get_all_inventory_admin( self, client, admin_headers, test_inventory, test_vendor ): """Test admin getting all inventory.""" response = client.get("/api/v1/admin/inventory", headers=admin_headers) assert response.status_code == 200 data = response.json() assert "inventories" in data assert "total" in data assert "skip" in data assert "limit" in data assert data["total"] >= 1 assert len(data["inventories"]) >= 1 # Check that test inventory is in the response inventory_ids = [i["id"] for i in data["inventories"]] assert test_inventory.id in inventory_ids def test_get_all_inventory_non_admin(self, client, auth_headers): """Test non-admin trying to access inventory endpoint.""" response = client.get("/api/v1/admin/inventory", headers=auth_headers) assert response.status_code == 403 data = response.json() assert data["error_code"] == "ADMIN_REQUIRED" def test_get_all_inventory_with_vendor_filter( self, client, admin_headers, test_inventory, test_vendor ): """Test admin filtering inventory by vendor.""" response = client.get( "/api/v1/admin/inventory", params={"vendor_id": test_vendor.id}, headers=admin_headers, ) assert response.status_code == 200 data = response.json() assert data["total"] >= 1 assert data["vendor_filter"] == test_vendor.id # All inventory should be from the filtered vendor for item in data["inventories"]: assert item["vendor_id"] == test_vendor.id def test_get_all_inventory_with_location_filter( self, client, admin_headers, test_inventory ): """Test admin filtering inventory by location.""" location = test_inventory.location[:5] # Partial match response = client.get( "/api/v1/admin/inventory", params={"location": location}, headers=admin_headers, ) assert response.status_code == 200 data = response.json() # All items should have matching location for item in data["inventories"]: assert location.upper() in item["location"].upper() def test_get_all_inventory_with_low_stock_filter( self, client, admin_headers, test_inventory, db ): """Test admin filtering inventory by low stock threshold.""" # Set test inventory to low stock test_inventory.quantity = 5 db.commit() response = client.get( "/api/v1/admin/inventory", params={"low_stock": 10}, headers=admin_headers, ) assert response.status_code == 200 data = response.json() # All items should have quantity <= threshold for item in data["inventories"]: assert item["quantity"] <= 10 def test_get_all_inventory_pagination( self, client, admin_headers, test_inventory ): """Test admin inventory pagination.""" response = client.get( "/api/v1/admin/inventory", params={"skip": 0, "limit": 10}, headers=admin_headers, ) assert response.status_code == 200 data = response.json() assert data["skip"] == 0 assert data["limit"] == 10 def test_get_inventory_stats_admin( self, client, admin_headers, test_inventory ): """Test admin getting inventory statistics.""" response = client.get( "/api/v1/admin/inventory/stats", headers=admin_headers ) assert response.status_code == 200 data = response.json() assert "total_entries" in data assert "total_quantity" in data assert "total_reserved" in data assert "total_available" in data assert "low_stock_count" in data assert "vendors_with_inventory" in data assert "unique_locations" in data assert data["total_entries"] >= 1 def test_get_inventory_stats_non_admin(self, client, auth_headers): """Test non-admin trying to access inventory stats.""" response = client.get( "/api/v1/admin/inventory/stats", headers=auth_headers ) assert response.status_code == 403 def test_get_low_stock_items_admin( self, client, admin_headers, test_inventory, db ): """Test admin getting low stock items.""" # Set test inventory to low stock test_inventory.quantity = 3 db.commit() response = client.get( "/api/v1/admin/inventory/low-stock", params={"threshold": 10}, headers=admin_headers, ) assert response.status_code == 200 data = response.json() assert isinstance(data, list) # All items should have quantity <= threshold for item in data: assert item["quantity"] <= 10 def test_get_vendors_with_inventory_admin( self, client, admin_headers, test_inventory, test_vendor ): """Test admin getting vendors with inventory.""" response = client.get( "/api/v1/admin/inventory/vendors", headers=admin_headers ) assert response.status_code == 200 data = response.json() assert "vendors" in data assert isinstance(data["vendors"], list) assert len(data["vendors"]) >= 1 # Check that test_vendor is in the list vendor_ids = [v["id"] for v in data["vendors"]] assert test_vendor.id in vendor_ids def test_get_inventory_locations_admin( self, client, admin_headers, test_inventory ): """Test admin getting inventory locations.""" response = client.get( "/api/v1/admin/inventory/locations", headers=admin_headers ) assert response.status_code == 200 data = response.json() assert "locations" in data assert isinstance(data["locations"], list) assert len(data["locations"]) >= 1 assert test_inventory.location in data["locations"] # ======================================================================== # Vendor-Specific Tests # ======================================================================== def test_get_vendor_inventory_admin( self, client, admin_headers, test_inventory, test_vendor ): """Test admin getting vendor-specific inventory.""" response = client.get( f"/api/v1/admin/inventory/vendors/{test_vendor.id}", headers=admin_headers, ) assert response.status_code == 200 data = response.json() assert "inventories" in data assert "total" in data assert data["vendor_filter"] == test_vendor.id assert data["total"] >= 1 # All inventory should be from this vendor for item in data["inventories"]: assert item["vendor_id"] == test_vendor.id def test_get_vendor_inventory_not_found(self, client, admin_headers): """Test admin getting inventory for non-existent vendor.""" response = client.get( "/api/v1/admin/inventory/vendors/99999", headers=admin_headers, ) assert response.status_code == 404 data = response.json() assert data["error_code"] == "VENDOR_NOT_FOUND" def test_get_product_inventory_admin( self, client, admin_headers, test_inventory, test_product ): """Test admin getting product inventory summary.""" response = client.get( f"/api/v1/admin/inventory/products/{test_product.id}", headers=admin_headers, ) assert response.status_code == 200 data = response.json() assert data["product_id"] == test_product.id assert "total_quantity" in data assert "total_reserved" in data assert "total_available" in data assert "locations" in data def test_get_product_inventory_not_found(self, client, admin_headers): """Test admin getting inventory for non-existent product.""" response = client.get( "/api/v1/admin/inventory/products/99999", headers=admin_headers, ) assert response.status_code == 404 data = response.json() assert data["error_code"] == "PRODUCT_NOT_FOUND" # ======================================================================== # Inventory Modification Tests # ======================================================================== def test_set_inventory_admin( self, client, admin_headers, test_product, test_vendor ): """Test admin setting inventory for a product.""" inventory_data = { "vendor_id": test_vendor.id, "product_id": test_product.id, "location": "ADMIN_TEST_WAREHOUSE", "quantity": 150, } response = client.post( "/api/v1/admin/inventory/set", headers=admin_headers, json=inventory_data, ) assert response.status_code == 200, f"Failed: {response.json()}" data = response.json() assert data["product_id"] == test_product.id assert data["vendor_id"] == test_vendor.id assert data["quantity"] == 150 assert data["location"] == "ADMIN_TEST_WAREHOUSE" def test_set_inventory_non_admin( self, client, auth_headers, test_product, test_vendor ): """Test non-admin trying to set inventory.""" inventory_data = { "vendor_id": test_vendor.id, "product_id": test_product.id, "location": "WAREHOUSE_A", "quantity": 100, } response = client.post( "/api/v1/admin/inventory/set", headers=auth_headers, json=inventory_data, ) assert response.status_code == 403 def test_set_inventory_vendor_not_found( self, client, admin_headers, test_product ): """Test admin setting inventory for non-existent vendor.""" inventory_data = { "vendor_id": 99999, "product_id": test_product.id, "location": "WAREHOUSE_A", "quantity": 100, } response = client.post( "/api/v1/admin/inventory/set", headers=admin_headers, json=inventory_data, ) assert response.status_code == 404 data = response.json() assert data["error_code"] == "VENDOR_NOT_FOUND" def test_adjust_inventory_add_admin( self, client, admin_headers, test_inventory, test_vendor, test_product ): """Test admin adding to inventory.""" original_qty = test_inventory.quantity adjust_data = { "vendor_id": test_vendor.id, "product_id": test_product.id, "location": test_inventory.location, "quantity": 25, "reason": "Admin restocking", } response = client.post( "/api/v1/admin/inventory/adjust", headers=admin_headers, json=adjust_data, ) assert response.status_code == 200 data = response.json() assert data["quantity"] == original_qty + 25 def test_adjust_inventory_remove_admin( self, client, admin_headers, test_inventory, test_vendor, test_product, db ): """Test admin removing from inventory.""" # Ensure we have enough inventory test_inventory.quantity = 100 db.commit() adjust_data = { "vendor_id": test_vendor.id, "product_id": test_product.id, "location": test_inventory.location, "quantity": -10, "reason": "Admin adjustment", } response = client.post( "/api/v1/admin/inventory/adjust", headers=admin_headers, json=adjust_data, ) assert response.status_code == 200 data = response.json() assert data["quantity"] == 90 def test_adjust_inventory_insufficient( self, client, admin_headers, test_inventory, test_vendor, test_product, db ): """Test admin trying to remove more than available.""" # Set low inventory test_inventory.quantity = 5 db.commit() adjust_data = { "vendor_id": test_vendor.id, "product_id": test_product.id, "location": test_inventory.location, "quantity": -100, } response = client.post( "/api/v1/admin/inventory/adjust", headers=admin_headers, json=adjust_data, ) # Service wraps InsufficientInventoryException in ValidationException assert response.status_code == 422 data = response.json() # Error is wrapped - check message contains relevant info assert "error_code" in data assert "insufficient" in data.get("message", "").lower() or data["error_code"] in [ "INSUFFICIENT_INVENTORY", "VALIDATION_ERROR", ] def test_update_inventory_admin( self, client, admin_headers, test_inventory ): """Test admin updating inventory entry.""" update_data = { "quantity": 200, } response = client.put( f"/api/v1/admin/inventory/{test_inventory.id}", headers=admin_headers, json=update_data, ) assert response.status_code == 200 data = response.json() assert data["quantity"] == 200 def test_update_inventory_not_found(self, client, admin_headers): """Test admin updating non-existent inventory.""" update_data = {"quantity": 100} response = client.put( "/api/v1/admin/inventory/99999", headers=admin_headers, json=update_data, ) assert response.status_code == 404 data = response.json() assert data["error_code"] == "INVENTORY_NOT_FOUND" def test_delete_inventory_admin( self, client, admin_headers, test_product, test_vendor, db ): """Test admin deleting inventory entry.""" # Create a new inventory entry to delete from models.database.inventory import Inventory new_inventory = Inventory( product_id=test_product.id, vendor_id=test_vendor.id, warehouse="strassen", bin_location="DEL-01-01", location="TO_DELETE_WAREHOUSE", quantity=50, ) db.add(new_inventory) db.commit() db.refresh(new_inventory) inventory_id = new_inventory.id response = client.delete( f"/api/v1/admin/inventory/{inventory_id}", headers=admin_headers, ) assert response.status_code == 200 data = response.json() assert "message" in data assert "deleted" in data["message"].lower() # Verify it's deleted deleted = db.query(Inventory).filter(Inventory.id == inventory_id).first() assert deleted is None def test_delete_inventory_not_found(self, client, admin_headers): """Test admin deleting non-existent inventory.""" response = client.delete( "/api/v1/admin/inventory/99999", headers=admin_headers, ) assert response.status_code == 404 data = response.json() assert data["error_code"] == "INVENTORY_NOT_FOUND" def test_delete_inventory_non_admin( self, client, auth_headers, test_inventory ): """Test non-admin trying to delete inventory.""" response = client.delete( f"/api/v1/admin/inventory/{test_inventory.id}", headers=auth_headers, ) assert response.status_code == 403