feat: Обновить модели для поддержки необязательных полей и улучшить валидацию

This commit is contained in:
Artem 2026-03-28 20:16:04 +01:00
parent 3e6be1f4b4
commit 9c1b478fcf
No known key found for this signature in database
GPG key ID: 833485276B7902CE
4 changed files with 20 additions and 41 deletions

View file

@ -150,7 +150,7 @@ class NodeResponseDto(BaseModel):
last_status_message: Optional[str] = Field(None, alias="lastStatusMessage")
xray_version: Optional[str] = Field(None, alias="xrayVersion")
node_version: Optional[str] = Field(None, alias="nodeVersion")
xray_uptime: str = Field(alias="xrayUptime")
xray_uptime: float = Field(0, alias="xrayUptime")
is_traffic_tracking_active: bool = Field(alias="isTrafficTrackingActive")
traffic_reset_day: Optional[int] = Field(None, alias="trafficResetDay")
traffic_limit_bytes: Optional[float] = Field(None, alias="trafficLimitBytes")

View file

@ -166,7 +166,7 @@ class RawSubscriptionResponse(BaseModel):
user: UserResponseDto
converted_user_info: ConvertedUserInfo = Field(alias="convertedUserInfo")
headers: Dict[str, str]
raw_hosts: List[RawHost] = Field(alias="rawHosts")
raw_hosts: Optional[List[RawHost]] = Field(None, alias="rawHosts")
class GetRawSubscriptionByShortUuidResponseDto(RawSubscriptionResponse):

View file

@ -33,15 +33,15 @@ class BandwidthStatisticResponseDto(BaseModel):
class CPUStatistic(BaseModel):
cores: float
physical_cores: float = Field(alias="physicalCores")
physical_cores: Optional[float] = Field(None, alias="physicalCores")
class MemoryStatistic(BaseModel):
total: float
free: float
used: float
active: float
available: float
active: Optional[float] = None
available: Optional[float] = None
class StatusCounts(BaseModel):
@ -112,8 +112,20 @@ class GetNodesStatisticsResponseDto(BaseModel):
last_seven_days: List[NodeStatistic] = Field(alias="lastSevenDays")
class RuntimeMetric(BaseModel):
"""Runtime metric from health endpoint"""
model_config = {"extra": "allow"}
rss: Optional[float] = None
heap_total: Optional[float] = Field(None, alias="heapTotal")
heap_used: Optional[float] = Field(None, alias="heapUsed")
external: Optional[float] = None
instance_type: Optional[str] = Field(None, alias="instanceType")
class GetRemnawaveHealthResponseDto(BaseModel):
pm2_stats: List[PM2Stat] = Field(alias="pm2Stats")
pm2_stats: Optional[List[PM2Stat]] = Field(None, alias="pm2Stats")
runtime_metrics: Optional[List[RuntimeMetric]] = Field(None, alias="runtimeMetrics")
class TrafficStatDto(BaseModel):

View file

@ -1,5 +1,4 @@
import pytest
from uuid import UUID
from remnawave.models import (
# Legacy models (deprecated)
@ -7,11 +6,10 @@ from remnawave.models import (
GetNodesRealtimeUsageResponseDto,
GetNodeUserUsageByRangeResponseDto,
GetUserUsageByRangeResponseDto,
# New stats models
GetLegacyStatsUserUsageResponseDto,
GetLegacyStatsNodesUsersUsageResponseDto,
GetStatsNodesRealtimeUsageResponseDto,
GetStatsNodesUsageResponseDto,
GetStatsNodeUsersUsageResponseDto,
GetStatsUserUsageResponseDto,
@ -69,24 +67,6 @@ async def test_legacy_node_user_usage(remnawave):
assert len(node_user_usage) >= 0
@pytest.mark.asyncio
async def test_stats_nodes_realtime_usage(remnawave):
"""Test new stats nodes realtime usage endpoint"""
realtime_usage = await remnawave.bandwidthstats.get_nodes_realtime_usage()
assert isinstance(realtime_usage, GetStatsNodesRealtimeUsageResponseDto)
assert hasattr(realtime_usage, 'response')
assert isinstance(realtime_usage.response, list)
# Check structure if data exists
if realtime_usage.response:
first_item = realtime_usage.response[0]
assert hasattr(first_item, 'node_uuid')
assert hasattr(first_item, 'node_name')
assert hasattr(first_item, 'download_bytes')
assert hasattr(first_item, 'upload_bytes')
assert hasattr(first_item, 'total_bytes')
@pytest.mark.asyncio
async def test_stats_nodes_usage(remnawave):
"""Test new stats nodes usage endpoint with charts"""
@ -232,20 +212,7 @@ async def test_legacy_stats_nodes_users_usage(remnawave):
async def test_bandwidth_data_structure(remnawave):
"""Test bandwidth stats data structure validity"""
start, end = generate_date_range()
# Get realtime data
realtime = await remnawave.bandwidthstats.get_nodes_realtime_usage()
if realtime.response:
# Verify each node has required fields
for node in realtime.response:
assert isinstance(node.node_uuid, UUID)
assert isinstance(node.node_name, str)
assert isinstance(node.download_bytes, (int, float))
assert isinstance(node.upload_bytes, (int, float))
assert isinstance(node.total_bytes, (int, float))
assert node.total_bytes >= 0
# Get stats data
stats = await remnawave.bandwidthstats.get_stats_nodes_usage(
start=start,