mirror of
https://github.com/remnawave/python-sdk.git
synced 2026-05-13 12:16:42 +00:00
Обновить версию SDK до 2.1.19; добавить новые DTO для истории биллинга и упрощенных моделей провайдеров; переименовать и изменить существующие DTO для соответствия новому API
This commit is contained in:
parent
936c006b9b
commit
0a627eed81
7 changed files with 332 additions and 112 deletions
|
|
@ -63,6 +63,7 @@ pip install git+https://github.com/remnawave/python-sdk.git@development
|
|||
|
||||
| Contract Version | Remnawave Panel Version |
|
||||
| ---------------- | ----------------------- |
|
||||
| 2.1.19 | >=2.1.19 |
|
||||
| 2.1.18 | >=2.1.18 |
|
||||
| 2.1.17 | >=2.1.16, <=2.1.17 |
|
||||
| 2.1.16 | >=2.1.16 |
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
[project]
|
||||
name = "remnawave"
|
||||
version = "2.1.18"
|
||||
description = "A Python SDK for interacting with the Remnawave API v2.1.18."
|
||||
version = "2.1.19"
|
||||
description = "A Python SDK for interacting with the Remnawave API v2.1.19."
|
||||
authors = [
|
||||
{name = "Artem",email = "dev@forestsnet.com"}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,20 +1,21 @@
|
|||
from typing import Annotated
|
||||
|
||||
from rapid_api_client import Path, Query
|
||||
from rapid_api_client import Path
|
||||
from rapid_api_client.annotations import PydanticBody
|
||||
|
||||
from remnawave.models import (
|
||||
CreateInfraBillingHistoryRecordRequestDto,
|
||||
CreateInfraBillingHistoryRecordResponseDto,
|
||||
CreateInfraBillingNodeRequestDto,
|
||||
CreateInfraBillingNodeResponseDto,
|
||||
CreateInfraProviderRequestDto,
|
||||
CreateInfraProviderResponseDto,
|
||||
DeleteInfraBillingNodeResponseDto,
|
||||
DeleteInfraProviderResponseDto,
|
||||
GetAllInfraBillingHistoryResponseDto,
|
||||
GetAllInfraBillingNodesResponseDto,
|
||||
GetAllInfraProvidersResponseDto,
|
||||
GetInfraBillingHistoryByUuidResponseDto,
|
||||
GetInfraBillingNodeByUuidResponseDto,
|
||||
DeleteInfraBillingHistoryRecordByUuidResponseDto,
|
||||
DeleteInfraBillingNodeByUuidResponseDto,
|
||||
DeleteInfraProviderByUuidResponseDto,
|
||||
GetInfraBillingHistoryRecordsResponseDto,
|
||||
GetInfraBillingNodesResponseDto,
|
||||
GetInfraProvidersResponseDto,
|
||||
GetInfraProviderByUuidResponseDto,
|
||||
UpdateInfraBillingNodeRequestDto,
|
||||
UpdateInfraBillingNodeResponseDto,
|
||||
|
|
@ -25,8 +26,8 @@ from remnawave.rapid import BaseController, delete, get, patch, post
|
|||
|
||||
|
||||
class InfraBillingController(BaseController):
|
||||
@get("/infra-billing/providers", response_class=GetAllInfraProvidersResponseDto)
|
||||
async def get_all_infra_providers(self) -> GetAllInfraProvidersResponseDto:
|
||||
@get("/infra-billing/providers", response_class=GetInfraProvidersResponseDto)
|
||||
async def get_infra_providers(self) -> GetInfraProvidersResponseDto:
|
||||
"""Get all infra providers"""
|
||||
...
|
||||
|
||||
|
|
@ -54,38 +55,38 @@ class InfraBillingController(BaseController):
|
|||
"""Get infra provider by uuid"""
|
||||
...
|
||||
|
||||
@delete("/infra-billing/providers/{uuid}", response_class=DeleteInfraProviderResponseDto)
|
||||
@delete("/infra-billing/providers/{uuid}", response_class=DeleteInfraProviderByUuidResponseDto)
|
||||
async def delete_infra_provider_by_uuid(
|
||||
self,
|
||||
uuid: Annotated[str, Path(description="UUID of the infra provider")],
|
||||
) -> DeleteInfraProviderResponseDto:
|
||||
"""Delete infra provider"""
|
||||
) -> DeleteInfraProviderByUuidResponseDto:
|
||||
"""Delete infra provider by uuid"""
|
||||
...
|
||||
|
||||
@get("/infra-billing/history", response_class=GetAllInfraBillingHistoryResponseDto)
|
||||
async def get_all_infra_billing_history(
|
||||
@post("/infra-billing/history", response_class=CreateInfraBillingHistoryRecordResponseDto)
|
||||
async def create_infra_billing_history_record(
|
||||
self,
|
||||
start: Annotated[int, Query(default=0, ge=0, description="Index to start pagination from")],
|
||||
size: Annotated[int, Query(default=25, ge=1, description="Number of entries per page")],
|
||||
) -> GetAllInfraBillingHistoryResponseDto:
|
||||
"""Get all infra billing history"""
|
||||
body: Annotated[CreateInfraBillingHistoryRecordRequestDto, PydanticBody()],
|
||||
) -> CreateInfraBillingHistoryRecordResponseDto:
|
||||
"""Create infra billing history"""
|
||||
...
|
||||
|
||||
@get("/infra-billing/history/{uuid}", response_class=GetInfraBillingHistoryByUuidResponseDto)
|
||||
async def get_infra_billing_history_by_uuid(
|
||||
self,
|
||||
uuid: Annotated[str, Path(description="UUID of the billing history entry")],
|
||||
) -> GetInfraBillingHistoryByUuidResponseDto:
|
||||
"""Get infra billing history by uuid"""
|
||||
@get("/infra-billing/history", response_class=GetInfraBillingHistoryRecordsResponseDto)
|
||||
async def get_infra_billing_history_records(self) -> GetInfraBillingHistoryRecordsResponseDto:
|
||||
"""Get infra billing history"""
|
||||
...
|
||||
|
||||
@get("/infra-billing/nodes", response_class=GetAllInfraBillingNodesResponseDto)
|
||||
async def get_all_infra_billing_nodes(
|
||||
@delete("/infra-billing/history/{uuid}", response_class=DeleteInfraBillingHistoryRecordByUuidResponseDto)
|
||||
async def delete_infra_billing_history_record_by_uuid(
|
||||
self,
|
||||
start: Annotated[int, Query(default=0, ge=0, description="Index to start pagination from")],
|
||||
size: Annotated[int, Query(default=25, ge=1, description="Number of entries per page")],
|
||||
) -> GetAllInfraBillingNodesResponseDto:
|
||||
"""Get all infra billing nodes"""
|
||||
uuid: Annotated[str, Path(description="UUID of the billing history record")],
|
||||
) -> DeleteInfraBillingHistoryRecordByUuidResponseDto:
|
||||
"""Delete infra billing history"""
|
||||
...
|
||||
|
||||
@get("/infra-billing/nodes", response_class=GetInfraBillingNodesResponseDto)
|
||||
async def get_billing_nodes(self) -> GetInfraBillingNodesResponseDto:
|
||||
"""Get infra billing nodes"""
|
||||
...
|
||||
|
||||
@patch("/infra-billing/nodes", response_class=UpdateInfraBillingNodeResponseDto)
|
||||
|
|
@ -93,7 +94,7 @@ class InfraBillingController(BaseController):
|
|||
self,
|
||||
body: Annotated[UpdateInfraBillingNodeRequestDto, PydanticBody()],
|
||||
) -> UpdateInfraBillingNodeResponseDto:
|
||||
"""Update infra billing node"""
|
||||
"""Update infra billing nodes"""
|
||||
...
|
||||
|
||||
@post("/infra-billing/nodes", response_class=CreateInfraBillingNodeResponseDto)
|
||||
|
|
@ -104,18 +105,10 @@ class InfraBillingController(BaseController):
|
|||
"""Create infra billing node"""
|
||||
...
|
||||
|
||||
@get("/infra-billing/nodes/{uuid}", response_class=GetInfraBillingNodeByUuidResponseDto)
|
||||
async def get_infra_billing_node_by_uuid(
|
||||
self,
|
||||
uuid: Annotated[str, Path(description="UUID of the infra billing node")],
|
||||
) -> GetInfraBillingNodeByUuidResponseDto:
|
||||
"""Get infra billing node by uuid"""
|
||||
...
|
||||
|
||||
@delete("/infra-billing/nodes/{uuid}", response_class=DeleteInfraBillingNodeResponseDto)
|
||||
@delete("/infra-billing/nodes/{uuid}", response_class=DeleteInfraBillingNodeByUuidResponseDto)
|
||||
async def delete_infra_billing_node_by_uuid(
|
||||
self,
|
||||
uuid: Annotated[str, Path(description="UUID of the infra billing node")],
|
||||
) -> DeleteInfraBillingNodeResponseDto:
|
||||
) -> DeleteInfraBillingNodeByUuidResponseDto:
|
||||
"""Delete infra billing node"""
|
||||
...
|
||||
...
|
||||
|
|
@ -98,15 +98,18 @@ from .inbounds_bulk_actions import (
|
|||
RemoveInboundFromUsersResponseDto,
|
||||
)
|
||||
from .infra_billing import (
|
||||
CreateInfraBillingHistoryRecordRequestDto,
|
||||
CreateInfraBillingHistoryRecordResponseDto,
|
||||
CreateInfraBillingNodeRequestDto,
|
||||
CreateInfraBillingNodeResponseDto,
|
||||
CreateInfraProviderRequestDto,
|
||||
CreateInfraProviderResponseDto,
|
||||
DeleteInfraBillingNodeResponseDto,
|
||||
DeleteInfraProviderResponseDto,
|
||||
GetAllInfraBillingHistoryResponseDto,
|
||||
GetAllInfraBillingNodesResponseDto,
|
||||
GetAllInfraProvidersResponseDto,
|
||||
DeleteInfraBillingHistoryRecordByUuidResponseDto,
|
||||
DeleteInfraBillingNodeByUuidResponseDto, # ПЕРЕИМЕНОВАНА (было DeleteInfraBillingNodeResponseDto)
|
||||
DeleteInfraProviderByUuidResponseDto, # ПЕРЕИМЕНОВАНА (было DeleteInfraProviderResponseDto)
|
||||
GetInfraBillingHistoryRecordsResponseDto, # ПЕРЕИМЕНОВАНА (было GetAllInfraBillingHistoryResponseDto)
|
||||
GetInfraBillingNodesResponseDto, # ПЕРЕИМЕНОВАНА (было GetAllInfraBillingNodesResponseDto)
|
||||
GetInfraProvidersResponseDto, # ПЕРЕИМЕНОВАНА (было GetAllInfraProvidersResponseDto)
|
||||
GetInfraBillingHistoryByUuidResponseDto,
|
||||
GetInfraBillingNodeByUuidResponseDto,
|
||||
GetInfraProviderByUuidResponseDto,
|
||||
|
|
@ -118,6 +121,11 @@ from .infra_billing import (
|
|||
UpdateInfraBillingNodeResponseDto,
|
||||
UpdateInfraProviderRequestDto,
|
||||
UpdateInfraProviderResponseDto,
|
||||
DeleteInfraBillingNodeResponseDto, # LEGACY
|
||||
DeleteInfraProviderResponseDto, # LEGACY
|
||||
GetAllInfraBillingHistoryResponseDto, # LEGACY
|
||||
GetAllInfraBillingNodesResponseDto, # LEGACY
|
||||
GetAllInfraProvidersResponseDto, # LEGACY
|
||||
)
|
||||
from .internal_squads import (
|
||||
AddUsersToInternalSquadRequestDto,
|
||||
|
|
@ -459,15 +467,18 @@ __all__ = [
|
|||
"UpdateConfigProfileResponseDto",
|
||||
"GetAllConfigProfilesResponsePaginated",
|
||||
# Infra billing models
|
||||
"CreateInfraBillingHistoryRecordRequestDto",
|
||||
"CreateInfraBillingHistoryRecordResponseDto",
|
||||
"CreateInfraBillingNodeRequestDto",
|
||||
"CreateInfraBillingNodeResponseDto",
|
||||
"CreateInfraProviderRequestDto",
|
||||
"CreateInfraProviderResponseDto",
|
||||
"DeleteInfraBillingNodeResponseDto",
|
||||
"DeleteInfraProviderResponseDto",
|
||||
"GetAllInfraBillingHistoryResponseDto",
|
||||
"GetAllInfraBillingNodesResponseDto",
|
||||
"GetAllInfraProvidersResponseDto",
|
||||
"CreateInfraProviderResponseDto",
|
||||
"DeleteInfraBillingHistoryRecordByUuidResponseDto",
|
||||
"DeleteInfraBillingNodeByUuidResponseDto", # ПЕРЕИМЕНОВАНА (было DeleteInfraBillingNodeResponseDto)
|
||||
"DeleteInfraProviderByUuidResponseDto", # ПЕРЕИМЕНОВАНА (было DeleteInfraProviderResponseDto)
|
||||
"GetInfraBillingHistoryRecordsResponseDto", # ПЕРЕИМЕНОВАНА (было GetAllInfraBillingHistoryResponseDto)
|
||||
"GetInfraBillingNodesResponseDto", # ПЕРЕИМЕНОВАНА (было GetAllInfraBillingNodesResponseDto)
|
||||
"GetInfraProvidersResponseDto", # ПЕРЕИМЕНОВАНА (было GetAllInfraProvidersResponseDto)
|
||||
"GetInfraBillingHistoryByUuidResponseDto",
|
||||
"GetInfraBillingNodeByUuidResponseDto",
|
||||
"GetInfraProviderByUuidResponseDto",
|
||||
|
|
@ -479,6 +490,11 @@ __all__ = [
|
|||
"UpdateInfraBillingNodeResponseDto",
|
||||
"UpdateInfraProviderRequestDto",
|
||||
"UpdateInfraProviderResponseDto",
|
||||
"DeleteInfraBillingNodeResponseDto", # LEGACY
|
||||
"DeleteInfraProviderResponseDto", # LEGACY
|
||||
"GetAllInfraBillingHistoryResponseDto", # LEGACY
|
||||
"GetAllInfraBillingNodesResponseDto", # LEGACY
|
||||
"GetAllInfraProvidersResponseDto", # LEGACY
|
||||
# Internal squads models
|
||||
"AddUsersToInternalSquadRequestDto",
|
||||
"AddUsersToInternalSquadResponseDto",
|
||||
|
|
|
|||
|
|
@ -5,6 +5,27 @@ from uuid import UUID
|
|||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class InfraProviderSimpleDto(BaseModel):
|
||||
"""Упрощенная модель провайдера для billingNodes"""
|
||||
uuid: UUID
|
||||
name: str
|
||||
login_url: Optional[str] = Field(alias="loginUrl")
|
||||
favicon_link: Optional[str] = Field(alias="faviconLink")
|
||||
|
||||
|
||||
class InfraBillingHistoryStatsDto(BaseModel):
|
||||
"""Статистика истории биллинга для провайдера"""
|
||||
total_amount: float = Field(alias="totalAmount")
|
||||
total_bills: float = Field(alias="totalBills")
|
||||
|
||||
|
||||
class InfraBillingNodeSimpleDto(BaseModel):
|
||||
"""Упрощенная модель узла биллинга для провайдера"""
|
||||
node_uuid: UUID = Field(alias="nodeUuid")
|
||||
name: str
|
||||
country_code: str = Field(alias="countryCode")
|
||||
|
||||
|
||||
class InfraProviderDto(BaseModel):
|
||||
uuid: UUID
|
||||
name: str
|
||||
|
|
@ -12,17 +33,8 @@ class InfraProviderDto(BaseModel):
|
|||
login_url: Optional[str] = Field(None, alias="loginUrl")
|
||||
created_at: datetime = Field(alias="createdAt")
|
||||
updated_at: datetime = Field(alias="updatedAt")
|
||||
|
||||
|
||||
class InfraBillingHistoryDto(BaseModel):
|
||||
uuid: UUID
|
||||
node_uuid: UUID = Field(alias="nodeUuid")
|
||||
provider_uuid: UUID = Field(alias="providerUuid")
|
||||
provider: InfraProviderDto
|
||||
node: "NodeDto"
|
||||
next_billing_at: datetime = Field(alias="nextBillingAt")
|
||||
created_at: datetime = Field(alias="createdAt")
|
||||
updated_at: datetime = Field(alias="updatedAt")
|
||||
billing_history: InfraBillingHistoryStatsDto = Field(alias="billingHistory")
|
||||
billing_nodes: List[InfraBillingNodeSimpleDto] = Field(alias="billingNodes")
|
||||
|
||||
|
||||
class NodeDto(BaseModel):
|
||||
|
|
@ -31,17 +43,42 @@ class NodeDto(BaseModel):
|
|||
country_code: str = Field(alias="countryCode")
|
||||
|
||||
|
||||
class InfraBillingHistoryDto(BaseModel):
|
||||
uuid: UUID
|
||||
node_uuid: UUID = Field(alias="nodeUuid")
|
||||
provider_uuid: UUID = Field(alias="providerUuid")
|
||||
amount: float
|
||||
description: Optional[str] = None
|
||||
payment_date: datetime = Field(alias="paymentDate")
|
||||
created_at: datetime = Field(alias="createdAt")
|
||||
updated_at: datetime = Field(alias="updatedAt")
|
||||
|
||||
|
||||
class InfraBillingNodeDto(BaseModel):
|
||||
uuid: UUID
|
||||
node_uuid: UUID = Field(alias="nodeUuid")
|
||||
provider_uuid: UUID = Field(alias="providerUuid")
|
||||
provider: InfraProviderDto
|
||||
provider: InfraProviderSimpleDto
|
||||
node: NodeDto
|
||||
next_billing_at: datetime = Field(alias="nextBillingAt")
|
||||
created_at: datetime = Field(alias="createdAt")
|
||||
updated_at: datetime = Field(alias="updatedAt")
|
||||
|
||||
|
||||
class AvailableBillingNodeDto(BaseModel):
|
||||
"""Модель для доступных узлов биллинга"""
|
||||
uuid: UUID
|
||||
name: str
|
||||
country_code: str = Field(alias="countryCode")
|
||||
|
||||
|
||||
class BillingStatsDto(BaseModel):
|
||||
"""Статистика биллинга"""
|
||||
upcoming_nodes_count: float = Field(alias="upcomingNodesCount")
|
||||
current_month_payments: float = Field(alias="currentMonthPayments")
|
||||
total_spent: float = Field(alias="totalSpent")
|
||||
|
||||
|
||||
# Provider models
|
||||
class CreateInfraProviderRequestDto(BaseModel):
|
||||
name: str
|
||||
|
|
@ -65,11 +102,12 @@ class UpdateInfraProviderResponseDto(InfraProviderDto):
|
|||
|
||||
|
||||
class AllInfraProvidersData(BaseModel):
|
||||
total: int
|
||||
total: float = Field(alias="total")
|
||||
providers: List[InfraProviderDto]
|
||||
|
||||
|
||||
class GetAllInfraProvidersResponseDto(AllInfraProvidersData):
|
||||
# Исправленные имена моделей согласно OpenAPI
|
||||
class GetInfraProvidersResponseDto(AllInfraProvidersData):
|
||||
pass
|
||||
|
||||
|
||||
|
|
@ -77,22 +115,35 @@ class GetInfraProviderByUuidResponseDto(InfraProviderDto):
|
|||
pass
|
||||
|
||||
|
||||
class DeleteInfraProviderResponseDto(BaseModel):
|
||||
class DeleteInfraProviderByUuidResponseDto(BaseModel):
|
||||
is_deleted: bool = Field(alias="isDeleted")
|
||||
|
||||
|
||||
# Billing History models
|
||||
class CreateInfraBillingHistoryRecordRequestDto(BaseModel):
|
||||
"""Модель для создания записи истории биллинга"""
|
||||
node_uuid: UUID = Field(serialization_alias="nodeUuid")
|
||||
provider_uuid: UUID = Field(serialization_alias="providerUuid")
|
||||
amount: float
|
||||
description: Optional[str] = None
|
||||
payment_date: datetime = Field(serialization_alias="paymentDate")
|
||||
|
||||
|
||||
class CreateInfraBillingHistoryRecordResponseDto(InfraBillingHistoryDto):
|
||||
pass
|
||||
|
||||
|
||||
class InfraBillingHistoryData(BaseModel):
|
||||
records: List[InfraBillingHistoryDto]
|
||||
total: int
|
||||
|
||||
|
||||
class GetAllInfraBillingHistoryResponseDto(InfraBillingHistoryData):
|
||||
class GetInfraBillingHistoryRecordsResponseDto(InfraBillingHistoryData):
|
||||
pass
|
||||
|
||||
|
||||
class GetInfraBillingHistoryByUuidResponseDto(InfraBillingHistoryDto):
|
||||
pass
|
||||
class DeleteInfraBillingHistoryRecordByUuidResponseDto(BaseModel):
|
||||
is_deleted: bool = Field(alias="isDeleted")
|
||||
|
||||
|
||||
# Billing Nodes models
|
||||
|
|
@ -102,15 +153,18 @@ class CreateInfraBillingNodeRequestDto(BaseModel):
|
|||
next_billing_at: datetime = Field(serialization_alias="nextBillingAt")
|
||||
|
||||
|
||||
class CreateInfraBillingNodeResponseDto(InfraBillingNodeDto):
|
||||
pass
|
||||
# ИСПРАВЛЕНО: API возвращает список всех billing nodes после создания, а не один созданный
|
||||
class CreateInfraBillingNodeResponseDto(BaseModel):
|
||||
total_billing_nodes: float = Field(alias="totalBillingNodes")
|
||||
billing_nodes: List[InfraBillingNodeDto] = Field(alias="billingNodes")
|
||||
available_billing_nodes: List[AvailableBillingNodeDto] = Field(alias="availableBillingNodes")
|
||||
total_available_billing_nodes: float = Field(alias="totalAvailableBillingNodes")
|
||||
stats: BillingStatsDto
|
||||
|
||||
|
||||
class UpdateInfraBillingNodeRequestDto(BaseModel):
|
||||
uuid: UUID
|
||||
node_uuid: Optional[UUID] = Field(None, serialization_alias="nodeUuid")
|
||||
provider_uuid: Optional[UUID] = Field(None, serialization_alias="providerUuid")
|
||||
next_billing_at: Optional[datetime] = Field(None, serialization_alias="nextBillingAt")
|
||||
uuids: List[UUID]
|
||||
next_billing_at: datetime = Field(serialization_alias="nextBillingAt")
|
||||
|
||||
|
||||
class UpdateInfraBillingNodeResponseDto(InfraBillingNodeDto):
|
||||
|
|
@ -118,19 +172,31 @@ class UpdateInfraBillingNodeResponseDto(InfraBillingNodeDto):
|
|||
|
||||
|
||||
class InfraBillingNodesData(BaseModel):
|
||||
total_billing_nodes: int = Field(alias="totalBillingNodes")
|
||||
total_active_nodes: Optional[int] = Field(None, alias="totalActiveNodes")
|
||||
total_spent: Optional[int] = Field(None, alias="totalSpent")
|
||||
total_billing_nodes: float = Field(alias="totalBillingNodes")
|
||||
billing_nodes: List[InfraBillingNodeDto] = Field(alias="billingNodes")
|
||||
available_billing_nodes: List[AvailableBillingNodeDto] = Field(alias="availableBillingNodes")
|
||||
total_available_billing_nodes: float = Field(alias="totalAvailableBillingNodes")
|
||||
stats: BillingStatsDto
|
||||
|
||||
|
||||
class GetAllInfraBillingNodesResponseDto(InfraBillingNodesData):
|
||||
class GetInfraBillingNodesResponseDto(InfraBillingNodesData):
|
||||
pass
|
||||
|
||||
|
||||
class GetInfraBillingNodeByUuidResponseDto(InfraBillingNodeDto):
|
||||
pass
|
||||
class DeleteInfraBillingNodeByUuidResponseDto(BaseModel):
|
||||
"""API возвращает обновленный список billing nodes после удаления"""
|
||||
total_billing_nodes: float = Field(alias="totalBillingNodes")
|
||||
billing_nodes: List[InfraBillingNodeDto] = Field(alias="billingNodes")
|
||||
available_billing_nodes: List[AvailableBillingNodeDto] = Field(alias="availableBillingNodes")
|
||||
total_available_billing_nodes: float = Field(alias="totalAvailableBillingNodes")
|
||||
stats: BillingStatsDto
|
||||
|
||||
|
||||
class DeleteInfraBillingNodeResponseDto(BaseModel):
|
||||
is_deleted: bool = Field(alias="isDeleted")
|
||||
# Legacy aliases для обратной совместимости
|
||||
GetAllInfraProvidersResponseDto = GetInfraProvidersResponseDto
|
||||
DeleteInfraProviderResponseDto = DeleteInfraProviderByUuidResponseDto
|
||||
GetAllInfraBillingHistoryResponseDto = GetInfraBillingHistoryRecordsResponseDto
|
||||
GetInfraBillingHistoryByUuidResponseDto = InfraBillingHistoryDto
|
||||
GetAllInfraBillingNodesResponseDto = GetInfraBillingNodesResponseDto
|
||||
GetInfraBillingNodeByUuidResponseDto = InfraBillingNodeDto
|
||||
DeleteInfraBillingNodeResponseDto = DeleteInfraBillingNodeByUuidResponseDto
|
||||
|
|
@ -292,7 +292,8 @@ class WebhookPayloadDto(BaseModel):
|
|||
data = NodeDto(**data_raw)
|
||||
elif event.startswith("service."):
|
||||
if event.startswith("service.login_attempt"):
|
||||
data = LoginAttemptDto(**data_raw)
|
||||
login_attempt_data = data_raw.get("loginAttempt", {})
|
||||
data = LoginAttemptDto(**login_attempt_data)
|
||||
else: # service.panel_started - содержит пустой json
|
||||
data = data_raw
|
||||
elif event.startswith("errors."):
|
||||
|
|
|
|||
|
|
@ -3,17 +3,18 @@ from datetime import datetime, timedelta
|
|||
import pytest
|
||||
|
||||
from remnawave.models import (
|
||||
CreateInfraBillingHistoryRecordRequestDto,
|
||||
CreateInfraBillingHistoryRecordResponseDto,
|
||||
CreateInfraBillingNodeRequestDto,
|
||||
CreateInfraBillingNodeResponseDto,
|
||||
CreateInfraProviderRequestDto,
|
||||
CreateInfraProviderResponseDto,
|
||||
DeleteInfraBillingNodeResponseDto,
|
||||
DeleteInfraProviderResponseDto,
|
||||
GetAllInfraBillingHistoryResponseDto,
|
||||
GetAllInfraBillingNodesResponseDto,
|
||||
GetAllInfraProvidersResponseDto,
|
||||
GetInfraBillingHistoryByUuidResponseDto,
|
||||
GetInfraBillingNodeByUuidResponseDto,
|
||||
DeleteInfraBillingHistoryRecordByUuidResponseDto,
|
||||
DeleteInfraBillingNodeByUuidResponseDto,
|
||||
DeleteInfraProviderByUuidResponseDto,
|
||||
GetInfraBillingHistoryRecordsResponseDto,
|
||||
GetInfraBillingNodesResponseDto,
|
||||
GetInfraProvidersResponseDto,
|
||||
GetInfraProviderByUuidResponseDto,
|
||||
UpdateInfraBillingNodeRequestDto,
|
||||
UpdateInfraBillingNodeResponseDto,
|
||||
|
|
@ -24,7 +25,8 @@ from tests.utils import generate_random_string
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_infra_billing(remnawave) -> None:
|
||||
async def test_infra_billing_providers(remnawave) -> None:
|
||||
"""Test infra billing providers CRUD operations"""
|
||||
provider_name = f"test_provider_{generate_random_string(length=6)}"
|
||||
|
||||
# Test create infra provider
|
||||
|
|
@ -38,19 +40,26 @@ async def test_infra_billing(remnawave) -> None:
|
|||
|
||||
assert isinstance(create_provider, CreateInfraProviderResponseDto)
|
||||
assert create_provider.name == provider_name
|
||||
assert create_provider.favicon_link == "https://example.com/favicon.ico"
|
||||
assert create_provider.login_url == "https://example.com/login"
|
||||
|
||||
provider_uuid = str(create_provider.uuid)
|
||||
|
||||
# Test get all infra providers
|
||||
all_providers = await remnawave.infra_billing.get_all_infra_providers()
|
||||
assert isinstance(all_providers, GetAllInfraProvidersResponseDto)
|
||||
all_providers = await remnawave.infra_billing.get_infra_providers()
|
||||
assert isinstance(all_providers, GetInfraProvidersResponseDto)
|
||||
assert all_providers.total > 0
|
||||
assert len(all_providers.providers) > 0
|
||||
|
||||
# Verify our provider is in the list
|
||||
provider_found = any(p.uuid == create_provider.uuid for p in all_providers.providers)
|
||||
assert provider_found
|
||||
|
||||
# Test get infra provider by uuid
|
||||
provider_by_uuid = await remnawave.infra_billing.get_infra_provider_by_uuid(provider_uuid)
|
||||
assert isinstance(provider_by_uuid, GetInfraProviderByUuidResponseDto)
|
||||
assert provider_by_uuid.name == provider_name
|
||||
assert provider_by_uuid.uuid == create_provider.uuid
|
||||
|
||||
# Test update infra provider
|
||||
updated_name = f"updated_{provider_name}"
|
||||
|
|
@ -58,25 +67,159 @@ async def test_infra_billing(remnawave) -> None:
|
|||
UpdateInfraProviderRequestDto(
|
||||
uuid=create_provider.uuid,
|
||||
name=updated_name,
|
||||
favicon_link="https://example.com/new-favicon.ico"
|
||||
favicon_link="https://example.com/new-favicon.ico",
|
||||
login_url="https://example.com/new-login"
|
||||
)
|
||||
)
|
||||
|
||||
assert isinstance(update_provider, UpdateInfraProviderResponseDto)
|
||||
assert update_provider.name == updated_name
|
||||
|
||||
# Test get all infra billing history
|
||||
billing_history = await remnawave.infra_billing.get_all_infra_billing_history(start=0, size=25)
|
||||
assert isinstance(billing_history, GetAllInfraBillingHistoryResponseDto)
|
||||
|
||||
# Test get all infra billing nodes
|
||||
billing_nodes = await remnawave.infra_billing.get_all_infra_billing_nodes(start=0, size=25)
|
||||
assert isinstance(billing_nodes, GetAllInfraBillingNodesResponseDto)
|
||||
|
||||
# Skip testing actual billing node creation/update/delete as it requires existing nodes
|
||||
# These would need real node UUIDs which may not exist in test environment
|
||||
assert update_provider.favicon_link == "https://example.com/new-favicon.ico"
|
||||
assert update_provider.login_url == "https://example.com/new-login"
|
||||
|
||||
# Test delete infra provider
|
||||
delete_provider = await remnawave.infra_billing.delete_infra_provider_by_uuid(provider_uuid)
|
||||
assert isinstance(delete_provider, DeleteInfraProviderResponseDto)
|
||||
assert isinstance(delete_provider, DeleteInfraProviderByUuidResponseDto)
|
||||
assert delete_provider.is_deleted is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_infra_billing_history(remnawave) -> None:
|
||||
"""Test infra billing history operations"""
|
||||
|
||||
# Test get all infra billing history
|
||||
billing_history = await remnawave.infra_billing.get_infra_billing_history_records()
|
||||
assert isinstance(billing_history, GetInfraBillingHistoryRecordsResponseDto)
|
||||
assert hasattr(billing_history, 'records')
|
||||
assert hasattr(billing_history, 'total')
|
||||
|
||||
# Skip creating history record as it may require specific setup
|
||||
print("Billing history operations tested successfully")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_infra_billing_nodes(remnawave) -> None:
|
||||
"""Test infra billing nodes operations"""
|
||||
|
||||
# Test get all infra billing nodes
|
||||
billing_nodes = await remnawave.infra_billing.get_billing_nodes()
|
||||
assert isinstance(billing_nodes, GetInfraBillingNodesResponseDto)
|
||||
assert hasattr(billing_nodes, 'total_billing_nodes')
|
||||
assert hasattr(billing_nodes, 'billing_nodes')
|
||||
assert hasattr(billing_nodes, 'available_billing_nodes')
|
||||
assert hasattr(billing_nodes, 'total_available_billing_nodes')
|
||||
assert hasattr(billing_nodes, 'stats')
|
||||
|
||||
# Verify stats structure
|
||||
assert hasattr(billing_nodes.stats, 'upcoming_nodes_count')
|
||||
assert hasattr(billing_nodes.stats, 'current_month_payments')
|
||||
assert hasattr(billing_nodes.stats, 'total_spent')
|
||||
|
||||
# Test create billing node (only if we have providers and available nodes)
|
||||
providers = await remnawave.infra_billing.get_infra_providers()
|
||||
if (providers.total > 0 and len(providers.providers) > 0 and
|
||||
billing_nodes.total_available_billing_nodes > 0 and
|
||||
len(billing_nodes.available_billing_nodes) > 0):
|
||||
|
||||
provider = providers.providers[0]
|
||||
available_node = billing_nodes.available_billing_nodes[0]
|
||||
next_billing = datetime.now() + timedelta(days=30)
|
||||
|
||||
# Create billing node - API возвращает весь список узлов
|
||||
create_billing_node = await remnawave.infra_billing.create_infra_billing_node(
|
||||
CreateInfraBillingNodeRequestDto(
|
||||
node_uuid=available_node.uuid,
|
||||
provider_uuid=provider.uuid,
|
||||
next_billing_at=next_billing
|
||||
)
|
||||
)
|
||||
|
||||
assert isinstance(create_billing_node, CreateInfraBillingNodeResponseDto)
|
||||
assert hasattr(create_billing_node, 'billing_nodes')
|
||||
assert hasattr(create_billing_node, 'total_billing_nodes')
|
||||
|
||||
# Find the created billing node
|
||||
created_node = None
|
||||
for node in create_billing_node.billing_nodes:
|
||||
if node.node.uuid == available_node.uuid and node.provider.uuid == provider.uuid:
|
||||
created_node = node
|
||||
break
|
||||
|
||||
assert created_node is not None, "Created billing node not found in response"
|
||||
billing_node_uuid = str(created_node.uuid)
|
||||
|
||||
# Test delete billing node - API возвращает обновленный список
|
||||
delete_billing_node = await remnawave.infra_billing.delete_infra_billing_node_by_uuid(billing_node_uuid)
|
||||
assert isinstance(delete_billing_node, DeleteInfraBillingNodeByUuidResponseDto)
|
||||
assert hasattr(delete_billing_node, 'billing_nodes')
|
||||
assert hasattr(delete_billing_node, 'total_billing_nodes')
|
||||
|
||||
# Verify the node was deleted (not in the response list)
|
||||
node_still_exists = any(
|
||||
node.uuid == created_node.uuid
|
||||
for node in delete_billing_node.billing_nodes
|
||||
)
|
||||
assert not node_still_exists, "Billing node should be deleted but still found in response"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_infra_billing_complete_workflow(remnawave) -> None:
|
||||
"""Test complete workflow: create provider -> create billing node -> cleanup"""
|
||||
provider_name = f"workflow_provider_{generate_random_string(length=6)}"
|
||||
|
||||
# 1. Create provider
|
||||
create_provider = await remnawave.infra_billing.create_infra_provider(
|
||||
CreateInfraProviderRequestDto(
|
||||
name=provider_name,
|
||||
favicon_link="https://workflow.com/favicon.ico",
|
||||
login_url="https://workflow.com/login"
|
||||
)
|
||||
)
|
||||
|
||||
assert isinstance(create_provider, CreateInfraProviderResponseDto)
|
||||
provider_uuid = str(create_provider.uuid)
|
||||
|
||||
try:
|
||||
# 2. Get available nodes
|
||||
billing_nodes = await remnawave.infra_billing.get_billing_nodes()
|
||||
|
||||
if (billing_nodes.total_available_billing_nodes > 0 and
|
||||
len(billing_nodes.available_billing_nodes) > 0):
|
||||
|
||||
available_node = billing_nodes.available_billing_nodes[0]
|
||||
next_billing = datetime.now() + timedelta(days=30)
|
||||
|
||||
# 3. Create billing node
|
||||
create_billing_node = await remnawave.infra_billing.create_infra_billing_node(
|
||||
CreateInfraBillingNodeRequestDto(
|
||||
node_uuid=available_node.uuid,
|
||||
provider_uuid=create_provider.uuid,
|
||||
next_billing_at=next_billing
|
||||
)
|
||||
)
|
||||
|
||||
# Find created node
|
||||
created_node = None
|
||||
for node in create_billing_node.billing_nodes:
|
||||
if node.node.uuid == available_node.uuid and node.provider.uuid == create_provider.uuid:
|
||||
created_node = node
|
||||
break
|
||||
|
||||
assert created_node is not None
|
||||
billing_node_uuid = str(created_node.uuid)
|
||||
|
||||
# 4. Cleanup billing node - API возвращает обновленный список
|
||||
delete_billing_node = await remnawave.infra_billing.delete_infra_billing_node_by_uuid(billing_node_uuid)
|
||||
assert isinstance(delete_billing_node, DeleteInfraBillingNodeByUuidResponseDto)
|
||||
|
||||
# Verify deletion by checking the node is not in the list
|
||||
node_still_exists = any(
|
||||
node.uuid == created_node.uuid
|
||||
for node in delete_billing_node.billing_nodes
|
||||
)
|
||||
assert not node_still_exists, "Billing node should be deleted"
|
||||
|
||||
finally:
|
||||
# 5. Cleanup provider
|
||||
delete_provider = await remnawave.infra_billing.delete_infra_provider_by_uuid(provider_uuid)
|
||||
assert delete_provider.is_deleted is True
|
||||
Loading…
Add table
Add a link
Reference in a new issue