修复:完善报表查询校验
This commit is contained in:
@@ -8,6 +8,7 @@ from sqlalchemy.orm.attributes import InstrumentedAttribute
|
|||||||
|
|
||||||
from common.vo import PageModel
|
from common.vo import PageModel
|
||||||
from module_admin.entity.do.user_do import SysUser
|
from module_admin.entity.do.user_do import SysUser
|
||||||
|
from module_business.domain.enums import DocumentStatus
|
||||||
from module_business.entity.do.master_do import (
|
from module_business.entity.do.master_do import (
|
||||||
BizCustomer,
|
BizCustomer,
|
||||||
BizCustomerSite,
|
BizCustomerSite,
|
||||||
@@ -234,7 +235,7 @@ class ReportDao:
|
|||||||
source_warehouse = aliased(BizWarehouse, name='source_warehouse')
|
source_warehouse = aliased(BizWarehouse, name='source_warehouse')
|
||||||
target_site = aliased(BizCustomerSite, name='target_site')
|
target_site = aliased(BizCustomerSite, name='target_site')
|
||||||
target_customer = aliased(BizCustomer, name='target_customer')
|
target_customer = aliased(BizCustomer, name='target_customer')
|
||||||
conditions = []
|
conditions = [BizShipmentOrder.status == DocumentStatus.CONFIRMED.value]
|
||||||
warehouse_scope = cls._scope_condition(BizShipmentOrder.source_warehouse_id, warehouse_ids)
|
warehouse_scope = cls._scope_condition(BizShipmentOrder.source_warehouse_id, warehouse_ids)
|
||||||
site_scope = cls._scope_condition(BizShipmentOrder.target_site_id, site_ids)
|
site_scope = cls._scope_condition(BizShipmentOrder.target_site_id, site_ids)
|
||||||
if warehouse_scope is not None:
|
if warehouse_scope is not None:
|
||||||
|
|||||||
@@ -1,18 +1,40 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from pydantic import BaseModel, ConfigDict, Field
|
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||||
from pydantic.alias_generators import to_camel
|
from pydantic.alias_generators import to_camel
|
||||||
|
|
||||||
from module_business.domain.enums import DocumentStatus, PalletEventType, PalletFlowState, ReturnStatus
|
from module_business.domain.enums import DocumentStatus, PalletEventType, PalletFlowState, ReturnStatus
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_report_query_date(value: str) -> datetime:
|
||||||
|
try:
|
||||||
|
parsed = datetime.strptime(value, '%Y-%m-%d')
|
||||||
|
except ValueError as exc:
|
||||||
|
raise ValueError('日期必须为有效的 YYYY-MM-DD 格式') from exc
|
||||||
|
if parsed.strftime('%Y-%m-%d') != value:
|
||||||
|
raise ValueError('日期必须为有效的 YYYY-MM-DD 格式')
|
||||||
|
return parsed
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_report_query_date_range(value: BaseModel) -> BaseModel:
|
||||||
|
begin_value = value.begin_time
|
||||||
|
end_value = value.end_time
|
||||||
|
begin_date = _parse_report_query_date(begin_value) if begin_value else None
|
||||||
|
end_date = _parse_report_query_date(end_value) if end_value else None
|
||||||
|
if end_date and end_date.date() == datetime.max.date():
|
||||||
|
raise ValueError('结束日期必须早于 9999-12-31')
|
||||||
|
if begin_date and end_date and begin_date > end_date:
|
||||||
|
raise ValueError('开始日期不能晚于结束日期')
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
class PalletInventoryReportQueryModel(BaseModel):
|
class PalletInventoryReportQueryModel(BaseModel):
|
||||||
"""托盘库存报表查询模型。"""
|
"""托盘库存报表查询模型。"""
|
||||||
|
|
||||||
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True)
|
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True)
|
||||||
|
|
||||||
page_num: int = Field(default=1, gt=0, description='当前页码')
|
page_num: int = Field(default=1, gt=0, description='当前页码')
|
||||||
page_size: int = Field(default=10, gt=0, description='每页记录数')
|
page_size: int = Field(default=10, gt=0, le=100, description='每页记录数')
|
||||||
begin_time: str | None = Field(default=None, min_length=1, pattern=r'\S', description='开始时间')
|
begin_time: str | None = Field(default=None, min_length=1, pattern=r'\S', description='开始时间')
|
||||||
end_time: str | None = Field(default=None, min_length=1, pattern=r'\S', description='结束时间')
|
end_time: str | None = Field(default=None, min_length=1, pattern=r'\S', description='结束时间')
|
||||||
pallet_code: str | None = Field(default=None, min_length=1, max_length=64, pattern=r'\S', description='托盘编号')
|
pallet_code: str | None = Field(default=None, min_length=1, max_length=64, pattern=r'\S', description='托盘编号')
|
||||||
@@ -23,6 +45,8 @@ class PalletInventoryReportQueryModel(BaseModel):
|
|||||||
strain_id: int | None = Field(default=None, gt=0, description='菌种ID')
|
strain_id: int | None = Field(default=None, gt=0, description='菌种ID')
|
||||||
batch_no: str | None = Field(default=None, min_length=1, max_length=64, pattern=r'\S', description='生产批次')
|
batch_no: str | None = Field(default=None, min_length=1, max_length=64, pattern=r'\S', description='生产批次')
|
||||||
|
|
||||||
|
_validate_date_range = model_validator(mode='after')(_validate_report_query_date_range)
|
||||||
|
|
||||||
|
|
||||||
class PalletInventoryReportModel(BaseModel):
|
class PalletInventoryReportModel(BaseModel):
|
||||||
"""托盘库存报表行模型。"""
|
"""托盘库存报表行模型。"""
|
||||||
@@ -53,7 +77,7 @@ class FillingReportQueryModel(BaseModel):
|
|||||||
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True)
|
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True)
|
||||||
|
|
||||||
page_num: int = Field(default=1, gt=0, description='当前页码')
|
page_num: int = Field(default=1, gt=0, description='当前页码')
|
||||||
page_size: int = Field(default=10, gt=0, description='每页记录数')
|
page_size: int = Field(default=10, gt=0, le=100, description='每页记录数')
|
||||||
begin_time: str | None = Field(default=None, min_length=1, pattern=r'\S', description='开始时间')
|
begin_time: str | None = Field(default=None, min_length=1, pattern=r'\S', description='开始时间')
|
||||||
end_time: str | None = Field(default=None, min_length=1, pattern=r'\S', description='结束时间')
|
end_time: str | None = Field(default=None, min_length=1, pattern=r'\S', description='结束时间')
|
||||||
warehouse_id: int | None = Field(default=None, gt=0, description='仓库ID')
|
warehouse_id: int | None = Field(default=None, gt=0, description='仓库ID')
|
||||||
@@ -61,6 +85,8 @@ class FillingReportQueryModel(BaseModel):
|
|||||||
strain_id: int | None = Field(default=None, gt=0, description='菌种ID')
|
strain_id: int | None = Field(default=None, gt=0, description='菌种ID')
|
||||||
batch_no: str | None = Field(default=None, min_length=1, max_length=64, pattern=r'\S', description='生产批次')
|
batch_no: str | None = Field(default=None, min_length=1, max_length=64, pattern=r'\S', description='生产批次')
|
||||||
|
|
||||||
|
_validate_date_range = model_validator(mode='after')(_validate_report_query_date_range)
|
||||||
|
|
||||||
|
|
||||||
class FillingReportModel(BaseModel):
|
class FillingReportModel(BaseModel):
|
||||||
"""装填记录报表行模型。"""
|
"""装填记录报表行模型。"""
|
||||||
@@ -90,7 +116,7 @@ class ShipmentReportQueryModel(BaseModel):
|
|||||||
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True)
|
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True)
|
||||||
|
|
||||||
page_num: int = Field(default=1, gt=0, description='当前页码')
|
page_num: int = Field(default=1, gt=0, description='当前页码')
|
||||||
page_size: int = Field(default=10, gt=0, description='每页记录数')
|
page_size: int = Field(default=10, gt=0, le=100, description='每页记录数')
|
||||||
begin_time: str | None = Field(default=None, min_length=1, pattern=r'\S', description='开始时间')
|
begin_time: str | None = Field(default=None, min_length=1, pattern=r'\S', description='开始时间')
|
||||||
end_time: str | None = Field(default=None, min_length=1, pattern=r'\S', description='结束时间')
|
end_time: str | None = Field(default=None, min_length=1, pattern=r'\S', description='结束时间')
|
||||||
shipment_no: str | None = Field(default=None, min_length=1, max_length=64, pattern=r'\S', description='发货单号')
|
shipment_no: str | None = Field(default=None, min_length=1, max_length=64, pattern=r'\S', description='发货单号')
|
||||||
@@ -99,6 +125,8 @@ class ShipmentReportQueryModel(BaseModel):
|
|||||||
pallet_code: str | None = Field(default=None, min_length=1, max_length=64, pattern=r'\S', description='托盘编号')
|
pallet_code: str | None = Field(default=None, min_length=1, max_length=64, pattern=r'\S', description='托盘编号')
|
||||||
status: DocumentStatus | None = Field(default=None, description='单据状态')
|
status: DocumentStatus | None = Field(default=None, description='单据状态')
|
||||||
|
|
||||||
|
_validate_date_range = model_validator(mode='after')(_validate_report_query_date_range)
|
||||||
|
|
||||||
|
|
||||||
class ShipmentReportModel(BaseModel):
|
class ShipmentReportModel(BaseModel):
|
||||||
"""发货出库报表行模型。"""
|
"""发货出库报表行模型。"""
|
||||||
@@ -126,7 +154,7 @@ class ConsumptionReportQueryModel(BaseModel):
|
|||||||
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True)
|
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True)
|
||||||
|
|
||||||
page_num: int = Field(default=1, gt=0, description='当前页码')
|
page_num: int = Field(default=1, gt=0, description='当前页码')
|
||||||
page_size: int = Field(default=10, gt=0, description='每页记录数')
|
page_size: int = Field(default=10, gt=0, le=100, description='每页记录数')
|
||||||
begin_time: str | None = Field(default=None, min_length=1, pattern=r'\S', description='开始时间')
|
begin_time: str | None = Field(default=None, min_length=1, pattern=r'\S', description='开始时间')
|
||||||
end_time: str | None = Field(default=None, min_length=1, pattern=r'\S', description='结束时间')
|
end_time: str | None = Field(default=None, min_length=1, pattern=r'\S', description='结束时间')
|
||||||
customer_id: int | None = Field(default=None, gt=0, description='客户ID')
|
customer_id: int | None = Field(default=None, gt=0, description='客户ID')
|
||||||
@@ -136,6 +164,8 @@ class ConsumptionReportQueryModel(BaseModel):
|
|||||||
strain_id: int | None = Field(default=None, gt=0, description='菌种ID')
|
strain_id: int | None = Field(default=None, gt=0, description='菌种ID')
|
||||||
batch_no: str | None = Field(default=None, min_length=1, max_length=64, pattern=r'\S', description='生产批次')
|
batch_no: str | None = Field(default=None, min_length=1, max_length=64, pattern=r'\S', description='生产批次')
|
||||||
|
|
||||||
|
_validate_date_range = model_validator(mode='after')(_validate_report_query_date_range)
|
||||||
|
|
||||||
|
|
||||||
class ConsumptionReportModel(BaseModel):
|
class ConsumptionReportModel(BaseModel):
|
||||||
"""产线消耗报表行模型。"""
|
"""产线消耗报表行模型。"""
|
||||||
@@ -169,7 +199,7 @@ class ReturnReportQueryModel(BaseModel):
|
|||||||
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True)
|
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True)
|
||||||
|
|
||||||
page_num: int = Field(default=1, gt=0, description='当前页码')
|
page_num: int = Field(default=1, gt=0, description='当前页码')
|
||||||
page_size: int = Field(default=10, gt=0, description='每页记录数')
|
page_size: int = Field(default=10, gt=0, le=100, description='每页记录数')
|
||||||
begin_time: str | None = Field(default=None, min_length=1, pattern=r'\S', description='开始时间')
|
begin_time: str | None = Field(default=None, min_length=1, pattern=r'\S', description='开始时间')
|
||||||
end_time: str | None = Field(default=None, min_length=1, pattern=r'\S', description='结束时间')
|
end_time: str | None = Field(default=None, min_length=1, pattern=r'\S', description='结束时间')
|
||||||
return_no: str | None = Field(default=None, min_length=1, max_length=64, pattern=r'\S', description='退回单号')
|
return_no: str | None = Field(default=None, min_length=1, max_length=64, pattern=r'\S', description='退回单号')
|
||||||
@@ -178,6 +208,8 @@ class ReturnReportQueryModel(BaseModel):
|
|||||||
pallet_code: str | None = Field(default=None, min_length=1, max_length=64, pattern=r'\S', description='托盘编号')
|
pallet_code: str | None = Field(default=None, min_length=1, max_length=64, pattern=r'\S', description='托盘编号')
|
||||||
status: ReturnStatus | None = Field(default=None, description='退回状态')
|
status: ReturnStatus | None = Field(default=None, description='退回状态')
|
||||||
|
|
||||||
|
_validate_date_range = model_validator(mode='after')(_validate_report_query_date_range)
|
||||||
|
|
||||||
|
|
||||||
class ReturnReportModel(BaseModel):
|
class ReturnReportModel(BaseModel):
|
||||||
"""退回仓库报表行模型。"""
|
"""退回仓库报表行模型。"""
|
||||||
@@ -205,12 +237,14 @@ class PalletTraceReportQueryModel(BaseModel):
|
|||||||
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True)
|
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True, populate_by_name=True)
|
||||||
|
|
||||||
page_num: int = Field(default=1, gt=0, description='当前页码')
|
page_num: int = Field(default=1, gt=0, description='当前页码')
|
||||||
page_size: int = Field(default=10, gt=0, description='每页记录数')
|
page_size: int = Field(default=10, gt=0, le=100, description='每页记录数')
|
||||||
begin_time: str | None = Field(default=None, min_length=1, pattern=r'\S', description='开始时间')
|
begin_time: str | None = Field(default=None, min_length=1, pattern=r'\S', description='开始时间')
|
||||||
end_time: str | None = Field(default=None, min_length=1, pattern=r'\S', description='结束时间')
|
end_time: str | None = Field(default=None, min_length=1, pattern=r'\S', description='结束时间')
|
||||||
pallet_code: str | None = Field(default=None, min_length=1, max_length=64, pattern=r'\S', description='托盘编号')
|
pallet_code: str | None = Field(default=None, min_length=1, max_length=64, pattern=r'\S', description='托盘编号')
|
||||||
event_type: PalletEventType | None = Field(default=None, description='事件类型')
|
event_type: PalletEventType | None = Field(default=None, description='事件类型')
|
||||||
|
|
||||||
|
_validate_date_range = model_validator(mode='after')(_validate_report_query_date_range)
|
||||||
|
|
||||||
|
|
||||||
class PalletTraceReportModel(BaseModel):
|
class PalletTraceReportModel(BaseModel):
|
||||||
"""托盘追溯报表行模型。"""
|
"""托盘追溯报表行模型。"""
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from typing import Any
|
|||||||
from unittest.mock import AsyncMock, MagicMock, patch
|
from unittest.mock import AsyncMock, MagicMock, patch
|
||||||
|
|
||||||
from fastapi.routing import APIRoute
|
from fastapi.routing import APIRoute
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel, ValidationError
|
||||||
from sqlalchemy import Select
|
from sqlalchemy import Select
|
||||||
from sqlalchemy.dialects import mysql, postgresql
|
from sqlalchemy.dialects import mysql, postgresql
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
@@ -44,6 +44,15 @@ ReportDao = getattr(report_dao_module, 'ReportDao', None)
|
|||||||
ReportService = getattr(report_service_module, 'ReportService', None)
|
ReportService = getattr(report_service_module, 'ReportService', None)
|
||||||
report_controller = getattr(report_controller_module, 'report_controller', None)
|
report_controller = getattr(report_controller_module, 'report_controller', None)
|
||||||
|
|
||||||
|
REPORT_QUERY_MODELS = (
|
||||||
|
PalletInventoryReportQueryModel,
|
||||||
|
FillingReportQueryModel,
|
||||||
|
ShipmentReportQueryModel,
|
||||||
|
ConsumptionReportQueryModel,
|
||||||
|
ReturnReportQueryModel,
|
||||||
|
PalletTraceReportQueryModel,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def make_user(user_id: int = 7) -> CurrentUserModel:
|
def make_user(user_id: int = 7) -> CurrentUserModel:
|
||||||
return CurrentUserModel(
|
return CurrentUserModel(
|
||||||
@@ -255,6 +264,58 @@ class TestReportPublicContract(unittest.TestCase):
|
|||||||
self.assertTrue(forbidden_calls.isdisjoint(called_attributes))
|
self.assertTrue(forbidden_calls.isdisjoint(called_attributes))
|
||||||
|
|
||||||
|
|
||||||
|
class TestReportQueryValidation(unittest.TestCase):
|
||||||
|
def test_dates_require_strict_real_calendar_values(self) -> None:
|
||||||
|
for model_class in REPORT_QUERY_MODELS:
|
||||||
|
with self.subTest(model=model_class.__name__, case='begin-only'):
|
||||||
|
model = model_class.model_validate({'beginTime': '2026-07-01'})
|
||||||
|
self.assertEqual('2026-07-01', model.begin_time)
|
||||||
|
self.assertIsNone(model.end_time)
|
||||||
|
with self.subTest(model=model_class.__name__, case='end-only'):
|
||||||
|
model = model_class.model_validate({'endTime': '2026-07-31'})
|
||||||
|
self.assertIsNone(model.begin_time)
|
||||||
|
self.assertEqual('2026-07-31', model.end_time)
|
||||||
|
for field_name in ('beginTime', 'endTime'):
|
||||||
|
for invalid_date in ('not-a-date', '2026-02-30', '2026-7-01', '2026-07-1'):
|
||||||
|
with (
|
||||||
|
self.subTest(
|
||||||
|
model=model_class.__name__,
|
||||||
|
field=field_name,
|
||||||
|
value=invalid_date,
|
||||||
|
),
|
||||||
|
self.assertRaises(ValidationError),
|
||||||
|
):
|
||||||
|
model_class.model_validate({field_name: invalid_date})
|
||||||
|
|
||||||
|
def test_date_range_allows_none_and_equal_but_rejects_inverted_or_overflowing_end(self) -> None:
|
||||||
|
for model_class in REPORT_QUERY_MODELS:
|
||||||
|
with self.subTest(model=model_class.__name__, case='none'):
|
||||||
|
model = model_class.model_validate({'beginTime': None, 'endTime': None})
|
||||||
|
self.assertIsNone(model.begin_time)
|
||||||
|
self.assertIsNone(model.end_time)
|
||||||
|
with self.subTest(model=model_class.__name__, case='equal'):
|
||||||
|
model = model_class.model_validate({'beginTime': '2026-07-02', 'endTime': '2026-07-02'})
|
||||||
|
self.assertEqual(model.begin_time, model.end_time)
|
||||||
|
with self.subTest(model=model_class.__name__, case='max-begin'):
|
||||||
|
model = model_class.model_validate({'beginTime': '9999-12-31'})
|
||||||
|
self.assertEqual('9999-12-31', model.begin_time)
|
||||||
|
with self.subTest(model=model_class.__name__, case='inverted'), self.assertRaises(ValidationError):
|
||||||
|
model_class.model_validate({'beginTime': '2026-07-03', 'endTime': '2026-07-02'})
|
||||||
|
with self.subTest(model=model_class.__name__, case='max-end'), self.assertRaises(ValidationError):
|
||||||
|
model_class.model_validate({'endTime': '9999-12-31'})
|
||||||
|
|
||||||
|
def test_page_size_is_capped_at_100(self) -> None:
|
||||||
|
for model_class in REPORT_QUERY_MODELS:
|
||||||
|
with self.subTest(model=model_class.__name__, page_size=100):
|
||||||
|
self.assertEqual(100, model_class.model_validate({'pageSize': 100}).page_size)
|
||||||
|
for invalid_size in (101, 10**9):
|
||||||
|
with (
|
||||||
|
self.subTest(model=model_class.__name__, page_size=invalid_size),
|
||||||
|
self.assertRaises(ValidationError),
|
||||||
|
):
|
||||||
|
model_class.model_validate({'pageSize': invalid_size})
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipUnless(ReportDao is not None, 'ReportDao 尚未实现')
|
@unittest.skipUnless(ReportDao is not None, 'ReportDao 尚未实现')
|
||||||
class TestReportDaoQueries(unittest.IsolatedAsyncioTestCase):
|
class TestReportDaoQueries(unittest.IsolatedAsyncioTestCase):
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
@@ -417,6 +478,19 @@ class TestReportDaoQueries(unittest.IsolatedAsyncioTestCase):
|
|||||||
sql,
|
sql,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def test_shipment_query_requires_confirmed_fact_in_addition_to_optional_status(self) -> None:
|
||||||
|
statement = await self._capture_statement(
|
||||||
|
ReportDao.get_shipment_report_list,
|
||||||
|
ShipmentReportQueryModel(status=DocumentStatus.DRAFT),
|
||||||
|
warehouse_ids=None,
|
||||||
|
site_ids=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
for sql in self._assert_compiles_for_supported_databases(statement):
|
||||||
|
self.assertIn("biz_shipment_order.status = 'draft'", sql)
|
||||||
|
self.assertIn("biz_shipment_order.status = 'confirmed'", sql)
|
||||||
|
self.assertEqual(2, sql.count('biz_shipment_order.status ='))
|
||||||
|
|
||||||
async def test_consumption_query_uses_usage_filling_snapshots_all_filters_and_site_scope(self) -> None:
|
async def test_consumption_query_uses_usage_filling_snapshots_all_filters_and_site_scope(self) -> None:
|
||||||
query = ConsumptionReportQueryModel(
|
query = ConsumptionReportQueryModel(
|
||||||
customerId=3,
|
customerId=3,
|
||||||
@@ -917,9 +991,14 @@ class TestReportControllerContract(unittest.TestCase):
|
|||||||
}
|
}
|
||||||
for path, parameters in expected_parameters.items():
|
for path, parameters in expected_parameters.items():
|
||||||
with self.subTest(path=path):
|
with self.subTest(path=path):
|
||||||
actual = {parameter['name'] for parameter in schema['paths'][path]['get']['parameters']}
|
operation_parameters = schema['paths'][path]['get']['parameters']
|
||||||
|
actual = {parameter['name'] for parameter in operation_parameters}
|
||||||
self.assertEqual(parameters, actual)
|
self.assertEqual(parameters, actual)
|
||||||
self.assertNotIn('mode', actual)
|
self.assertNotIn('mode', actual)
|
||||||
|
page_size_parameter = next(
|
||||||
|
parameter for parameter in operation_parameters if parameter['name'] == 'pageSize'
|
||||||
|
)
|
||||||
|
self.assertEqual(100, page_size_parameter['schema'].get('maximum'))
|
||||||
|
|
||||||
def test_routes_have_exact_permissions_response_models_current_user_and_no_log(self) -> None:
|
def test_routes_have_exact_permissions_response_models_current_user_and_no_log(self) -> None:
|
||||||
expected = {
|
expected = {
|
||||||
|
|||||||
Reference in New Issue
Block a user