import unittest from sqlalchemy import CHAR, BigInteger, CheckConstraint, DateTime, Integer, String, Table, Text, UniqueConstraint from sqlalchemy.dialects import mysql, postgresql 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_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_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', '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', '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', 'remaining_packages_snapshot', 'create_by', 'create_time', }, 'biz_pallet_inventory': { 'pallet_id', 'active_filling_id', 'warehouse_id', 'site_id', '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_line_id', 'after_warehouse_id', 'after_site_id', 'after_line_id', 'before_remaining_packages', 'after_remaining_packages', '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_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), '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), '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), '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), '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_line_id': (BigInteger, None), 'after_warehouse_id': (BigInteger, None), 'after_site_id': (BigInteger, None), 'after_line_id': (BigInteger, None), 'before_remaining_packages': (Integer, None), 'after_remaining_packages': (Integer, None), '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_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, '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, 'line_id': False, '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, '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, '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_line_id': True, 'after_warehouse_id': True, 'after_site_id': True, 'after_line_id': True, 'before_remaining_packages': False, 'after_remaining_packages': False, '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_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_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_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_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'), ('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'), }, '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'), ('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_line_id', 'biz_production_line.line_id'), ('after_warehouse_id', 'biz_warehouse.warehouse_id'), ('after_site_id', 'biz_customer_site.site_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_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'}), }, } 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_and_postgresql(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(18, len(batch_tables)) for table_name in batch_tables: for dialect_name, dialect in (('mysql', mysql.dialect()), ('postgresql', postgresql.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_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.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(), 'postgresql': postgresql.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_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_active_slot': "active_slotISNULLORactive_slot='ACTIVE'", }, 'biz_shipment_order': { 'ck_biz_shipment_order_status': "statusIN('DRAFT','CONFIRMED')", }, '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'," "'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'," "'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_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', 'current_line_id', 'last_event_id', }, 'biz_pallet_event': { 'before_warehouse_id', 'before_site_id', 'before_line_id', 'after_warehouse_id', 'after_site_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()