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
16 changes: 15 additions & 1 deletion src/runpod_flash/core/resources/network_volume.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
from typing import Optional, Dict, Any

from pydantic import (
ConfigDict,
Field,
field_validator,
field_serializer,
model_validator,
)
Expand Down Expand Up @@ -80,13 +82,25 @@ class NetworkVolume(DeployableResource):
"name",
}

model_config = ConfigDict(extra="forbid")

# public alias -- users pass datacenter=, which syncs to dataCenterId for the API
datacenter: Optional[DataCenter] = Field(default=None, exclude=True)
dataCenterId: DataCenter = Field(default=DataCenter.EU_RO_1)

id: Optional[str] = Field(default=None)
name: Optional[str] = None
size: Optional[int] = Field(default=100, gt=0) # Size in GB
size: Optional[int] = Field(default=20, ge=10, le=4096) # Size in GB

@field_validator("name")
@classmethod
def validate_name_not_empty(cls, value: Optional[str]) -> Optional[str]:
"""Reject empty/whitespace-only volume names."""
if value is None:
return value
if not value.strip():
raise ValueError("name must not be empty or whitespace-only")
return value

@model_validator(mode="before")
@classmethod
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/cli/commands/build_utils/test_manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -617,7 +617,7 @@ def test_extract_deployment_config_network_volume_minimal():

assert config["networkVolume"]["name"] == "minimal-vol"
# Default size and dataCenterId should still be present
assert config["networkVolume"]["size"] == 100
assert config["networkVolume"]["size"] == 20
assert config["networkVolume"]["dataCenterId"] == "EU-RO-1"


Expand Down
31 changes: 31 additions & 0 deletions tests/unit/resources/test_network_volume.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from unittest.mock import AsyncMock, patch, MagicMock
import pytest
from pydantic import ValidationError

from runpod_flash.core.resources.network_volume import NetworkVolume, DataCenter

Expand Down Expand Up @@ -166,6 +167,36 @@ def test_resource_id_based_on_name_and_datacenter(self):
assert volume1.resource_id == volume2.resource_id # Same name + datacenter
assert volume1.resource_id != volume3.resource_id # Different name

def test_empty_name_rejected(self):
"""Reject empty names at model construction time."""
with pytest.raises(ValidationError, match="name must not be empty"):
NetworkVolume(name="")

def test_whitespace_name_rejected(self):
"""Reject whitespace-only names at model construction time."""
with pytest.raises(ValidationError, match="name must not be empty"):
NetworkVolume(name=" ")

def test_max_size_is_allowed(self):
"""Max supported size (4TB) is accepted."""
volume = NetworkVolume(name="large-vol", size=4096)
assert volume.size == 4096

def test_size_above_max_rejected(self):
"""Size above 4TB should fail validation."""
with pytest.raises(ValidationError, match="less than or equal to 4096"):
NetworkVolume(name="too-large", size=4097)

def test_size_below_min_rejected(self):
"""Size below 10GB should fail validation."""
with pytest.raises(ValidationError, match="greater than or equal to 10"):
NetworkVolume(name="too-small", size=5)

def test_unknown_field_rejected(self):
"""Unknown fields should raise validation errors."""
with pytest.raises(ValidationError, match="Extra inputs are not permitted"):
NetworkVolume(name="data", sizee=500)

@pytest.mark.asyncio
async def test_deploy_uses_resource_manager_to_register(self, sample_volume_data):
"""deploy() should go through the ResourceManager for persistence."""
Expand Down
12 changes: 6 additions & 6 deletions tests/unit/test_p2_gaps.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,25 +106,25 @@ async def my_func(x):


# ---------------------------------------------------------------------------
# VOL-005: Volume size=0 raises validation error
# VOL-005: Volume size below minimum raises validation error
# VOL-006: Volume name empty
# VOL-007: Undeploy raises NotImplementedError
# ---------------------------------------------------------------------------
class TestNetworkVolumeValidation:
"""Network volume validation edge cases."""

def test_volume_size_zero_raises(self):
"""VOL-005: size=0 raises validation error (gt=0 constraint)."""
"""VOL-005: size=0 raises validation error (min 10GB)."""
from runpod_flash.core.resources.network_volume import NetworkVolume

with pytest.raises(ValidationError, match="greater than 0"):
with pytest.raises(ValidationError, match="greater than or equal to 10"):
NetworkVolume(name="test-vol", size=0)

def test_volume_size_negative_raises(self):
"""VOL-005: Negative size also rejected."""
from runpod_flash.core.resources.network_volume import NetworkVolume

with pytest.raises(ValidationError, match="greater than 0"):
with pytest.raises(ValidationError, match="greater than or equal to 10"):
NetworkVolume(name="test-vol", size=-10)

@pytest.mark.asyncio
Expand All @@ -138,11 +138,11 @@ async def test_volume_undeploy_raises_not_implemented(self):
await vol.undeploy()

def test_volume_default_size(self):
"""Default volume size is 100GB."""
"""Default volume size is 20GB."""
from runpod_flash.core.resources.network_volume import NetworkVolume

vol = NetworkVolume(name="test-vol")
assert vol.size == 100
assert vol.size == 20


# ---------------------------------------------------------------------------
Expand Down
6 changes: 3 additions & 3 deletions tests/unit/test_p2_remaining_gaps.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
CLI-RUN-018 – watchfiles fallback stub raises ModuleNotFoundError
REM-CLS-013 – extract_class_code_simple fallback when inspect.getsource fails
RES-LS-008 – ServerlessResource.env default populated from .env file
VOL-006 – NetworkVolume with empty name still constructs (no validator guards it)
VOL-006 – NetworkVolume with empty/whitespace name is rejected by validator
SCAN-016 – RuntimeScanner handles @remote on nested class (class in function)
SCAN-017 – RuntimeScanner handles conditional @remote gracefully
STUB-STACK-004 – detect_remote_dependencies terminates on circular dependency graph
Expand Down Expand Up @@ -217,7 +217,7 @@ def test_network_volume_with_empty_name_and_no_id_raises(self):
from runpod_flash.core.resources.network_volume import NetworkVolume

with pytest.raises(
ValidationError, match="either 'name' or 'id' must be provided"
ValidationError, match="name must not be empty or whitespace-only"
):
NetworkVolume(name="")

Expand All @@ -238,7 +238,7 @@ def test_network_volume_non_empty_name_works(self):
assert vol.size == 50

def test_network_volume_size_zero_raises(self):
"""VOL-006: size=0 violates the gt=0 constraint and raises ValidationError."""
"""VOL-006: size=0 violates the min 10GB constraint and raises ValidationError."""
from pydantic import ValidationError

from runpod_flash.core.resources.network_volume import NetworkVolume
Expand Down
Loading