import unittest from datetime import date from unittest.mock import MagicMock, patch from flask import Flask from routes import spot from services.spot_confirmation_edit import confirmation_reminder_state, validate_confirmation_values from services.spot_confirmation_import import parse_confirmation_import, ImportValidationError class ConfirmationEditValidationTest(unittest.TestCase): def test_price_is_free_text_with_length_limit(self): for price in ('0.1234', '不含税运单价4.4元 含税4.444元', ''): self.assertEqual(validate_confirmation_values({'product_unit_price':price})['product_unit_price'], price) with self.assertRaises(ValueError): validate_confirmation_values({'product_unit_price':'价' * 1001}) def test_confirmation_options_and_other_reason(self): for value in ('做','不做',''): self.assertEqual(validate_confirmation_values({'sample_confirmed':value})['sample_confirmed'],value) with self.assertRaises(ValueError): validate_confirmation_values({'sample_confirmed':'是'}) for reason in ('价格高','质感差','资质不全','同质化严重'): self.assertEqual(validate_confirmation_values({'sample_confirmation_reason':reason})['sample_confirmation_reason'],reason) with self.assertRaises(ValueError): validate_confirmation_values({'sample_confirmation_reason':'其他','other_reason':'原因是'+reason}) with self.assertRaises(ValueError): validate_confirmation_values({'sample_confirmation_reason':'其他','other_reason':' '}) self.assertEqual(validate_confirmation_values({'sample_confirmation_reason':'其他','other_reason':' 尺寸不合适 '})['sample_confirmation_reason'],'其他:尺寸不合适') self.assertEqual(validate_confirmation_values({'sample_confirmation_reason':'无'})['sample_confirmation_reason'],'无') self.assertEqual(validate_confirmation_values({'sample_confirmed':'做'})['sample_confirmation_reason'],'无') self.assertEqual(validate_confirmation_values({ 'sample_confirmed':'做','sample_confirmation_reason':'价格高' })['sample_confirmation_reason'],'价格高') def test_sample_status_options_and_legacy_compatibility(self): current = ('未付款','付款中','样品到货中','样品已到货','不授权','厂家无货','停产','其他') for value in current + ('产品停产','厂家无现货','已到货','不做','待寄出',''): self.assertEqual(validate_confirmation_values({'sample_status':value})['sample_status'], value) with self.assertRaises(ValueError): validate_confirmation_values({'sample_status':'任意状态'}) def test_confirmation_reminder_rule(self): self.assertEqual(confirmation_reminder_state('', '', date(2026,10,16)), {'due_date':None,'pending':False}) self.assertEqual(confirmation_reminder_state('2026-10-06', '', date(2026,10,15)), {'due_date':'2026-10-16','pending':False}) self.assertEqual(confirmation_reminder_state('2026-10-06', '', date(2026,10,16)), {'due_date':'2026-10-16','pending':True}) self.assertFalse(confirmation_reminder_state('2026-10-01', '做', date(2026,10,16))['pending']) def test_import_enforces_same_rules_and_template_removals(self): rows=parse_confirmation_import('产品名称,产品单价,样品确认,样品确认原因\n样品,12.3456,不做,其他:尺寸不合适'.encode(),'test.csv') self.assertEqual(rows[0][1]['product_unit_price'],'12.3456') self.assertEqual(parse_confirmation_import('产品名称,产品单价\n样品,不含税4元'.encode(),'test.csv')[0][1]['product_unit_price'],'不含税4元') for text in ('产品名称,样品确认\n样品,是', '产品名称,样品确认原因\n样品,其他:价格高且尺寸不合适'): with self.assertRaises(ImportValidationError): parse_confirmation_import(text.encode(),'test.csv') class ConfirmationSaveTest(unittest.TestCase): def setUp(self): self.app=Flask(__name__) self.app.secret_key='test' self.conn=MagicMock() self.cur=self.conn.cursor.return_value.__enter__.return_value self.cur.fetchone.return_value={'id':7,'status':'通过'} self.db=patch.object(spot,'get_db',return_value=self.conn).start() self.perm=patch.object(spot,'operation_required',return_value=True).start() patch.object(spot,'role_required',return_value=True).start() patch.object(spot,'bump_data_version').start() patch.object(spot,'add_operation_log').start() self.addCleanup(patch.stopall) def save(self, data): with self.app.test_request_context('/api/spot/confirmation/save',method='POST',json=data): response=spot.confirmation_save.__wrapped__() return (response[0] if isinstance(response,tuple) else response).get_json() def test_save_transaction_and_clear_optional_fields(self): result=self.save({'application_id':7,'product_unit_price':'12.3456', 'sample_confirmed':'做','arrival_date':'','sample_confirmation_reason':'其他','other_reason':'尺寸不合适'}) self.assertEqual(result['code'],0) self.conn.commit.assert_called_once() sql,params=self.cur.execute.call_args.args self.assertIn('UPDATE spot_confirmations',sql) self.assertIn('12.3456',params) self.assertIn(None,params) self.assertIn('其他:尺寸不合适',params) self.conn.close.assert_called_once() def test_readonly_and_removed_fields_cannot_be_written(self): for key in ('brand','product_name','recipient','receiving_department','sample_location','sample_remark'): self.assertEqual(self.save({'application_id':7,key:'篡改'})['code'],400) self.db.assert_not_called() def test_permissions_are_required(self): self.perm.return_value=False self.assertEqual(self.save({'application_id':7,'remark':'修改'})['code'],403) self.db.assert_not_called() def test_rejected_or_missing_application_cannot_be_saved(self): for value in (None,{'id':7,'status':'驳回'}): self.cur.fetchone.return_value=value self.assertEqual(self.save({'application_id':7,'remark':'修改'})['code'],404) self.conn.commit.assert_not_called() def test_db_failure_rolls_back(self): def execute(sql,*args): if sql.startswith('UPDATE'): raise RuntimeError('simulated failure') self.cur.execute.side_effect=execute self.assertEqual(self.save({'application_id':7,'remark':'修改'})['code'],500) self.conn.rollback.assert_called_once() self.conn.commit.assert_not_called()