Files
ShiErFeng/shierfeng-fastapi-backend/tests/module_business/test_report_dates.py
T

117 lines
6.6 KiB
Python

"""日期报表查询与响应回归,仅读取生成的查询,不连接数据库。"""
import unittest
from datetime import date
from unittest.mock import AsyncMock, patch
from common.vo import PageModel
from module_business.dao.report_dao import ReportDao
from module_business.domain.test_batch_scope import TestBatchScope
from module_business.entity.vo.report_vo import (
ConsumptionReportModel, ConsumptionReportQueryModel,
PalletInventoryReportModel, PalletInventoryReportQueryModel,
PalletTraceReportModel, PalletTraceReportQueryModel,
ReturnReportModel, ReturnReportQueryModel,
)
from module_business.service.report_service import ReportService
from tests.module_business.test_report_service import make_user, report_row
from tests.module_business.test_report_tunnel import joins, sql
REPORTS = (
('pallet_inventory', PalletInventoryReportModel, PalletInventoryReportQueryModel, 'active_filling'),
('consumption_report', ConsumptionReportModel, ConsumptionReportQueryModel, 'biz_filling_record'),
('return_report', ReturnReportModel, ReturnReportQueryModel, 'return_filling'),
('pallet_trace', PalletTraceReportModel, PalletTraceReportQueryModel, 'trace_filling'),
)
class TestReportDates(unittest.IsolatedAsyncioTestCase):
async def statement(self, name, query, batch_id=None):
page = PageModel(rows=[], total=0, pageNum=1, pageSize=10, hasNext=False)
with patch.object(ReportDao, '_paginate', AsyncMock(return_value=page)) as paginate:
await getattr(ReportDao, f'get_{name}_list')(
None, query, TestBatchScope(batch_id, 'TEST' if batch_id else 'FORMAL', None, False),
)
return paginate.await_args.args[1]
async def test_dates_are_selected_from_the_referenced_filling_not_operation_time(self):
for name, _, query_type, alias in REPORTS:
with self.subTest(report=name):
statement = await self.statement(name, query_type())
for field in ('production_date', 'expiry_date'):
self.assertIn(field, statement.selected_columns.keys())
self.assertEqual(f'{alias}.{field}', sql(statement.selected_columns[field].element))
if name != 'pallet_inventory':
self.assertNotIn('biz_pallet_inventory', sql(statement))
expected_join = {
'pallet_inventory': 'active_filling.filling_id = biz_pallet_inventory.active_filling_id',
'consumption_report': 'biz_filling_record.filling_id = biz_usage_record.active_filling_id',
'return_report': 'return_filling.filling_id = biz_return_item.active_filling_id_snapshot',
}.get(name)
if expected_join:
self.assertIn(expected_join, sql(statement))
async def test_trace_resolves_original_filling_by_event_source_and_same_pallet(self):
statement = await self.statement('pallet_trace', PalletTraceReportQueryModel())
self.assertIn('production_date', statement.selected_columns.keys())
query = sql(statement)
for source, reference in (
('FILLING', 'biz_pallet_event.source_id'),
('USAGE', 'trace_usage.active_filling_id'),
('RETURN', 'trace_return_item.active_filling_id_snapshot'),
('SHIPMENT', 'trace_shipment_item.active_filling_id'),
):
self.assertIn(f"WHEN (biz_pallet_event.source_type = '{source}') THEN {reference}", query)
for alias, id_field in (('trace_usage', 'usage_id'), ('trace_shipment_item', 'shipment_id')):
self.assertIn(f'{alias}.{id_field} = biz_pallet_event.source_id', query)
self.assertIn(f'{alias}.pallet_id = biz_pallet_event.pallet_id', query)
self.assertIn('trace_filling.pallet_id = biz_pallet_event.pallet_id', query)
self.assertNotIn('biz_pallet_inventory', query)
async def test_tunnel_entry_resolves_prior_filling_event_not_later_refill(self):
statement = await self.statement('pallet_trace', PalletTraceReportQueryModel())
query = sql(statement)
self.assertIn('SELECT prior_filling_event.source_id', query)
self.assertIn('prior_filling_event.pallet_id = biz_pallet_event.pallet_id', query)
self.assertIn('prior_filling_event.event_seq < biz_pallet_event.event_seq', query)
self.assertIn("prior_filling_event.event_type = 'FILLING_CONFIRMED'", query)
self.assertIn('ORDER BY prior_filling_event.event_seq DESC', query)
self.assertIn('LIMIT 1', query)
async def test_trace_date_joins_are_optional_and_batch_isolated(self):
for batch_id, predicate in ((None, 'IS NULL'), (41, '= 41')):
with self.subTest(batch_id=batch_id):
statement = await self.statement('pallet_trace', PalletTraceReportQueryModel(), batch_id)
all_joins = {join.right.name: join for join in joins(statement)}
for alias, key in (
('trace_usage', 'usage_id'), ('trace_shipment_item', 'shipment_item_id'),
('trace_filling', 'filling_id'),
):
self.assertIn(alias, all_joins)
self.assertTrue(all_joins[alias].isouter)
self.assertIn(f'({alias}.{key} IS NULL OR {alias}.test_batch_id {predicate})', sql(statement))
self.assertIn(f'prior_filling_event.test_batch_id {predicate}', sql(statement))
async def test_response_preserves_original_date_and_null_as_iso_calendar_dates(self):
for name, model_type, query_type, _ in REPORTS:
for production, expiry, expected in (
(date(2025, 12, 31), date(2026, 12, 31), ('2025-12-31', '2026-12-31')),
(None, None, (None, None)),
):
with self.subTest(report=name, production=production):
row = dict(report_row(model_type), production_date=production, expiry_date=expiry)
page = PageModel(rows=[row], total=1, pageNum=1, pageSize=10, hasNext=False)
with patch.object(ReportDao, f'get_{name}_list', AsyncMock(return_value=page)):
result = await getattr(ReportService, f'get_{name}_list_services')(
None, query_type(), make_user(1), TestBatchScope.formal(),
)
actual = result.rows[0].model_dump(mode='json', by_alias=True)
self.assertIn('productionDate', actual)
self.assertIn('expiryDate', actual)
self.assertEqual(expected, (actual['productionDate'], actual['expiryDate']))
if __name__ == '__main__':
unittest.main()