1537 lines
54 KiB
Python
1537 lines
54 KiB
Python
import unittest
|
||
|
||
from sqlalchemy import JSON, CHAR, BigInteger, CheckConstraint, Date, DateTime, Integer, String, Table, Text, UniqueConstraint
|
||
from sqlalchemy.dialects import mysql
|
||
from sqlalchemy.schema import CreateColumn, CreateTable
|
||
|
||
from config.database import Base
|
||
from module_admin.entity.do import user_do
|
||
from module_business.entity.do import master_do, operation_do, test_batch_do
|
||
|
||
EXPECTED_TEST_BATCH_TABLES = {'biz_test_batch', 'biz_test_batch_user'}
|
||
TEST_BATCH_OWNED_TABLES = {
|
||
'biz_strain',
|
||
'biz_customer',
|
||
'biz_customer_site',
|
||
'biz_production_line',
|
||
'biz_tunnel',
|
||
'biz_warehouse',
|
||
'biz_pallet',
|
||
'biz_inbound_order',
|
||
'biz_inbound_item',
|
||
'biz_filling_record',
|
||
'biz_shipment_order',
|
||
'biz_shipment_item',
|
||
'biz_usage_record',
|
||
'biz_return_order',
|
||
'biz_return_item',
|
||
'biz_pallet_inventory',
|
||
'biz_pallet_event',
|
||
}
|
||
|
||
EXPECTED_COLUMNS = {
|
||
'biz_strain': {
|
||
'strain_id',
|
||
'strain_code',
|
||
'strain_name',
|
||
'status',
|
||
'create_by',
|
||
'create_time',
|
||
'update_by',
|
||
'update_time',
|
||
'remark',
|
||
},
|
||
'biz_warehouse': {
|
||
'warehouse_id',
|
||
'warehouse_code',
|
||
'warehouse_name',
|
||
'address',
|
||
'status',
|
||
'create_by',
|
||
'create_time',
|
||
'update_by',
|
||
'update_time',
|
||
'remark',
|
||
},
|
||
'biz_customer': {
|
||
'customer_id',
|
||
'customer_code',
|
||
'customer_name',
|
||
'status',
|
||
'create_by',
|
||
'create_time',
|
||
'update_by',
|
||
'update_time',
|
||
'remark',
|
||
},
|
||
'biz_customer_site': {
|
||
'site_id',
|
||
'customer_id',
|
||
'site_code',
|
||
'site_name',
|
||
'address',
|
||
'status',
|
||
'create_by',
|
||
'create_time',
|
||
'update_by',
|
||
'update_time',
|
||
'remark',
|
||
},
|
||
'biz_production_line': {
|
||
'line_id',
|
||
'site_id',
|
||
'line_code',
|
||
'line_name',
|
||
'status',
|
||
'create_by',
|
||
'create_time',
|
||
'update_by',
|
||
'update_time',
|
||
'remark',
|
||
},
|
||
'biz_tunnel': {
|
||
'tunnel_id',
|
||
'site_id',
|
||
'tunnel_code',
|
||
'tunnel_name',
|
||
'current_trace_no',
|
||
'current_trace_started_at',
|
||
'status',
|
||
'create_by',
|
||
'create_time',
|
||
'update_by',
|
||
'update_time',
|
||
'remark',
|
||
},
|
||
'biz_pallet': {
|
||
'pallet_id',
|
||
'pallet_code',
|
||
'qr_token',
|
||
'capacity_packages',
|
||
'asset_status',
|
||
'create_by',
|
||
'create_time',
|
||
'update_by',
|
||
'update_time',
|
||
'remark',
|
||
},
|
||
'biz_user_warehouse': {'user_id', 'warehouse_id', 'create_by', 'create_time'},
|
||
'biz_user_site': {'user_id', 'site_id', 'create_by', 'create_time'},
|
||
'biz_inbound_order': {
|
||
'inbound_id',
|
||
'inbound_no',
|
||
'warehouse_id',
|
||
'status',
|
||
'confirm_idempotency_key',
|
||
'confirm_request_hash',
|
||
'confirmed_by',
|
||
'confirmed_at',
|
||
'create_by',
|
||
'create_time',
|
||
'update_by',
|
||
'update_time',
|
||
'remark',
|
||
},
|
||
'biz_inbound_item': {
|
||
'inbound_item_id',
|
||
'inbound_id',
|
||
'pallet_id',
|
||
'create_by',
|
||
'create_time',
|
||
},
|
||
'biz_filling_record': {
|
||
'filling_id',
|
||
'filling_no',
|
||
'pallet_id',
|
||
'warehouse_id',
|
||
'strain_id',
|
||
'strain_code_snapshot',
|
||
'strain_name_snapshot',
|
||
'batch_no',
|
||
'production_date',
|
||
'expiry_date',
|
||
'package_spec',
|
||
'initial_packages',
|
||
'active_slot',
|
||
'closed_at',
|
||
'idempotency_key',
|
||
'request_hash',
|
||
'operator_id',
|
||
'operated_at',
|
||
'create_by',
|
||
'create_time',
|
||
'update_by',
|
||
'update_time',
|
||
'remark',
|
||
},
|
||
'biz_shipment_order': {
|
||
'shipment_id',
|
||
'shipment_no',
|
||
'source_warehouse_id',
|
||
'target_site_id',
|
||
'status',
|
||
'confirm_idempotency_key',
|
||
'confirm_request_hash',
|
||
'confirmed_by',
|
||
'confirmed_at',
|
||
'create_by',
|
||
'create_time',
|
||
'update_by',
|
||
'update_time',
|
||
'remark',
|
||
},
|
||
'biz_shipment_item': {
|
||
'shipment_item_id',
|
||
'shipment_id',
|
||
'pallet_id',
|
||
'active_filling_id',
|
||
'package_qty',
|
||
'create_by',
|
||
'create_time',
|
||
},
|
||
'biz_usage_record': {
|
||
'usage_id',
|
||
'usage_no',
|
||
'pallet_id',
|
||
'active_filling_id',
|
||
'site_id',
|
||
'tunnel_id',
|
||
'trace_no',
|
||
'batch_no_snapshot',
|
||
'line_id',
|
||
'used_packages',
|
||
'remaining_packages_after',
|
||
'idempotency_key',
|
||
'request_hash',
|
||
'operator_id',
|
||
'occurred_at',
|
||
},
|
||
'biz_return_order': {
|
||
'return_id',
|
||
'return_no',
|
||
'source_site_id',
|
||
'target_warehouse_id',
|
||
'status',
|
||
'request_idempotency_key',
|
||
'request_hash',
|
||
'confirm_idempotency_key',
|
||
'confirm_request_hash',
|
||
'requested_by',
|
||
'requested_at',
|
||
'confirmed_by',
|
||
'confirmed_at',
|
||
'create_by',
|
||
'create_time',
|
||
'update_by',
|
||
'update_time',
|
||
'remark',
|
||
},
|
||
'biz_return_item': {
|
||
'return_item_id',
|
||
'return_id',
|
||
'pallet_id',
|
||
'active_filling_id_snapshot',
|
||
'tunnel_id_snapshot',
|
||
'trace_no_snapshot',
|
||
'batch_no_snapshot',
|
||
'remaining_packages_snapshot',
|
||
'create_by',
|
||
'create_time',
|
||
},
|
||
'biz_pallet_inventory': {
|
||
'pallet_id',
|
||
'active_filling_id',
|
||
'warehouse_id',
|
||
'site_id',
|
||
'tunnel_id',
|
||
'trace_no',
|
||
'current_line_id',
|
||
'flow_state',
|
||
'remaining_packages',
|
||
'last_event_id',
|
||
'version',
|
||
'updated_at',
|
||
},
|
||
'biz_pallet_event': {
|
||
'event_id',
|
||
'pallet_id',
|
||
'event_seq',
|
||
'event_type',
|
||
'source_type',
|
||
'source_id',
|
||
'before_state',
|
||
'after_state',
|
||
'before_warehouse_id',
|
||
'before_site_id',
|
||
'before_tunnel_id',
|
||
'before_line_id',
|
||
'after_warehouse_id',
|
||
'after_site_id',
|
||
'after_tunnel_id',
|
||
'after_line_id',
|
||
'before_remaining_packages',
|
||
'after_remaining_packages',
|
||
'trace_no',
|
||
'batch_no_snapshot',
|
||
'operator_id',
|
||
'occurred_at',
|
||
'idempotency_key',
|
||
'request_hash',
|
||
},
|
||
}
|
||
|
||
EXPECTED_COLUMN_TYPES = {
|
||
'biz_strain': {
|
||
'strain_id': (BigInteger, None),
|
||
'strain_code': (String, 64),
|
||
'strain_name': (String, 100),
|
||
'status': (CHAR, 1),
|
||
'create_by': (String, 64),
|
||
'create_time': (DateTime, None),
|
||
'update_by': (String, 64),
|
||
'update_time': (DateTime, None),
|
||
'remark': (String, 500),
|
||
},
|
||
'biz_tunnel': {
|
||
'tunnel_id': (BigInteger, None),
|
||
'site_id': (BigInteger, None),
|
||
'tunnel_code': (String, 64),
|
||
'tunnel_name': (String, 100),
|
||
'current_trace_no': (String, 64),
|
||
'current_trace_started_at': (DateTime, None),
|
||
'status': (CHAR, 1),
|
||
'create_by': (String, 64),
|
||
'create_time': (DateTime, None),
|
||
'update_by': (String, 64),
|
||
'update_time': (DateTime, None),
|
||
'remark': (String, 500),
|
||
},
|
||
'biz_warehouse': {
|
||
'warehouse_id': (BigInteger, None),
|
||
'warehouse_code': (String, 64),
|
||
'warehouse_name': (String, 100),
|
||
'address': (String, 500),
|
||
'status': (CHAR, 1),
|
||
'create_by': (String, 64),
|
||
'create_time': (DateTime, None),
|
||
'update_by': (String, 64),
|
||
'update_time': (DateTime, None),
|
||
'remark': (String, 500),
|
||
},
|
||
'biz_customer': {
|
||
'customer_id': (BigInteger, None),
|
||
'customer_code': (String, 64),
|
||
'customer_name': (String, 100),
|
||
'status': (CHAR, 1),
|
||
'create_by': (String, 64),
|
||
'create_time': (DateTime, None),
|
||
'update_by': (String, 64),
|
||
'update_time': (DateTime, None),
|
||
'remark': (String, 500),
|
||
},
|
||
'biz_customer_site': {
|
||
'site_id': (BigInteger, None),
|
||
'customer_id': (BigInteger, None),
|
||
'site_code': (String, 64),
|
||
'site_name': (String, 100),
|
||
'address': (String, 500),
|
||
'status': (CHAR, 1),
|
||
'create_by': (String, 64),
|
||
'create_time': (DateTime, None),
|
||
'update_by': (String, 64),
|
||
'update_time': (DateTime, None),
|
||
'remark': (String, 500),
|
||
},
|
||
'biz_production_line': {
|
||
'line_id': (BigInteger, None),
|
||
'site_id': (BigInteger, None),
|
||
'line_code': (String, 64),
|
||
'line_name': (String, 100),
|
||
'status': (CHAR, 1),
|
||
'create_by': (String, 64),
|
||
'create_time': (DateTime, None),
|
||
'update_by': (String, 64),
|
||
'update_time': (DateTime, None),
|
||
'remark': (String, 500),
|
||
},
|
||
'biz_pallet': {
|
||
'pallet_id': (BigInteger, None),
|
||
'pallet_code': (String, 64),
|
||
'qr_token': (String, 128),
|
||
'capacity_packages': (Integer, None),
|
||
'asset_status': (CHAR, 1),
|
||
'create_by': (String, 64),
|
||
'create_time': (DateTime, None),
|
||
'update_by': (String, 64),
|
||
'update_time': (DateTime, None),
|
||
'remark': (String, 500),
|
||
},
|
||
'biz_user_warehouse': {
|
||
'user_id': (BigInteger, None),
|
||
'warehouse_id': (BigInteger, None),
|
||
'create_by': (String, 64),
|
||
'create_time': (DateTime, None),
|
||
},
|
||
'biz_user_site': {
|
||
'user_id': (BigInteger, None),
|
||
'site_id': (BigInteger, None),
|
||
'create_by': (String, 64),
|
||
'create_time': (DateTime, None),
|
||
},
|
||
'biz_inbound_order': {
|
||
'inbound_id': (BigInteger, None),
|
||
'inbound_no': (String, 64),
|
||
'warehouse_id': (BigInteger, None),
|
||
'status': (String, 32),
|
||
'confirm_idempotency_key': (String, 64),
|
||
'confirm_request_hash': (String, 64),
|
||
'confirmed_by': (BigInteger, None),
|
||
'confirmed_at': (DateTime, None),
|
||
'create_by': (String, 64),
|
||
'create_time': (DateTime, None),
|
||
'update_by': (String, 64),
|
||
'update_time': (DateTime, None),
|
||
'remark': (String, 500),
|
||
},
|
||
'biz_inbound_item': {
|
||
'inbound_item_id': (BigInteger, None),
|
||
'inbound_id': (BigInteger, None),
|
||
'pallet_id': (BigInteger, None),
|
||
'create_by': (String, 64),
|
||
'create_time': (DateTime, None),
|
||
},
|
||
'biz_filling_record': {
|
||
'filling_id': (BigInteger, None),
|
||
'filling_no': (String, 64),
|
||
'pallet_id': (BigInteger, None),
|
||
'warehouse_id': (BigInteger, None),
|
||
'strain_id': (BigInteger, None),
|
||
'strain_code_snapshot': (String, 64),
|
||
'strain_name_snapshot': (String, 100),
|
||
'batch_no': (String, 64),
|
||
'production_date': (Date, None),
|
||
'expiry_date': (Date, None),
|
||
'package_spec': (String, 64),
|
||
'initial_packages': (Integer, None),
|
||
'active_slot': (String, 16),
|
||
'closed_at': (DateTime, None),
|
||
'idempotency_key': (String, 64),
|
||
'request_hash': (String, 64),
|
||
'operator_id': (BigInteger, None),
|
||
'operated_at': (DateTime, None),
|
||
'create_by': (String, 64),
|
||
'create_time': (DateTime, None),
|
||
'update_by': (String, 64),
|
||
'update_time': (DateTime, None),
|
||
'remark': (String, 500),
|
||
},
|
||
'biz_shipment_order': {
|
||
'shipment_id': (BigInteger, None),
|
||
'shipment_no': (String, 64),
|
||
'source_warehouse_id': (BigInteger, None),
|
||
'target_site_id': (BigInteger, None),
|
||
'status': (String, 32),
|
||
'confirm_idempotency_key': (String, 64),
|
||
'confirm_request_hash': (String, 64),
|
||
'confirmed_by': (BigInteger, None),
|
||
'confirmed_at': (DateTime, None),
|
||
'create_by': (String, 64),
|
||
'create_time': (DateTime, None),
|
||
'update_by': (String, 64),
|
||
'update_time': (DateTime, None),
|
||
'remark': (String, 500),
|
||
},
|
||
'biz_shipment_item': {
|
||
'shipment_item_id': (BigInteger, None),
|
||
'shipment_id': (BigInteger, None),
|
||
'pallet_id': (BigInteger, None),
|
||
'active_filling_id': (BigInteger, None),
|
||
'package_qty': (Integer, None),
|
||
'create_by': (String, 64),
|
||
'create_time': (DateTime, None),
|
||
},
|
||
'biz_usage_record': {
|
||
'usage_id': (BigInteger, None),
|
||
'usage_no': (String, 64),
|
||
'pallet_id': (BigInteger, None),
|
||
'active_filling_id': (BigInteger, None),
|
||
'site_id': (BigInteger, None),
|
||
'tunnel_id': (BigInteger, None),
|
||
'trace_no': (String, 64),
|
||
'batch_no_snapshot': (String, 64),
|
||
'line_id': (BigInteger, None),
|
||
'used_packages': (Integer, None),
|
||
'remaining_packages_after': (Integer, None),
|
||
'idempotency_key': (String, 64),
|
||
'request_hash': (String, 64),
|
||
'operator_id': (BigInteger, None),
|
||
'occurred_at': (DateTime, None),
|
||
},
|
||
'biz_return_order': {
|
||
'return_id': (BigInteger, None),
|
||
'return_no': (String, 64),
|
||
'source_site_id': (BigInteger, None),
|
||
'target_warehouse_id': (BigInteger, None),
|
||
'status': (String, 32),
|
||
'request_idempotency_key': (String, 64),
|
||
'request_hash': (String, 64),
|
||
'confirm_idempotency_key': (String, 64),
|
||
'confirm_request_hash': (String, 64),
|
||
'requested_by': (BigInteger, None),
|
||
'requested_at': (DateTime, None),
|
||
'confirmed_by': (BigInteger, None),
|
||
'confirmed_at': (DateTime, None),
|
||
'create_by': (String, 64),
|
||
'create_time': (DateTime, None),
|
||
'update_by': (String, 64),
|
||
'update_time': (DateTime, None),
|
||
'remark': (String, 500),
|
||
},
|
||
'biz_return_item': {
|
||
'return_item_id': (BigInteger, None),
|
||
'return_id': (BigInteger, None),
|
||
'pallet_id': (BigInteger, None),
|
||
'active_filling_id_snapshot': (BigInteger, None),
|
||
'tunnel_id_snapshot': (BigInteger, None),
|
||
'trace_no_snapshot': (String, 64),
|
||
'batch_no_snapshot': (String, 64),
|
||
'remaining_packages_snapshot': (Integer, None),
|
||
'create_by': (String, 64),
|
||
'create_time': (DateTime, None),
|
||
},
|
||
'biz_pallet_inventory': {
|
||
'pallet_id': (BigInteger, None),
|
||
'active_filling_id': (BigInteger, None),
|
||
'warehouse_id': (BigInteger, None),
|
||
'site_id': (BigInteger, None),
|
||
'tunnel_id': (BigInteger, None),
|
||
'trace_no': (String, 64),
|
||
'current_line_id': (BigInteger, None),
|
||
'flow_state': (String, 32),
|
||
'remaining_packages': (Integer, None),
|
||
'last_event_id': (BigInteger, None),
|
||
'version': (Integer, None),
|
||
'updated_at': (DateTime, None),
|
||
},
|
||
'biz_pallet_event': {
|
||
'event_id': (BigInteger, None),
|
||
'pallet_id': (BigInteger, None),
|
||
'event_seq': (Integer, None),
|
||
'event_type': (String, 32),
|
||
'source_type': (String, 32),
|
||
'source_id': (BigInteger, None),
|
||
'before_state': (String, 32),
|
||
'after_state': (String, 32),
|
||
'before_warehouse_id': (BigInteger, None),
|
||
'before_site_id': (BigInteger, None),
|
||
'before_tunnel_id': (BigInteger, None),
|
||
'before_line_id': (BigInteger, None),
|
||
'after_warehouse_id': (BigInteger, None),
|
||
'after_site_id': (BigInteger, None),
|
||
'after_tunnel_id': (BigInteger, None),
|
||
'after_line_id': (BigInteger, None),
|
||
'before_remaining_packages': (Integer, None),
|
||
'after_remaining_packages': (Integer, None),
|
||
'trace_no': (String, 64),
|
||
'batch_no_snapshot': (String, 64),
|
||
'operator_id': (BigInteger, None),
|
||
'occurred_at': (DateTime, None),
|
||
'idempotency_key': (String, 64),
|
||
'request_hash': (String, 64),
|
||
},
|
||
}
|
||
|
||
EXPECTED_NULLABILITY = {
|
||
'biz_strain': {
|
||
'strain_id': False,
|
||
'strain_code': False,
|
||
'strain_name': False,
|
||
'status': False,
|
||
'create_by': True,
|
||
'create_time': True,
|
||
'update_by': True,
|
||
'update_time': True,
|
||
'remark': True,
|
||
},
|
||
'biz_tunnel': {
|
||
'tunnel_id': False,
|
||
'site_id': False,
|
||
'tunnel_code': False,
|
||
'tunnel_name': False,
|
||
'current_trace_no': True,
|
||
'current_trace_started_at': True,
|
||
'status': False,
|
||
'create_by': True,
|
||
'create_time': True,
|
||
'update_by': True,
|
||
'update_time': True,
|
||
'remark': True,
|
||
},
|
||
'biz_warehouse': {
|
||
'warehouse_id': False,
|
||
'warehouse_code': False,
|
||
'warehouse_name': False,
|
||
'address': True,
|
||
'status': False,
|
||
'create_by': True,
|
||
'create_time': True,
|
||
'update_by': True,
|
||
'update_time': True,
|
||
'remark': True,
|
||
},
|
||
'biz_customer': {
|
||
'customer_id': False,
|
||
'customer_code': False,
|
||
'customer_name': False,
|
||
'status': False,
|
||
'create_by': True,
|
||
'create_time': True,
|
||
'update_by': True,
|
||
'update_time': True,
|
||
'remark': True,
|
||
},
|
||
'biz_customer_site': {
|
||
'site_id': False,
|
||
'customer_id': False,
|
||
'site_code': False,
|
||
'site_name': False,
|
||
'address': True,
|
||
'status': False,
|
||
'create_by': True,
|
||
'create_time': True,
|
||
'update_by': True,
|
||
'update_time': True,
|
||
'remark': True,
|
||
},
|
||
'biz_production_line': {
|
||
'line_id': False,
|
||
'site_id': False,
|
||
'line_code': False,
|
||
'line_name': False,
|
||
'status': False,
|
||
'create_by': True,
|
||
'create_time': True,
|
||
'update_by': True,
|
||
'update_time': True,
|
||
'remark': True,
|
||
},
|
||
'biz_pallet': {
|
||
'pallet_id': False,
|
||
'pallet_code': False,
|
||
'qr_token': False,
|
||
'capacity_packages': False,
|
||
'asset_status': False,
|
||
'create_by': True,
|
||
'create_time': True,
|
||
'update_by': True,
|
||
'update_time': True,
|
||
'remark': True,
|
||
},
|
||
'biz_user_warehouse': {
|
||
'user_id': False,
|
||
'warehouse_id': False,
|
||
'create_by': True,
|
||
'create_time': True,
|
||
},
|
||
'biz_user_site': {
|
||
'user_id': False,
|
||
'site_id': False,
|
||
'create_by': True,
|
||
'create_time': True,
|
||
},
|
||
'biz_inbound_order': {
|
||
'inbound_id': False,
|
||
'inbound_no': False,
|
||
'warehouse_id': False,
|
||
'status': False,
|
||
'confirm_idempotency_key': True,
|
||
'confirm_request_hash': True,
|
||
'confirmed_by': True,
|
||
'confirmed_at': True,
|
||
'create_by': True,
|
||
'create_time': True,
|
||
'update_by': True,
|
||
'update_time': True,
|
||
'remark': True,
|
||
},
|
||
'biz_inbound_item': {
|
||
'inbound_item_id': False,
|
||
'inbound_id': False,
|
||
'pallet_id': False,
|
||
'create_by': True,
|
||
'create_time': True,
|
||
},
|
||
'biz_filling_record': {
|
||
'filling_id': False,
|
||
'filling_no': False,
|
||
'pallet_id': False,
|
||
'warehouse_id': False,
|
||
'strain_id': False,
|
||
'strain_code_snapshot': False,
|
||
'strain_name_snapshot': False,
|
||
'batch_no': False,
|
||
'production_date': True,
|
||
'expiry_date': True,
|
||
'package_spec': False,
|
||
'initial_packages': False,
|
||
'active_slot': True,
|
||
'closed_at': True,
|
||
'idempotency_key': False,
|
||
'request_hash': False,
|
||
'operator_id': False,
|
||
'operated_at': False,
|
||
'create_by': True,
|
||
'create_time': True,
|
||
'update_by': True,
|
||
'update_time': True,
|
||
'remark': True,
|
||
},
|
||
'biz_shipment_order': {
|
||
'shipment_id': False,
|
||
'shipment_no': False,
|
||
'source_warehouse_id': False,
|
||
'target_site_id': False,
|
||
'status': False,
|
||
'confirm_idempotency_key': True,
|
||
'confirm_request_hash': True,
|
||
'confirmed_by': True,
|
||
'confirmed_at': True,
|
||
'create_by': True,
|
||
'create_time': True,
|
||
'update_by': True,
|
||
'update_time': True,
|
||
'remark': True,
|
||
},
|
||
'biz_shipment_item': {
|
||
'shipment_item_id': False,
|
||
'shipment_id': False,
|
||
'pallet_id': False,
|
||
'active_filling_id': True,
|
||
'package_qty': True,
|
||
'create_by': True,
|
||
'create_time': True,
|
||
},
|
||
'biz_usage_record': {
|
||
'usage_id': False,
|
||
'usage_no': False,
|
||
'pallet_id': False,
|
||
'active_filling_id': False,
|
||
'site_id': False,
|
||
'tunnel_id': True,
|
||
'trace_no': True,
|
||
'batch_no_snapshot': True,
|
||
'line_id': True,
|
||
'used_packages': False,
|
||
'remaining_packages_after': False,
|
||
'idempotency_key': False,
|
||
'request_hash': False,
|
||
'operator_id': False,
|
||
'occurred_at': False,
|
||
},
|
||
'biz_return_order': {
|
||
'return_id': False,
|
||
'return_no': False,
|
||
'source_site_id': False,
|
||
'target_warehouse_id': False,
|
||
'status': False,
|
||
'request_idempotency_key': False,
|
||
'request_hash': False,
|
||
'confirm_idempotency_key': True,
|
||
'confirm_request_hash': True,
|
||
'requested_by': False,
|
||
'requested_at': False,
|
||
'confirmed_by': True,
|
||
'confirmed_at': True,
|
||
'create_by': True,
|
||
'create_time': True,
|
||
'update_by': True,
|
||
'update_time': True,
|
||
'remark': True,
|
||
},
|
||
'biz_return_item': {
|
||
'return_item_id': False,
|
||
'return_id': False,
|
||
'pallet_id': False,
|
||
'active_filling_id_snapshot': True,
|
||
'tunnel_id_snapshot': True,
|
||
'trace_no_snapshot': True,
|
||
'batch_no_snapshot': True,
|
||
'remaining_packages_snapshot': False,
|
||
'create_by': True,
|
||
'create_time': True,
|
||
},
|
||
'biz_pallet_inventory': {
|
||
'pallet_id': False,
|
||
'active_filling_id': True,
|
||
'warehouse_id': True,
|
||
'site_id': True,
|
||
'tunnel_id': True,
|
||
'trace_no': True,
|
||
'current_line_id': True,
|
||
'flow_state': False,
|
||
'remaining_packages': False,
|
||
'last_event_id': True,
|
||
'version': False,
|
||
'updated_at': False,
|
||
},
|
||
'biz_pallet_event': {
|
||
'event_id': False,
|
||
'pallet_id': False,
|
||
'event_seq': False,
|
||
'event_type': False,
|
||
'source_type': False,
|
||
'source_id': False,
|
||
'before_state': False,
|
||
'after_state': False,
|
||
'before_warehouse_id': True,
|
||
'before_site_id': True,
|
||
'before_tunnel_id': True,
|
||
'before_line_id': True,
|
||
'after_warehouse_id': True,
|
||
'after_site_id': True,
|
||
'after_tunnel_id': True,
|
||
'after_line_id': True,
|
||
'before_remaining_packages': False,
|
||
'after_remaining_packages': False,
|
||
'trace_no': True,
|
||
'batch_no_snapshot': True,
|
||
'operator_id': False,
|
||
'occurred_at': False,
|
||
'idempotency_key': False,
|
||
'request_hash': False,
|
||
},
|
||
}
|
||
|
||
EXPECTED_CALLABLE_DATETIME_DEFAULTS = {
|
||
('biz_strain', 'create_time'),
|
||
('biz_strain', 'update_time'),
|
||
('biz_warehouse', 'create_time'),
|
||
('biz_warehouse', 'update_time'),
|
||
('biz_customer', 'create_time'),
|
||
('biz_customer', 'update_time'),
|
||
('biz_customer_site', 'create_time'),
|
||
('biz_customer_site', 'update_time'),
|
||
('biz_production_line', 'create_time'),
|
||
('biz_production_line', 'update_time'),
|
||
('biz_tunnel', 'create_time'),
|
||
('biz_tunnel', 'update_time'),
|
||
('biz_pallet', 'create_time'),
|
||
('biz_pallet', 'update_time'),
|
||
('biz_user_warehouse', 'create_time'),
|
||
('biz_user_site', 'create_time'),
|
||
('biz_inbound_order', 'create_time'),
|
||
('biz_inbound_order', 'update_time'),
|
||
('biz_inbound_item', 'create_time'),
|
||
('biz_filling_record', 'create_time'),
|
||
('biz_filling_record', 'update_time'),
|
||
('biz_shipment_order', 'create_time'),
|
||
('biz_shipment_order', 'update_time'),
|
||
('biz_shipment_item', 'create_time'),
|
||
('biz_return_order', 'create_time'),
|
||
('biz_return_order', 'update_time'),
|
||
('biz_return_item', 'create_time'),
|
||
('biz_pallet_inventory', 'updated_at'),
|
||
}
|
||
|
||
EXPECTED_EMPTY_AUDIT_SERVER_DEFAULTS = {
|
||
('biz_strain', 'create_by'),
|
||
('biz_strain', 'update_by'),
|
||
('biz_warehouse', 'create_by'),
|
||
('biz_warehouse', 'update_by'),
|
||
('biz_customer', 'create_by'),
|
||
('biz_customer', 'update_by'),
|
||
('biz_customer_site', 'create_by'),
|
||
('biz_customer_site', 'update_by'),
|
||
('biz_production_line', 'create_by'),
|
||
('biz_production_line', 'update_by'),
|
||
('biz_tunnel', 'create_by'),
|
||
('biz_tunnel', 'update_by'),
|
||
('biz_pallet', 'create_by'),
|
||
('biz_pallet', 'update_by'),
|
||
('biz_user_warehouse', 'create_by'),
|
||
('biz_user_site', 'create_by'),
|
||
('biz_inbound_order', 'create_by'),
|
||
('biz_inbound_order', 'update_by'),
|
||
('biz_inbound_item', 'create_by'),
|
||
('biz_filling_record', 'create_by'),
|
||
('biz_filling_record', 'update_by'),
|
||
('biz_shipment_order', 'create_by'),
|
||
('biz_shipment_order', 'update_by'),
|
||
('biz_shipment_item', 'create_by'),
|
||
('biz_return_order', 'create_by'),
|
||
('biz_return_order', 'update_by'),
|
||
('biz_return_item', 'create_by'),
|
||
}
|
||
|
||
EXPECTED_PRIMARY_KEYS = {
|
||
'biz_strain': ('strain_id',),
|
||
'biz_warehouse': ('warehouse_id',),
|
||
'biz_customer': ('customer_id',),
|
||
'biz_customer_site': ('site_id',),
|
||
'biz_production_line': ('line_id',),
|
||
'biz_tunnel': ('tunnel_id',),
|
||
'biz_pallet': ('pallet_id',),
|
||
'biz_user_warehouse': ('user_id', 'warehouse_id'),
|
||
'biz_user_site': ('user_id', 'site_id'),
|
||
'biz_inbound_order': ('inbound_id',),
|
||
'biz_inbound_item': ('inbound_item_id',),
|
||
'biz_filling_record': ('filling_id',),
|
||
'biz_shipment_order': ('shipment_id',),
|
||
'biz_shipment_item': ('shipment_item_id',),
|
||
'biz_usage_record': ('usage_id',),
|
||
'biz_return_order': ('return_id',),
|
||
'biz_return_item': ('return_item_id',),
|
||
'biz_pallet_inventory': ('pallet_id',),
|
||
'biz_pallet_event': ('event_id',),
|
||
}
|
||
|
||
EXPECTED_FOREIGN_KEYS = {
|
||
'biz_strain': set(),
|
||
'biz_warehouse': set(),
|
||
'biz_customer': set(),
|
||
'biz_customer_site': {('customer_id', 'biz_customer.customer_id')},
|
||
'biz_production_line': {('site_id', 'biz_customer_site.site_id')},
|
||
'biz_tunnel': {
|
||
('site_id', 'biz_customer_site.site_id'),
|
||
},
|
||
'biz_pallet': set(),
|
||
'biz_user_warehouse': {
|
||
('user_id', 'sys_user.user_id'),
|
||
('warehouse_id', 'biz_warehouse.warehouse_id'),
|
||
},
|
||
'biz_user_site': {
|
||
('user_id', 'sys_user.user_id'),
|
||
('site_id', 'biz_customer_site.site_id'),
|
||
},
|
||
'biz_inbound_order': {
|
||
('warehouse_id', 'biz_warehouse.warehouse_id'),
|
||
('confirmed_by', 'sys_user.user_id'),
|
||
},
|
||
'biz_inbound_item': {
|
||
('inbound_id', 'biz_inbound_order.inbound_id'),
|
||
('pallet_id', 'biz_pallet.pallet_id'),
|
||
},
|
||
'biz_filling_record': {
|
||
('pallet_id', 'biz_pallet.pallet_id'),
|
||
('warehouse_id', 'biz_warehouse.warehouse_id'),
|
||
('strain_id', 'biz_strain.strain_id'),
|
||
('operator_id', 'sys_user.user_id'),
|
||
},
|
||
'biz_shipment_order': {
|
||
('source_warehouse_id', 'biz_warehouse.warehouse_id'),
|
||
('target_site_id', 'biz_customer_site.site_id'),
|
||
('confirmed_by', 'sys_user.user_id'),
|
||
},
|
||
'biz_shipment_item': {
|
||
('shipment_id', 'biz_shipment_order.shipment_id'),
|
||
('pallet_id', 'biz_pallet.pallet_id'),
|
||
('active_filling_id', 'biz_filling_record.filling_id'),
|
||
},
|
||
'biz_usage_record': {
|
||
('pallet_id', 'biz_pallet.pallet_id'),
|
||
('active_filling_id', 'biz_filling_record.filling_id'),
|
||
('site_id', 'biz_customer_site.site_id'),
|
||
('tunnel_id', 'biz_tunnel.tunnel_id'),
|
||
('line_id', 'biz_production_line.line_id'),
|
||
('operator_id', 'sys_user.user_id'),
|
||
},
|
||
'biz_return_order': {
|
||
('source_site_id', 'biz_customer_site.site_id'),
|
||
('target_warehouse_id', 'biz_warehouse.warehouse_id'),
|
||
('requested_by', 'sys_user.user_id'),
|
||
('confirmed_by', 'sys_user.user_id'),
|
||
},
|
||
'biz_return_item': {
|
||
('return_id', 'biz_return_order.return_id'),
|
||
('pallet_id', 'biz_pallet.pallet_id'),
|
||
('active_filling_id_snapshot', 'biz_filling_record.filling_id'),
|
||
('tunnel_id_snapshot', 'biz_tunnel.tunnel_id'),
|
||
},
|
||
'biz_pallet_inventory': {
|
||
('pallet_id', 'biz_pallet.pallet_id'),
|
||
('active_filling_id', 'biz_filling_record.filling_id'),
|
||
('warehouse_id', 'biz_warehouse.warehouse_id'),
|
||
('site_id', 'biz_customer_site.site_id'),
|
||
('tunnel_id', 'biz_tunnel.tunnel_id'),
|
||
('current_line_id', 'biz_production_line.line_id'),
|
||
('last_event_id', 'biz_pallet_event.event_id'),
|
||
},
|
||
'biz_pallet_event': {
|
||
('pallet_id', 'biz_pallet.pallet_id'),
|
||
('before_warehouse_id', 'biz_warehouse.warehouse_id'),
|
||
('before_site_id', 'biz_customer_site.site_id'),
|
||
('before_tunnel_id', 'biz_tunnel.tunnel_id'),
|
||
('before_line_id', 'biz_production_line.line_id'),
|
||
('after_warehouse_id', 'biz_warehouse.warehouse_id'),
|
||
('after_site_id', 'biz_customer_site.site_id'),
|
||
('after_tunnel_id', 'biz_tunnel.tunnel_id'),
|
||
('after_line_id', 'biz_production_line.line_id'),
|
||
('operator_id', 'sys_user.user_id'),
|
||
},
|
||
}
|
||
|
||
EXPECTED_UNIQUE_SETS = {
|
||
'biz_strain': {frozenset({'strain_code'})},
|
||
'biz_warehouse': {frozenset({'warehouse_code'})},
|
||
'biz_customer': {frozenset({'customer_code'})},
|
||
'biz_customer_site': {frozenset({'customer_id', 'site_code'})},
|
||
'biz_production_line': {frozenset({'site_id', 'line_code'})},
|
||
'biz_tunnel': {frozenset({'site_id', 'tunnel_code'})},
|
||
'biz_pallet': {frozenset({'pallet_code'}), frozenset({'qr_token'})},
|
||
'biz_user_warehouse': set(),
|
||
'biz_user_site': set(),
|
||
'biz_inbound_order': {
|
||
frozenset({'inbound_no'}),
|
||
frozenset({'warehouse_id', 'confirm_idempotency_key'}),
|
||
},
|
||
'biz_inbound_item': {frozenset({'inbound_id', 'pallet_id'})},
|
||
'biz_filling_record': {
|
||
frozenset({'filling_no'}),
|
||
frozenset({'warehouse_id', 'idempotency_key'}),
|
||
frozenset({'pallet_id', 'active_slot'}),
|
||
},
|
||
'biz_shipment_order': {
|
||
frozenset({'shipment_no'}),
|
||
frozenset({'source_warehouse_id', 'target_site_id', 'confirm_idempotency_key'}),
|
||
},
|
||
'biz_shipment_item': {frozenset({'shipment_id', 'pallet_id'})},
|
||
'biz_usage_record': {
|
||
frozenset({'usage_no'}),
|
||
frozenset({'site_id', 'idempotency_key'}),
|
||
},
|
||
'biz_return_order': {
|
||
frozenset({'return_no'}),
|
||
frozenset({'source_site_id', 'target_warehouse_id', 'request_idempotency_key'}),
|
||
frozenset({'source_site_id', 'target_warehouse_id', 'confirm_idempotency_key'}),
|
||
},
|
||
'biz_return_item': {frozenset({'return_id', 'pallet_id'})},
|
||
'biz_pallet_inventory': set(),
|
||
'biz_pallet_event': {
|
||
frozenset({'pallet_id', 'event_seq'}),
|
||
frozenset({'pallet_id', 'idempotency_key'}),
|
||
},
|
||
}
|
||
|
||
# 收货补齐:与既有日期、隧道契约并存,不用当前时间填充历史收货记录。
|
||
EXPECTED_COLUMNS['biz_shipment_order'].update({
|
||
'receipt_state', 'received_by', 'received_at', 'receipt_idempotency_key', 'receipt_request_hash',
|
||
})
|
||
EXPECTED_COLUMN_TYPES['biz_shipment_order'].update({
|
||
'receipt_state': (String, 16),
|
||
'received_by': (BigInteger, None),
|
||
'received_at': (DateTime, None),
|
||
'receipt_idempotency_key': (String, 64),
|
||
'receipt_request_hash': (String, 64),
|
||
})
|
||
EXPECTED_NULLABILITY['biz_shipment_order'].update({
|
||
'receipt_state': False,
|
||
'received_by': True,
|
||
'received_at': True,
|
||
'receipt_idempotency_key': True,
|
||
'receipt_request_hash': True,
|
||
})
|
||
EXPECTED_FOREIGN_KEYS['biz_shipment_order'].add(('received_by', 'sys_user.user_id'))
|
||
EXPECTED_UNIQUE_SETS['biz_shipment_order'].add(frozenset({'target_site_id', 'receipt_idempotency_key'}))
|
||
EXPECTED_COLUMNS['biz_pallet_inventory'].add('site_arrived_at')
|
||
EXPECTED_COLUMN_TYPES['biz_pallet_inventory']['site_arrived_at'] = (DateTime, None)
|
||
EXPECTED_NULLABILITY['biz_pallet_inventory']['site_arrived_at'] = True
|
||
|
||
for fifo_table in ('biz_usage_record', 'biz_pallet_event'):
|
||
EXPECTED_COLUMNS[fifo_table].update({'fifo_skip_reason', 'fifo_recommendation'})
|
||
EXPECTED_NULLABILITY[fifo_table].update({'fifo_skip_reason': True, 'fifo_recommendation': True})
|
||
EXPECTED_COLUMN_TYPES[fifo_table]['fifo_skip_reason'] = (String, 500)
|
||
EXPECTED_COLUMN_TYPES[fifo_table]['fifo_recommendation'] = (JSON, None)
|
||
|
||
for table_name in TEST_BATCH_OWNED_TABLES:
|
||
EXPECTED_COLUMNS[table_name].add('test_batch_id')
|
||
EXPECTED_COLUMN_TYPES[table_name]['test_batch_id'] = (BigInteger, None)
|
||
EXPECTED_NULLABILITY[table_name]['test_batch_id'] = True
|
||
EXPECTED_FOREIGN_KEYS[table_name].add(('test_batch_id', 'biz_test_batch.batch_id'))
|
||
|
||
EXPECTED_COLUMNS.update(
|
||
{
|
||
'biz_test_batch': {
|
||
'batch_id',
|
||
'batch_code',
|
||
'batch_name',
|
||
'status',
|
||
'create_by',
|
||
'create_time',
|
||
'update_by',
|
||
'update_time',
|
||
'closed_by',
|
||
'closed_at',
|
||
'purged_by',
|
||
'purged_at',
|
||
'purge_summary',
|
||
'preview_action',
|
||
'preview_token_hash',
|
||
'preview_scope_hash',
|
||
'preview_expires_at',
|
||
'remark',
|
||
},
|
||
'biz_test_batch_user': {'batch_id', 'user_id', 'create_by', 'create_time'},
|
||
}
|
||
)
|
||
EXPECTED_COLUMN_TYPES.update(
|
||
{
|
||
'biz_test_batch': {
|
||
'batch_id': (BigInteger, None),
|
||
'batch_code': (String, 64),
|
||
'batch_name': (String, 100),
|
||
'status': (String, 16),
|
||
'create_by': (String, 64),
|
||
'create_time': (DateTime, None),
|
||
'update_by': (String, 64),
|
||
'update_time': (DateTime, None),
|
||
'closed_by': (BigInteger, None),
|
||
'closed_at': (DateTime, None),
|
||
'purged_by': (BigInteger, None),
|
||
'purged_at': (DateTime, None),
|
||
'purge_summary': (Text, None),
|
||
'preview_action': (String, 16),
|
||
'preview_token_hash': (CHAR, 64),
|
||
'preview_scope_hash': (CHAR, 64),
|
||
'preview_expires_at': (DateTime, None),
|
||
'remark': (String, 500),
|
||
},
|
||
'biz_test_batch_user': {
|
||
'batch_id': (BigInteger, None),
|
||
'user_id': (BigInteger, None),
|
||
'create_by': (String, 64),
|
||
'create_time': (DateTime, None),
|
||
},
|
||
}
|
||
)
|
||
EXPECTED_NULLABILITY.update(
|
||
{
|
||
'biz_test_batch': {
|
||
'batch_id': False,
|
||
'batch_code': False,
|
||
'batch_name': False,
|
||
'status': False,
|
||
'create_by': False,
|
||
'create_time': False,
|
||
'update_by': False,
|
||
'update_time': False,
|
||
'closed_by': True,
|
||
'closed_at': True,
|
||
'purged_by': True,
|
||
'purged_at': True,
|
||
'purge_summary': True,
|
||
'preview_action': True,
|
||
'preview_token_hash': True,
|
||
'preview_scope_hash': True,
|
||
'preview_expires_at': True,
|
||
'remark': True,
|
||
},
|
||
'biz_test_batch_user': {
|
||
'batch_id': False,
|
||
'user_id': False,
|
||
'create_by': False,
|
||
'create_time': False,
|
||
},
|
||
}
|
||
)
|
||
EXPECTED_CALLABLE_DATETIME_DEFAULTS.update(
|
||
{
|
||
('biz_test_batch', 'create_time'),
|
||
('biz_test_batch', 'update_time'),
|
||
('biz_test_batch_user', 'create_time'),
|
||
}
|
||
)
|
||
EXPECTED_EMPTY_AUDIT_SERVER_DEFAULTS.update(
|
||
{
|
||
('biz_test_batch', 'create_by'),
|
||
('biz_test_batch', 'update_by'),
|
||
('biz_test_batch_user', 'create_by'),
|
||
}
|
||
)
|
||
EXPECTED_PRIMARY_KEYS.update(
|
||
{
|
||
'biz_test_batch': ('batch_id',),
|
||
'biz_test_batch_user': ('batch_id', 'user_id'),
|
||
}
|
||
)
|
||
EXPECTED_FOREIGN_KEYS.update(
|
||
{
|
||
'biz_test_batch': {
|
||
('closed_by', 'sys_user.user_id'),
|
||
('purged_by', 'sys_user.user_id'),
|
||
},
|
||
'biz_test_batch_user': {
|
||
('batch_id', 'biz_test_batch.batch_id'),
|
||
('user_id', 'sys_user.user_id'),
|
||
},
|
||
}
|
||
)
|
||
EXPECTED_UNIQUE_SETS.update(
|
||
{
|
||
'biz_test_batch': {frozenset({'batch_code'})},
|
||
'biz_test_batch_user': set(),
|
||
}
|
||
)
|
||
|
||
|
||
def _unique_column_sets(table: Table) -> set[frozenset[str]]:
|
||
return {
|
||
frozenset(column.name for column in constraint.columns)
|
||
for constraint in table.constraints
|
||
if isinstance(constraint, UniqueConstraint)
|
||
}
|
||
|
||
|
||
def _normalized_check_sql(table: Table) -> dict[str, str]:
|
||
return {
|
||
constraint.name: ''.join(str(constraint.sqltext).split())
|
||
for constraint in table.constraints
|
||
if isinstance(constraint, CheckConstraint) and constraint.name is not None
|
||
}
|
||
|
||
|
||
class BusinessModelContractTest(unittest.TestCase):
|
||
def test_batch_related_tables_compile_for_mysql(self) -> None:
|
||
self.assertIs(Base.metadata.tables['sys_user'], user_do.SysUser.__table__)
|
||
batch_tables = EXPECTED_TEST_BATCH_TABLES | TEST_BATCH_OWNED_TABLES
|
||
self.assertEqual(19, len(batch_tables))
|
||
for table_name in batch_tables:
|
||
for dialect_name, dialect in (('mysql', mysql.dialect()),):
|
||
with self.subTest(table_name=table_name, dialect=dialect_name):
|
||
compiled = str(CreateTable(Base.metadata.tables[table_name]).compile(dialect=dialect))
|
||
self.assertIn(table_name, compiled)
|
||
|
||
def test_exact_business_tables_are_registered(self) -> None:
|
||
expected = {
|
||
'biz_strain',
|
||
'biz_warehouse',
|
||
'biz_customer',
|
||
'biz_customer_site',
|
||
'biz_production_line',
|
||
'biz_tunnel',
|
||
'biz_pallet',
|
||
'biz_user_warehouse',
|
||
'biz_user_site',
|
||
'biz_inbound_order',
|
||
'biz_inbound_item',
|
||
'biz_filling_record',
|
||
'biz_shipment_order',
|
||
'biz_shipment_item',
|
||
'biz_usage_record',
|
||
'biz_return_order',
|
||
'biz_return_item',
|
||
'biz_pallet_inventory',
|
||
'biz_pallet_event',
|
||
} | EXPECTED_TEST_BATCH_TABLES
|
||
actual = {name for name in Base.metadata.tables if name.startswith('biz_')}
|
||
self.assertEqual(expected, actual)
|
||
|
||
def test_exact_model_classes_and_columns(self) -> None:
|
||
models = {
|
||
master_do.BizStrain: 'biz_strain',
|
||
master_do.BizWarehouse: 'biz_warehouse',
|
||
master_do.BizCustomer: 'biz_customer',
|
||
master_do.BizCustomerSite: 'biz_customer_site',
|
||
master_do.BizProductionLine: 'biz_production_line',
|
||
master_do.BizTunnel: 'biz_tunnel',
|
||
master_do.BizPallet: 'biz_pallet',
|
||
master_do.BizUserWarehouse: 'biz_user_warehouse',
|
||
master_do.BizUserSite: 'biz_user_site',
|
||
operation_do.BizInboundOrder: 'biz_inbound_order',
|
||
operation_do.BizInboundItem: 'biz_inbound_item',
|
||
operation_do.BizFillingRecord: 'biz_filling_record',
|
||
operation_do.BizShipmentOrder: 'biz_shipment_order',
|
||
operation_do.BizShipmentItem: 'biz_shipment_item',
|
||
operation_do.BizUsageRecord: 'biz_usage_record',
|
||
operation_do.BizReturnOrder: 'biz_return_order',
|
||
operation_do.BizReturnItem: 'biz_return_item',
|
||
operation_do.BizPalletInventory: 'biz_pallet_inventory',
|
||
operation_do.BizPalletEvent: 'biz_pallet_event',
|
||
test_batch_do.BizTestBatch: 'biz_test_batch',
|
||
test_batch_do.BizTestBatchUser: 'biz_test_batch_user',
|
||
}
|
||
self.assertEqual(set(EXPECTED_COLUMNS), set(models.values()))
|
||
for model, table_name in models.items():
|
||
with self.subTest(table_name=table_name):
|
||
self.assertEqual(table_name, model.__tablename__)
|
||
self.assertEqual(EXPECTED_COLUMNS[table_name], set(model.__table__.columns.keys()))
|
||
|
||
def test_every_column_type_and_string_length_matches_contract(self) -> None:
|
||
business_tables = {name for name in Base.metadata.tables if name.startswith('biz_')}
|
||
self.assertEqual(business_tables, set(EXPECTED_COLUMN_TYPES))
|
||
|
||
for table_name, expected_columns in EXPECTED_COLUMN_TYPES.items():
|
||
table = Base.metadata.tables[table_name]
|
||
with self.subTest(table_name=table_name, contract='all_type_columns'):
|
||
self.assertEqual(set(expected_columns), set(table.columns.keys()))
|
||
|
||
for column_name, (expected_type, expected_length) in expected_columns.items():
|
||
column_type = table.c[column_name].type
|
||
with self.subTest(table_name=table_name, column_name=column_name):
|
||
self.assertIs(expected_type, type(column_type))
|
||
self.assertEqual(expected_length, getattr(column_type, 'length', None))
|
||
|
||
def test_every_column_nullable_flag_matches_contract(self) -> None:
|
||
business_tables = {name for name in Base.metadata.tables if name.startswith('biz_')}
|
||
self.assertEqual(business_tables, set(EXPECTED_NULLABILITY))
|
||
|
||
for table_name, expected_columns in EXPECTED_NULLABILITY.items():
|
||
table = Base.metadata.tables[table_name]
|
||
with self.subTest(table_name=table_name, contract='all_nullable_columns'):
|
||
self.assertEqual(set(expected_columns), set(table.columns.keys()))
|
||
|
||
for column_name, expected_nullable in expected_columns.items():
|
||
with self.subTest(table_name=table_name, column_name=column_name):
|
||
self.assertIs(expected_nullable, table.c[column_name].nullable)
|
||
|
||
def test_datetime_python_defaults_are_callable(self) -> None:
|
||
business_tables = [table for table in Base.metadata.tables.values() if table.name.startswith('biz_')]
|
||
actual_defaults = {
|
||
(table.name, column.name)
|
||
for table in business_tables
|
||
for column in table.columns
|
||
if type(column.type) is DateTime and column.default is not None
|
||
}
|
||
self.assertEqual(EXPECTED_CALLABLE_DATETIME_DEFAULTS, actual_defaults)
|
||
|
||
violations = []
|
||
for table_name, column_name in sorted(EXPECTED_CALLABLE_DATETIME_DEFAULTS):
|
||
column_default = Base.metadata.tables[table_name].c[column_name].default
|
||
if column_default is None or not column_default.is_callable or column_default.is_scalar:
|
||
violations.append(
|
||
(
|
||
table_name,
|
||
column_name,
|
||
None if column_default is None else column_default.is_callable,
|
||
None if column_default is None else column_default.is_scalar,
|
||
)
|
||
)
|
||
self.assertEqual([], violations)
|
||
|
||
def test_audit_user_server_defaults_compile_to_empty_strings(self) -> None:
|
||
business_tables = [table for table in Base.metadata.tables.values() if table.name.startswith('biz_')]
|
||
actual_audit_columns = {
|
||
(table.name, column.name)
|
||
for table in business_tables
|
||
for column in table.columns
|
||
if column.name in {'create_by', 'update_by'}
|
||
}
|
||
self.assertEqual(EXPECTED_EMPTY_AUDIT_SERVER_DEFAULTS, actual_audit_columns)
|
||
|
||
violations = []
|
||
dialects = {'mysql': mysql.dialect()}
|
||
for table_name, column_name in sorted(EXPECTED_EMPTY_AUDIT_SERVER_DEFAULTS):
|
||
column = Base.metadata.tables[table_name].c[column_name]
|
||
for dialect_name, dialect in dialects.items():
|
||
compiled_column = str(CreateColumn(column).compile(dialect=dialect))
|
||
_, separator, compiled_default = compiled_column.partition(' DEFAULT ')
|
||
if separator:
|
||
compiled_default = compiled_default.split(' COMMENT ', maxsplit=1)[0].strip()
|
||
compiled_default = compiled_default.removesuffix(' NOT NULL')
|
||
if compiled_default != "''":
|
||
violations.append((dialect_name, table_name, column_name, compiled_default))
|
||
self.assertEqual([], violations)
|
||
|
||
def test_primary_keys_and_foreign_keys_match_contract(self) -> None:
|
||
for table_name, expected_primary_key in EXPECTED_PRIMARY_KEYS.items():
|
||
table = Base.metadata.tables[table_name]
|
||
with self.subTest(table_name=table_name, constraint='primary_key'):
|
||
self.assertEqual(expected_primary_key, tuple(table.primary_key.columns.keys()))
|
||
|
||
actual_foreign_keys = {
|
||
(foreign_key.parent.name, foreign_key.target_fullname) for foreign_key in table.foreign_keys
|
||
}
|
||
with self.subTest(table_name=table_name, constraint='foreign_keys'):
|
||
self.assertEqual(EXPECTED_FOREIGN_KEYS[table_name], actual_foreign_keys)
|
||
|
||
for table_name, primary_key in EXPECTED_PRIMARY_KEYS.items():
|
||
for column_name in primary_key:
|
||
with self.subTest(table_name=table_name, column_name=column_name):
|
||
self.assertIsInstance(Base.metadata.tables[table_name].c[column_name].type, BigInteger)
|
||
|
||
def test_all_required_unique_constraints_match_contract(self) -> None:
|
||
for table_name, expected_unique_sets in EXPECTED_UNIQUE_SETS.items():
|
||
with self.subTest(table_name=table_name):
|
||
self.assertEqual(expected_unique_sets, _unique_column_sets(Base.metadata.tables[table_name]))
|
||
|
||
def test_all_owned_models_have_nullable_indexed_batch_fk(self) -> None:
|
||
for table_name in TEST_BATCH_OWNED_TABLES:
|
||
table = Base.metadata.tables[table_name]
|
||
column = table.c.test_batch_id
|
||
self.assertTrue(column.nullable, table_name)
|
||
self.assertTrue(column.index, table_name)
|
||
fk = next(iter(column.foreign_keys))
|
||
self.assertEqual('biz_test_batch.batch_id', fk.target_fullname)
|
||
self.assertIsNone(fk.ondelete)
|
||
|
||
def test_test_batch_model_defaults_and_comments_match_contract(self) -> None:
|
||
table = Base.metadata.tables['biz_test_batch']
|
||
self.assertEqual('测试数据批次', table.comment)
|
||
self.assertEqual('ACTIVE', str(table.c.status.server_default.arg))
|
||
for table_name in TEST_BATCH_OWNED_TABLES:
|
||
self.assertEqual(
|
||
'测试数据批次ID;NULL表示正式数据',
|
||
Base.metadata.tables[table_name].c.test_batch_id.comment,
|
||
)
|
||
|
||
def test_pallet_identifiers_are_unique(self) -> None:
|
||
table = Base.metadata.tables['biz_pallet']
|
||
unique_columns = {column.name for column in table.columns if column.unique}
|
||
self.assertEqual({'pallet_code', 'qr_token'}, unique_columns)
|
||
|
||
def test_checks_lock_quantities_states_active_slot_and_location(self) -> None:
|
||
expected_checks = {
|
||
'biz_test_batch': {
|
||
'ck_biz_test_batch_status': "statusIN('ACTIVE','CLOSED','PURGED')",
|
||
'ck_biz_test_batch_preview_action': "preview_actionISNULLORpreview_actionIN('ADOPT','PURGE')",
|
||
},
|
||
'biz_strain': {'ck_biz_strain_status': "statusIN('0','1')"},
|
||
'biz_warehouse': {'ck_biz_warehouse_status': "statusIN('0','1')"},
|
||
'biz_customer': {'ck_biz_customer_status': "statusIN('0','1')"},
|
||
'biz_customer_site': {'ck_biz_customer_site_status': "statusIN('0','1')"},
|
||
'biz_production_line': {'ck_biz_production_line_status': "statusIN('0','1')"},
|
||
'biz_tunnel': {'ck_biz_tunnel_status': "statusIN('0','1')"},
|
||
'biz_pallet': {
|
||
'ck_biz_pallet_capacity_packages': 'capacity_packages>0',
|
||
'ck_biz_pallet_asset_status': "asset_statusIN('0','1')",
|
||
},
|
||
'biz_inbound_order': {
|
||
'ck_biz_inbound_order_status': "statusIN('DRAFT','CONFIRMED')",
|
||
},
|
||
'biz_filling_record': {
|
||
'ck_biz_filling_record_initial_packages': 'initial_packages>0',
|
||
'ck_biz_filling_record_material_dates': '(production_dateISNULLANDexpiry_dateISNULL)OR(production_dateISNOTNULLANDexpiry_dateISNOTNULLANDexpiry_date>=production_date)',
|
||
'ck_biz_filling_record_active_slot': "active_slotISNULLORactive_slot='ACTIVE'",
|
||
},
|
||
'biz_shipment_order': {
|
||
'ck_biz_shipment_order_status': "statusIN('DRAFT','CONFIRMED')",
|
||
'ck_biz_shipment_order_receipt_state': "receipt_stateIN('PENDING','RECEIVED','LEGACY')",
|
||
},
|
||
'biz_shipment_item': {
|
||
'ck_biz_shipment_item_package_qty': 'package_qtyISNULLORpackage_qty>0',
|
||
},
|
||
'biz_usage_record': {
|
||
'ck_biz_usage_record_used_packages': 'used_packages>0',
|
||
'ck_biz_usage_record_remaining_packages_after': 'remaining_packages_after>=0',
|
||
},
|
||
'biz_return_order': {
|
||
'ck_biz_return_order_status': "statusIN('REQUESTED','CONFIRMED')",
|
||
},
|
||
'biz_return_item': {
|
||
'ck_biz_return_item_remaining_packages_snapshot': 'remaining_packages_snapshot>=0',
|
||
},
|
||
'biz_pallet_inventory': {
|
||
'ck_biz_pallet_inventory_flow_state': (
|
||
"flow_stateIN('UNSTOCKED','EMPTY_IN_WAREHOUSE','FILLED_IN_WAREHOUSE',"
|
||
"'IN_TRANSIT','AT_SITE_UNUSED','AT_SITE_PARTIAL','AT_SITE_EMPTY','RETURNING')"
|
||
),
|
||
'ck_biz_pallet_inventory_remaining_packages': 'remaining_packages>=0',
|
||
'ck_biz_pallet_inventory_version': 'version>=0',
|
||
'ck_biz_pallet_inventory_location': 'NOT(warehouse_idISNOTNULLANDsite_idISNOTNULL)',
|
||
},
|
||
'biz_pallet_event': {
|
||
'ck_biz_pallet_event_event_type': (
|
||
"event_typeIN('INBOUND_CONFIRMED','FILLING_CONFIRMED','SHIPMENT_CONFIRMED','SHIPMENT_RECEIVED','TUNNEL_ENTERED',"
|
||
"'USAGE_RECORDED','RETURN_REQUESTED','RETURN_CONFIRMED')"
|
||
),
|
||
},
|
||
}
|
||
for table_name, expected in expected_checks.items():
|
||
with self.subTest(table_name=table_name):
|
||
self.assertEqual(expected, _normalized_check_sql(Base.metadata.tables[table_name]))
|
||
|
||
def test_status_fields_use_portable_string_types(self) -> None:
|
||
char_statuses = {
|
||
'biz_strain': 'status',
|
||
'biz_warehouse': 'status',
|
||
'biz_customer': 'status',
|
||
'biz_customer_site': 'status',
|
||
'biz_production_line': 'status',
|
||
'biz_tunnel': 'status',
|
||
'biz_pallet': 'asset_status',
|
||
}
|
||
for table_name, column_name in char_statuses.items():
|
||
status_type = Base.metadata.tables[table_name].c[column_name].type
|
||
with self.subTest(table_name=table_name, column_name=column_name):
|
||
self.assertIs(type(status_type), CHAR)
|
||
self.assertEqual(1, status_type.length)
|
||
|
||
string_statuses = {
|
||
'biz_inbound_order': {'status'},
|
||
'biz_shipment_order': {'status'},
|
||
'biz_return_order': {'status'},
|
||
'biz_pallet_inventory': {'flow_state'},
|
||
'biz_pallet_event': {'event_type', 'before_state', 'after_state'},
|
||
}
|
||
for table_name, column_names in string_statuses.items():
|
||
for column_name in column_names:
|
||
status_type = Base.metadata.tables[table_name].c[column_name].type
|
||
with self.subTest(table_name=table_name, column_name=column_name):
|
||
self.assertIs(type(status_type), String)
|
||
self.assertEqual(32, status_type.length)
|
||
|
||
def test_package_counts_use_integer_and_nullable_snapshots_are_preserved(self) -> None:
|
||
integer_columns = {
|
||
'biz_pallet': {'capacity_packages'},
|
||
'biz_filling_record': {'initial_packages'},
|
||
'biz_shipment_item': {'package_qty'},
|
||
'biz_usage_record': {'used_packages', 'remaining_packages_after'},
|
||
'biz_return_item': {'remaining_packages_snapshot'},
|
||
'biz_pallet_inventory': {'remaining_packages', 'version'},
|
||
'biz_pallet_event': {'before_remaining_packages', 'after_remaining_packages'},
|
||
}
|
||
for table_name, column_names in integer_columns.items():
|
||
for column_name in column_names:
|
||
with self.subTest(table_name=table_name, column_name=column_name):
|
||
self.assertIs(type(Base.metadata.tables[table_name].c[column_name].type), Integer)
|
||
|
||
nullable_columns = {
|
||
'biz_inbound_order': {
|
||
'confirm_idempotency_key',
|
||
'confirm_request_hash',
|
||
'confirmed_by',
|
||
'confirmed_at',
|
||
},
|
||
'biz_filling_record': {'active_slot', 'closed_at'},
|
||
'biz_shipment_order': {
|
||
'confirm_idempotency_key',
|
||
'confirm_request_hash',
|
||
'confirmed_by',
|
||
'confirmed_at',
|
||
},
|
||
'biz_shipment_item': {'active_filling_id', 'package_qty'},
|
||
'biz_return_order': {
|
||
'confirm_idempotency_key',
|
||
'confirm_request_hash',
|
||
'confirmed_by',
|
||
'confirmed_at',
|
||
},
|
||
'biz_return_item': {'active_filling_id_snapshot'},
|
||
'biz_pallet_inventory': {
|
||
'active_filling_id',
|
||
'warehouse_id',
|
||
'site_id',
|
||
'tunnel_id',
|
||
'trace_no',
|
||
'current_line_id',
|
||
'last_event_id',
|
||
},
|
||
'biz_pallet_event': {
|
||
'before_warehouse_id',
|
||
'before_site_id',
|
||
'before_tunnel_id',
|
||
'before_line_id',
|
||
'after_warehouse_id',
|
||
'after_site_id',
|
||
'after_tunnel_id',
|
||
'after_line_id',
|
||
},
|
||
}
|
||
for table_name, column_names in nullable_columns.items():
|
||
for column_name in column_names:
|
||
with self.subTest(table_name=table_name, column_name=column_name):
|
||
self.assertTrue(Base.metadata.tables[table_name].c[column_name].nullable)
|
||
|
||
|
||
if __name__ == '__main__':
|
||
unittest.main()
|