"""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], str(db.SCHEMA_VERSION)) 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()