120 lines
4.6 KiB
Python
120 lines
4.6 KiB
Python
"""历史派车补派脚本:预览只读、差量、幂等及事务。"""
|
|
from datetime import date
|
|
|
|
import pytest
|
|
from sqlalchemy import create_engine
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.models.pl_dispatch_calendar import PlDispatchCalendar as Record
|
|
from scripts.refresh_dispatch_calendar_site import refresh
|
|
|
|
|
|
@pytest.fixture
|
|
def db():
|
|
engine = create_engine('sqlite://')
|
|
Record.__table__.create(engine)
|
|
with Session(engine, autoflush=False, expire_on_commit=False) as session:
|
|
yield session
|
|
engine.dispose()
|
|
|
|
|
|
def source(db, id='s1', **values):
|
|
data = dict(id=id, tenant_no='t1', calendar_kind='acid', dispatch_date=date(2026, 10, 28),
|
|
vehicle_id='v1', license_plate='鄂E12345', product_id='p1', product_name='盐酸',
|
|
origin='山峡', destination='武汉', create_by='u1', create_dept_id='d1',
|
|
site_sync_enabled=0)
|
|
data.update(values)
|
|
row = Record(**data)
|
|
db.add(row)
|
|
db.commit()
|
|
return row
|
|
|
|
|
|
def run(db, apply=False):
|
|
return refresh(db, tenant='t1', start=date(2026, 10, 28), end=date(2026, 10, 30),
|
|
operator='calendar-site-refresh', apply=apply)
|
|
|
|
|
|
@pytest.mark.parametrize('kind', ['acid', 'alkali', 'other'])
|
|
def test_preview_backfill_and_repeat_preserve_identity(db, kind):
|
|
row = source(db, calendar_kind=kind)
|
|
assert run(db)['counts']['created'] == 1
|
|
assert db.query(Record).count() == 1 and row.site_sync_enabled == 0
|
|
assert run(db, True)['counts']['created'] == 1
|
|
target = db.query(Record).filter(Record.source_record_id == row.id).one()
|
|
identity = (target.id, target.create_time, target.create_by, target.update_time)
|
|
assert row.site_sync_enabled == 1 and target.document_type == '出库计划'
|
|
assert run(db, True)['counts']['unchanged'] == 1
|
|
assert (target.id, target.create_time, target.create_by, target.update_time) == identity
|
|
assert db.query(Record).count() == 2
|
|
|
|
|
|
def test_scope_and_matching(db):
|
|
source(db, 'before', dispatch_date=date(2026, 10, 27))
|
|
source(db, 'after', dispatch_date=date(2026, 10, 31))
|
|
source(db, 'foreign', tenant_no='t2')
|
|
source(db, 'manual', calendar_kind='site')
|
|
source(db, 'deleted', del_flag=1)
|
|
source(db, 'nomatch', origin='武汉', destination='合肥')
|
|
source(db, 'inbound', destination='云栋', origin='合肥', dispatch_date=date(2026, 10, 29))
|
|
source(db, 'both', destination='云栋', dispatch_date=date(2026, 10, 30))
|
|
result = run(db, True)
|
|
targets = db.query(Record).filter(Record.source_record_id.isnot(None)).all()
|
|
assert {r.source_record_id: r.document_type for r in targets} == {'inbound': '入库计划', 'both': '出入库计划'}
|
|
assert result['counts']['created'] == 2
|
|
assert db.get(Record, 'nomatch').site_sync_enabled == 0
|
|
assert db.get(Record, 'manual').origin == '山峡'
|
|
|
|
|
|
def test_update_soft_delete_restore_and_enable_history_with_existing_target(db):
|
|
row = source(db)
|
|
run(db, True)
|
|
target = db.query(Record).filter(Record.source_record_id == row.id).one()
|
|
identity = (target.id, target.create_time)
|
|
row.destination = '云栋'
|
|
row.quantity = '32.50'
|
|
db.commit()
|
|
assert run(db, True)['counts']['updated'] == 1
|
|
assert target.document_type == '出入库计划' and target.quantity == '32.50'
|
|
row.del_flag = 1
|
|
db.commit()
|
|
assert run(db, True)['counts']['removed'] == 1
|
|
assert target.del_flag == 1
|
|
row.del_flag = 0
|
|
row.site_sync_enabled = 0
|
|
db.commit()
|
|
assert run(db, True)['counts']['restored'] == 1
|
|
assert row.site_sync_enabled == 1 and target.del_flag == 0
|
|
assert (target.id, target.create_time) == identity
|
|
|
|
|
|
def test_failure_rolls_back_all_changes(db, monkeypatch):
|
|
import scripts.refresh_dispatch_calendar_site as script
|
|
row = source(db)
|
|
original = script.svc._sync_site_record
|
|
def fail(*args):
|
|
original(*args)
|
|
args[0].flush()
|
|
raise RuntimeError('forced failure')
|
|
monkeypatch.setattr(script.svc, '_sync_site_record', fail)
|
|
with pytest.raises(RuntimeError):
|
|
run(db, True)
|
|
assert db.query(Record).count() == 1
|
|
assert db.get(Record, row.id).site_sync_enabled == 0
|
|
|
|
|
|
def test_reversed_date_range_is_rejected(db):
|
|
with pytest.raises(ValueError):
|
|
refresh(db, tenant='t1', start=date(2026, 10, 30), end=date(2026, 10, 28),
|
|
operator='calendar-site-refresh', apply=True)
|
|
|
|
|
|
def test_preview_rejects_disabled_source_with_stale_automatic_target(db):
|
|
row = source(db)
|
|
run(db, True)
|
|
row.site_sync_enabled = 0
|
|
row.origin = '武汉'
|
|
db.commit()
|
|
with pytest.raises(ValueError, match='未启用同步'):
|
|
run(db)
|