Files
baiyue-logistics-chat/tests/test_order_import_admin.py
T

249 lines
12 KiB
Python

"""Staff-only upload, preview, and confirmation for customer query order data."""
import csv
import io
from pathlib import Path
import tempfile
import unittest
from openpyxl import Workbook
from logistics_chat import create_app, db, order_data
from tests.staff_fixture import login_staff
HEADERS = ['租户ID', '订单号', '业务状态', '入仓码', 'Shipment ID']
def csv_upload(rows):
stream = io.StringIO(newline='')
writer = csv.writer(stream)
writer.writerow(HEADERS)
writer.writerows(rows)
return (io.BytesIO(stream.getvalue().encode('utf-8-sig')), 'orders.csv')
class OrderImportAdminTests(unittest.TestCase):
def setUp(self):
folder = tempfile.TemporaryDirectory()
self.addCleanup(folder.cleanup)
root = Path(folder.name)
self.app = create_app({'TESTING': True, 'DATABASE': root / 'history.db',
'UPLOAD_DIR': root / 'uploads', 'BACKUP_DIR': root / 'backups',
'LOG_DIR': root / 'logs', 'DEEPSEEK_API_KEY': '',
'CUSTOMER_DEMO_ENABLED': False, 'RATE_LIMIT_PER_MIN': 1000})
self.client = self.app.test_client()
with self.app.app_context():
connection = db.connect()
try:
connection.execute('INSERT INTO customer_users(username,password_hash,display_name,tenant_id) '
"VALUES('tenant-a-user','hash','Tenant A','tenant-a')")
connection.commit()
finally:
connection.close()
def signed_in(self):
login_staff(self.app, self.client)
return self.client.environ_base['HTTP_X_CSRF_TOKEN']
def add_existing(self):
with self.app.app_context():
connection = db.connect()
try:
with connection:
order_data.import_records(connection, [{'tenant_id': 'tenant-a',
'order_no': 'EXISTING001', 'status': 'PROCESSING', 'inbound_code': 'OLDINB001'}])
finally:
connection.close()
def preview(self, upload, csrf=None):
headers = {'X-CSRF-Token': csrf} if csrf is not None else {}
return self.client.post('/api/admin/orders/import/preview',
data={'file': upload}, headers=headers, content_type='multipart/form-data')
def test_import_page_and_template_are_staff_protected(self):
self.assertEqual(self.client.get('/admin/orders').status_code, 302)
self.signed_in()
page = self.client.get('/admin/orders')
self.assertEqual(page.status_code, 200)
self.assertIn('订单数据管理', page.get_data(as_text=True))
template = self.client.get('/api/admin/orders/import/template')
self.assertEqual(template.status_code, 200)
self.assertIn('租户ID,订单号', template.get_data(as_text=True))
def test_preview_validates_rows_and_reports_existing_orders_without_writing(self):
self.signed_in()
self.add_existing()
response = self.preview(csv_upload([
['tenant-a', 'NEW000001', 'PROCESSING', 'INB000001', 'SHIP000001'],
['tenant-a', 'EXISTING001', 'COMPLETED', 'NEWINB001', ''],
]), self.client.environ_base['HTTP_X_CSRF_TOKEN'])
self.assertEqual(response.status_code, 200, response.get_data(as_text=True))
self.assertEqual(response.json['summary'], {'total_rows': 2, 'valid_rows': 2,
'duplicate_rows': 1, 'error_rows': 0})
self.assertEqual(response.json['preview'][0]['order_no'], 'NEW000001')
with self.app.app_context():
connection = db.connect()
try:
self.assertEqual(connection.execute('SELECT COUNT(*) FROM order_test_data').fetchone()[0], 1)
finally:
connection.close()
def test_invalid_file_does_not_partially_import_rows(self):
self.signed_in()
response = self.preview(csv_upload([
['tenant-a', 'NEW000001', 'PROCESSING', 'INB000001', ''],
['', 'BROKEN0001', 'PROCESSING', '', ''],
]), self.client.environ_base['HTTP_X_CSRF_TOKEN'])
self.assertEqual(response.status_code, 400)
self.assertEqual(response.json['errors'][0]['row'], 3)
with self.app.app_context():
connection = db.connect()
try:
self.assertEqual(connection.execute('SELECT COUNT(*) FROM order_test_data').fetchone()[0], 0)
finally:
connection.close()
def test_confirm_import_requires_csrf_and_preserves_existing_order_values(self):
csrf = self.signed_in()
self.add_existing()
upload = csv_upload([
['tenant-a', 'NEW000001', 'PROCESSING', 'INB000001', 'SHIP000001'],
['tenant-a', 'EXISTING001', 'COMPLETED', 'NEWINB001', ''],
])
payload = upload[0].getvalue()
blocked = self.client.post('/api/admin/orders/import',
data={'file': (io.BytesIO(payload), 'orders.csv')}, headers={'X-CSRF-Token': ''},
content_type='multipart/form-data')
self.assertEqual(blocked.status_code, 403)
response = self.client.post('/api/admin/orders/import',
data={'file': (io.BytesIO(payload), 'orders.csv')}, headers={'X-CSRF-Token': csrf},
content_type='multipart/form-data')
self.assertEqual(response.status_code, 200, response.get_data(as_text=True))
self.assertEqual(response.json['imported'], 1)
self.assertEqual(response.json['preserved'], 1)
with self.app.app_context():
connection = db.connect()
try:
self.assertEqual(connection.execute('SELECT status,inbound_code FROM order_test_data '
'WHERE order_no="EXISTING001"').fetchone(), ('PROCESSING', 'OLDINB001'))
self.assertEqual(connection.execute('SELECT tenant_id FROM order_test_data '
'WHERE order_no="NEW000001"').fetchone(), ('tenant-a',))
finally:
connection.close()
def test_xlsx_workbook_can_be_previewed(self):
csrf = self.signed_in()
workbook = Workbook()
sheet = workbook.active
sheet.append(HEADERS)
sheet.append(['tenant-a', 'XLSX000001', 'SUBMITTED', 'XLSXINB01', ''])
content = io.BytesIO()
workbook.save(content)
response = self.preview((io.BytesIO(content.getvalue()), 'orders.xlsx'), csrf)
self.assertEqual(response.status_code, 200, response.get_data(as_text=True))
self.assertEqual(response.json['preview'][0]['order_no'], 'XLSX000001')
def test_preview_rejects_unknown_tenant_status_and_malformed_tracking(self):
csrf = self.signed_in()
response = self.preview(csv_upload([
['unknown-tenant', 'ORDER000001', 'PROCESSING', '', ''],
['tenant-a', 'ORDER000002', 'NOT_A_STATUS', '', ''],
]), csrf)
self.assertEqual(response.status_code, 400)
self.assertEqual(response.json['summary']['error_rows'], 2)
stream = io.StringIO(newline='')
writer = csv.writer(stream)
writer.writerow(['租户ID', '订单号', '业务状态', '物流轨迹(JSON)'])
writer.writerow(['tenant-a', 'ORDER000003', 'PROCESSING', '[{"code":"NOT_A_NODE","actual_at":"2026-11-01"}]'])
invalid_tracking = self.preview((io.BytesIO(stream.getvalue().encode('utf-8-sig')), 'orders.csv'), csrf)
self.assertEqual(invalid_tracking.status_code, 400)
self.assertIn('物流轨迹', invalid_tracking.json['errors'][0]['message'])
def test_import_records_batch_and_undo_only_deletes_unchanged_inserted_rows(self):
csrf = self.signed_in()
self.add_existing()
response = self.client.post('/api/admin/orders/import',
data={'file': csv_upload([
['tenant-a', 'NEW000001', 'PROCESSING', 'INB000001', 'SHIP000001'],
['tenant-a', 'NEW000002', 'PROCESSING', '', ''],
['tenant-a', 'EXISTING001', 'COMPLETED', '', ''],
])}, headers={'X-CSRF-Token': csrf}, content_type='multipart/form-data')
self.assertEqual(response.status_code, 200, response.get_data(as_text=True))
self.assertEqual(response.json['imported'], 2)
self.assertEqual(response.json['preserved'], 1)
batch_id = response.json['batch_id']
self.assertGreater(batch_id, 0)
listed = self.client.get('/api/admin/orders/list?q=NEW000001')
self.assertEqual(listed.status_code, 200)
self.assertEqual(listed.json['total'], 1)
self.assertEqual(listed.json['orders'][0]['order_no'], 'NEW000001')
filtered = self.client.get('/api/admin/orders/list?tenant=tenant-a&status=PROCESSING&q=%25')
self.assertEqual(filtered.status_code, 200)
self.assertEqual(filtered.json['total'], 0)
with self.app.app_context():
connection = db.connect()
try:
connection.execute("UPDATE order_test_data SET data_note='人工备注' WHERE order_no='NEW000002'")
connection.commit()
finally:
connection.close()
undone = self.client.post(f'/api/admin/orders/import/batches/{batch_id}/undo',
headers={'X-CSRF-Token': csrf})
self.assertEqual(undone.status_code, 200, undone.get_data(as_text=True))
self.assertEqual(undone.json['deleted'], 1)
self.assertEqual(undone.json['preserved'], 1)
repeated = self.client.post(f'/api/admin/orders/import/batches/{batch_id}/undo',
headers={'X-CSRF-Token': csrf})
self.assertEqual(repeated.status_code, 200)
self.assertTrue(repeated.json['already_undone'])
with self.app.app_context():
connection = db.connect()
try:
self.assertIsNone(connection.execute("SELECT 1 FROM order_test_data "
"WHERE order_no='NEW000001'").fetchone())
self.assertEqual(connection.execute("SELECT data_note FROM order_test_data "
"WHERE order_no='NEW000002'").fetchone()[0], '人工备注')
self.assertEqual(connection.execute("SELECT status FROM order_test_data "
"WHERE order_no='EXISTING001'").fetchone()[0], 'PROCESSING')
finally:
connection.close()
def test_order_listing_requires_staff_and_csrf_for_undo(self):
self.assertEqual(self.client.get('/api/admin/orders/list').status_code, 401)
csrf = self.signed_in()
response = self.client.post('/api/admin/orders/import',
data={'file': csv_upload([['tenant-a', 'NEW000002', 'PROCESSING', '', '']])},
headers={'X-CSRF-Token': csrf}, content_type='multipart/form-data')
batch_id = response.json['batch_id']
denied = self.client.post(f'/api/admin/orders/import/batches/{batch_id}/undo',
headers={'X-CSRF-Token': ''})
self.assertEqual(denied.status_code, 403)
def test_schema_upgrade_adds_batch_tables_without_losing_orders(self):
self.add_existing()
with self.app.app_context():
connection = db.connect()
try:
connection.execute("UPDATE schema_meta SET value='3' WHERE key='schema_version'")
connection.execute('DROP TABLE order_import_batch_items')
connection.execute('DROP TABLE order_import_batches')
connection.commit()
finally:
connection.close()
db.init_db()
connection = db.connect()
try:
self.assertEqual(connection.execute("SELECT value FROM schema_meta WHERE key='schema_version'").fetchone()[0], '4')
self.assertEqual(connection.execute("SELECT status FROM order_test_data WHERE order_no='EXISTING001'").fetchone()[0], 'PROCESSING')
self.assertIsNotNone(connection.execute("SELECT 1 FROM sqlite_master WHERE name='order_import_batches'").fetchone())
finally:
connection.close()
if __name__ == '__main__':
unittest.main()