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
144 changes: 144 additions & 0 deletions python/cuopt_server/cuopt_server/tests/test_set_fleet_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from cuopt_server.tests.utils.utils import cuoptproc # noqa
from cuopt_server.tests.utils.utils import RequestClient
from cuopt_server.utils.routing.validation_fleet_data import validate_fleet_data

client = RequestClient()

Expand Down Expand Up @@ -62,6 +63,117 @@
# FLEET DATA TESTING


# Test validate_fleet_data rejects duplicate vehicle_ids (no server required)
def test_validate_fleet_data_duplicate_vehicle_ids():
vehicle_locations = [[0, 0], [0, 0], [0, 0]]
vehicle_ids_dup = ["Truck 1", "Truck 1", "Truck 1"]

is_valid, msg = validate_fleet_data(
vehicle_ids=vehicle_ids_dup,
vehicle_locations=vehicle_locations,
capacities=None,
vehicle_time_windows=None,
vehicle_breaks=None,
vehicle_break_time_windows=None,
vehicle_break_durations=None,
vehicle_break_locations=None,
vehicle_types=None,
vehicle_types_dict={},
vehicle_order_match=None,
skip_first_trips=None,
drop_return_trips=None,
min_vehicles=None,
vehicle_max_costs=None,
vehicle_max_times=None,
vehicle_fixed_costs=None,
)
assert is_valid is False
assert "unique" in msg.lower() and "duplicate" in msg.lower()


# Test validate_fleet_data accepts unique vehicle_ids (no server required)
def test_validate_fleet_data_unique_vehicle_ids():
vehicle_locations = [[0, 0], [0, 0]]
vehicle_ids_unique = ["Truck 1", "Truck 2"]

is_valid, msg = validate_fleet_data(
vehicle_ids=vehicle_ids_unique,
vehicle_locations=vehicle_locations,
capacities=None,
vehicle_time_windows=None,
vehicle_breaks=None,
vehicle_break_time_windows=None,
vehicle_break_durations=None,
vehicle_break_locations=None,
vehicle_types=None,
vehicle_types_dict={},
vehicle_order_match=None,
skip_first_trips=None,
drop_return_trips=None,
min_vehicles=None,
vehicle_max_costs=None,
vehicle_max_times=None,
vehicle_fixed_costs=None,
)
assert is_valid is True
assert msg == "Valid Fleet Data"


# Test validate_fleet_data with vehicle_ids=None passes (no server required)
def test_validate_fleet_data_vehicle_ids_none():
vehicle_locations = [[0, 0], [0, 0]]

is_valid, msg = validate_fleet_data(
vehicle_ids=None,
vehicle_locations=vehicle_locations,
capacities=None,
vehicle_time_windows=None,
vehicle_breaks=None,
vehicle_break_time_windows=None,
vehicle_break_durations=None,
vehicle_break_locations=None,
vehicle_types=None,
vehicle_types_dict={},
vehicle_order_match=None,
skip_first_trips=None,
drop_return_trips=None,
min_vehicles=None,
vehicle_max_costs=None,
vehicle_max_times=None,
vehicle_fixed_costs=None,
)
assert is_valid is True
assert msg == "Valid Fleet Data"


# Test validate_fleet_data with single vehicle (no server required)
def test_validate_fleet_data_single_vehicle():
vehicle_locations = [[0, 0]]
vehicle_ids_single = ["Truck 1"]

is_valid, msg = validate_fleet_data(
vehicle_ids=vehicle_ids_single,
vehicle_locations=vehicle_locations,
capacities=None,
vehicle_time_windows=None,
vehicle_breaks=None,
vehicle_break_time_windows=None,
vehicle_break_durations=None,
vehicle_break_locations=None,
vehicle_types=None,
vehicle_types_dict={},
vehicle_order_match=None,
skip_first_trips=None,
drop_return_trips=None,
min_vehicles=None,
vehicle_max_costs=None,
vehicle_max_times=None,
vehicle_fixed_costs=None,
)
assert is_valid is True
assert msg == "Valid Fleet Data"


# Test validation error when multiple cost matrices set without vehicle types
def test_invalid_vehicle_types(cuoptproc): # noqa
matrix_data = {
Expand Down Expand Up @@ -101,6 +213,38 @@ def test_valid_full_set_fleet_data(cuoptproc): # noqa
assert response_set.status_code == 200


# Testing duplicate vehicle_ids rejected (issue #903)
def test_duplicate_vehicle_ids_set_fleet_data(cuoptproc): # noqa
test_data = copy.deepcopy(valid_data)
test_data["fleet_data"]["vehicle_ids"] = [
"veh-1",
"veh-2",
"veh-1",
"veh-4",
]

response_set = client.post("/cuopt/request", json=test_data)
assert response_set.status_code == 400
assert response_set.json() == {
"error": "vehicle_ids must be unique; duplicates are not allowed",
"error_result": True,
}


# Testing valid with unique vehicle_ids
def test_valid_unique_vehicle_ids_set_fleet_data(cuoptproc): # noqa
test_data = copy.deepcopy(valid_data)
test_data["fleet_data"]["vehicle_ids"] = [
"veh-1",
"veh-2",
"veh-3",
"veh-4",
]

response_set = client.post("/cuopt/request", json=test_data)
assert response_set.status_code == 200


# Testing valid with minimal required parameters
def test_valid_minimal_set_fleet_data(cuoptproc): # noqa
test_data = copy.deepcopy(valid_data)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,10 @@ class FleetData(StrictModel):
vehicle_ids: Optional[List[str]] = Field(
default=None,
examples=[["veh-1", "veh-2"]],
description=("List of the vehicle ids or names provided as a string."),
description=(
"List of the vehicle ids or names provided as a string. "
"Must be unique; duplicates are not allowed."
),
)
capacities: Optional[List[List[int]]] = Field(
default=None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ def validate_fleet_data(

if vehicle_ids is not None:
fleet_length_check_array.append(len(vehicle_ids))
if len(vehicle_ids) != len(set(vehicle_ids)):
return (
False,
"vehicle_ids must be unique; duplicates are not allowed",
)
Comment on lines +88 to +92
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add a deprecation phase before hard-rejecting duplicate vehicle_ids.

Line 88 introduces an immediate server/API behavior break. Please gate this with a deprecation warning window first, then enforce rejection in a subsequent version.

As per coding guidelines, "maintain backward compatibility in Python and server APIs with deprecation warnings".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/cuopt_server/cuopt_server/utils/routing/validation_fleet_data.py`
around lines 88 - 92, The current hard-reject that returns False when
len(vehicle_ids) != len(set(vehicle_ids)) will break clients; replace it with a
deprecation-warning phase: instead of returning immediately in the
duplicate-check block that tests len(vehicle_ids) != len(set(vehicle_ids)), emit
a DeprecationWarning (via warnings.warn(..., DeprecationWarning) and/or a
structured server log) and allow the request to continue for now; add a
configurable flag or version-gated enforcement (e.g., check an
environment/config flag like ENFORCE_UNIQUE_VEHICLE_IDS or compare against a
target version) that, when enabled in a future release, flips the behavior to
the current hard-reject, and add/update unit tests to cover both warning-mode
and enforcement-mode.


if capacities is not None:
fleet_length_check_array.append(len(capacities[0]))
Expand Down
Loading