1
0
forked from erp-dev/erp

feat: stateflow v2 (params required)

This commit is contained in:
2025-11-15 19:39:15 +08:00
parent 9ad404a365
commit 9c38e2ac09
24 changed files with 3670 additions and 547 deletions

View File

@@ -0,0 +1,471 @@
"""
测试 BusinessObject 的完整 API 端点
包括 reset, timeline, add_parameters_to_log, get_log_parameters 等
"""
from django.test import TestCase
from rest_framework.test import APIClient
from rest_framework import status
from django.contrib.auth import get_user_model
from stateflow import models, services
User = get_user_model()
class BusinessObjectAPITestCase(TestCase):
"""测试 BusinessObject 的所有 API 端点"""
def setUp(self):
self.client = APIClient()
self.user = User.objects.create_user(username='testuser', password='testpass')
self.client.force_authenticate(user=self.user)
# 创建状态
self.state1 = models.State.objects.create(name='状态1', description='第一个状态')
self.state2 = models.State.objects.create(name='状态2', description='第二个状态')
self.state3 = models.State.objects.create(name='状态3', description='第三个状态')
# 为状态添加参数
self.param1 = models.StateParameter.objects.create(
key='temperature',
value='',
is_required=True,
description='温度'
)
self.param2 = models.StateParameter.objects.create(
key='humidity',
value='',
is_required=False,
description='湿度'
)
self.state1.parameters.add(self.param1, self.param2)
# 创建流程
self.process = models.Process.objects.create(name='测试流程')
models.ProcessNode.objects.create(process=self.process, state=self.state1, order=0)
models.ProcessNode.objects.create(process=self.process, state=self.state2, order=1)
models.ProcessNode.objects.create(process=self.process, state=self.state3, order=2)
# 创建业务对象
self.business_object = models.BusinessObject.objects.create(
name='测试业务对象',
process=self.process
)
def test_reset_api(self):
"""测试重置进度 API"""
# 先推进两次
self.client.post(
f'/api/v1/stateflow/business-objects/{self.business_object.id}/advance/',
{'parameters': {'temperature': '25.5'}},
format='json'
)
self.client.post(f'/api/v1/stateflow/business-objects/{self.business_object.id}/advance/')
# 验证已有进度
self.business_object.refresh_from_db()
self.assertIsNotNone(services.get_business_object_current_state(self.business_object))
self.assertEqual(self.business_object.state_logs.count(), 2)
# 重置进度
response = self.client.post(f'/api/v1/stateflow/business-objects/{self.business_object.id}/reset/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertTrue(response.data['success'])
self.assertIn('重置', response.data['message'])
# 验证进度已重置
self.business_object.refresh_from_db()
self.assertIsNone(services.get_business_object_current_state(self.business_object))
self.assertEqual(self.business_object.state_logs.filter(is_cancelled=False).count(), 0)
def test_reset_api_not_started(self):
"""测试重置未开始的业务对象"""
# 未开始也可以重置(不会报错)
response = self.client.post(f'/api/v1/stateflow/business-objects/{self.business_object.id}/reset/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertTrue(response.data['success'])
def test_timeline_api(self):
"""测试状态时间线 API"""
# 未开始时的时间线
response = self.client.get(f'/api/v1/stateflow/business-objects/{self.business_object.id}/timeline/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data), 3)
# 验证所有状态都是待执行
for item in response.data:
self.assertEqual(item['status'], 'not_started')
self.assertIsNone(item['completed_at'])
self.assertIsNone(item['completed_by'])
self.assertFalse(item['is_cancelled'])
# 推进第一个状态
self.client.post(
f'/api/v1/stateflow/business-objects/{self.business_object.id}/advance/',
{'parameters': {'temperature': '25.5'}},
format='json'
)
# 再次获取时间线
response = self.client.get(f'/api/v1/stateflow/business-objects/{self.business_object.id}/timeline/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data), 3)
# 第一个状态应该是已完成
self.assertEqual(response.data[0]['status'], 'completed')
self.assertIsNotNone(response.data[0]['completed_at'])
self.assertEqual(response.data[0]['completed_by'], 'testuser')
self.assertFalse(response.data[0]['is_cancelled'])
# 其他状态仍是待执行
self.assertEqual(response.data[1]['status'], 'not_started')
self.assertEqual(response.data[2]['status'], 'not_started')
def test_timeline_api_with_step_back(self):
"""测试时间线 API 包含回退后的已撤销状态"""
# 推进两次
self.client.post(
f'/api/v1/stateflow/business-objects/{self.business_object.id}/advance/',
{'parameters': {'temperature': '25.5'}},
format='json'
)
self.client.post(f'/api/v1/stateflow/business-objects/{self.business_object.id}/advance/')
# 回退一次
self.client.post(f'/api/v1/stateflow/business-objects/{self.business_object.id}/step_back/')
# 获取时间线
response = self.client.get(f'/api/v1/stateflow/business-objects/{self.business_object.id}/timeline/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
# 第一个状态:已完成且未撤销
self.assertEqual(response.data[0]['status'], 'completed')
self.assertFalse(response.data[0]['is_cancelled'])
# 第二个状态:已撤销
self.assertEqual(response.data[1]['status'], 'cancelled')
self.assertTrue(response.data[1]['is_cancelled'])
self.assertIsNotNone(response.data[1]['cancelled_at'])
# 第三个状态:待执行
self.assertEqual(response.data[2]['status'], 'not_started')
def test_add_parameters_to_log_api(self):
"""测试为状态流转记录补充参数 API"""
# 先推进一次
response = self.client.post(
f'/api/v1/stateflow/business-objects/{self.business_object.id}/advance/',
{'parameters': {'temperature': '25.5'}},
format='json'
)
state_log_id = response.data['state_log']['id']
# 补充参数
response = self.client.post(
f'/api/v1/stateflow/business-objects/{self.business_object.id}/state-logs/{state_log_id}/add-parameters/',
{
'parameters': {
'humidity': '60%',
'operator': '张三'
},
'remark': '补充测量数据'
},
format='json'
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertTrue(response.data['success'])
self.assertIn('parameter_record', response.data)
# 验证参数记录
param_record = response.data['parameter_record']
self.assertEqual(param_record['parameters']['humidity'], '60%')
self.assertEqual(param_record['parameters']['operator'], '张三')
self.assertEqual(param_record['remark'], '补充测量数据')
def test_add_parameters_to_log_api_empty_parameters(self):
"""测试补充空参数应该失败"""
# 先推进一次
response = self.client.post(
f'/api/v1/stateflow/business-objects/{self.business_object.id}/advance/',
{'parameters': {'temperature': '25.5'}},
format='json'
)
state_log_id = response.data['state_log']['id']
# 尝试补充空参数
response = self.client.post(
f'/api/v1/stateflow/business-objects/{self.business_object.id}/state-logs/{state_log_id}/add-parameters/',
{
'parameters': {},
'remark': '空参数测试'
},
format='json'
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertFalse(response.data['success'])
self.assertIn('不能为空', response.data['message'])
def test_add_parameters_to_log_api_nonexistent_log(self):
"""测试为不存在的记录补充参数应该失败"""
response = self.client.post(
f'/api/v1/stateflow/business-objects/{self.business_object.id}/state-logs/99999/add-parameters/',
{
'parameters': {'test': 'value'},
'remark': '测试'
},
format='json'
)
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
self.assertFalse(response.data['success'])
self.assertIn('不存在', response.data['message'])
def test_get_log_parameters_api_all_records(self):
"""测试获取状态流转记录的所有参数记录 API"""
# 推进并提供初始参数
response = self.client.post(
f'/api/v1/stateflow/business-objects/{self.business_object.id}/advance/',
{'parameters': {'temperature': '25.5', 'humidity': '60%'}},
format='json'
)
state_log_id = response.data['state_log']['id']
# 补充参数两次
self.client.post(
f'/api/v1/stateflow/business-objects/{self.business_object.id}/state-logs/{state_log_id}/add-parameters/',
{
'parameters': {'temperature': '26.0'},
'remark': '第一次重测'
},
format='json'
)
self.client.post(
f'/api/v1/stateflow/business-objects/{self.business_object.id}/state-logs/{state_log_id}/add-parameters/',
{
'parameters': {'temperature': '26.5'},
'remark': '第二次重测'
},
format='json'
)
# 获取所有参数记录
response = self.client.get(
f'/api/v1/stateflow/business-objects/{self.business_object.id}/state-logs/{state_log_id}/parameters/'
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['state_log_id'], state_log_id)
self.assertFalse(response.data['is_cancelled'])
self.assertEqual(response.data['count'], 3)
# 验证汇总数据
summary = response.data['summary']
self.assertEqual(summary['temperature'], '26.5') # 最新值
self.assertEqual(summary['humidity'], '60%')
# 验证记录列表
self.assertEqual(len(response.data['records']), 3)
def test_get_log_parameters_api_by_key(self):
"""测试获取指定参数的历史记录 API"""
# 推进并提供初始参数
response = self.client.post(
f'/api/v1/stateflow/business-objects/{self.business_object.id}/advance/',
{'parameters': {'temperature': '25.5'}},
format='json'
)
state_log_id = response.data['state_log']['id']
# 补充参数
self.client.post(
f'/api/v1/stateflow/business-objects/{self.business_object.id}/state-logs/{state_log_id}/add-parameters/',
{
'parameters': {'temperature': '26.0', 'humidity': '65%'},
'remark': '重测'
},
format='json'
)
# 获取 temperature 的历史
response = self.client.get(
f'/api/v1/stateflow/business-objects/{self.business_object.id}/state-logs/{state_log_id}/parameters/?key=temperature'
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['key'], 'temperature')
self.assertEqual(len(response.data['history']), 2)
self.assertEqual(response.data['history'][0]['value'], '25.5')
self.assertEqual(response.data['history'][1]['value'], '26.0')
self.assertEqual(response.data['history'][1]['remark'], '重测')
def test_get_log_parameters_api_cancelled_state_excluded(self):
"""测试已撤销状态的参数默认不可见"""
# 推进
response = self.client.post(
f'/api/v1/stateflow/business-objects/{self.business_object.id}/advance/',
{'parameters': {'temperature': '25.5'}},
format='json'
)
state_log_id = response.data['state_log']['id']
# 回退(撤销状态)
self.client.post(f'/api/v1/stateflow/business-objects/{self.business_object.id}/step_back/')
# 默认获取参数(应该返回空)
response = self.client.get(
f'/api/v1/stateflow/business-objects/{self.business_object.id}/state-logs/{state_log_id}/parameters/'
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertTrue(response.data['is_cancelled'])
self.assertEqual(len(response.data['summary']), 0) # 汇总应该为空
self.assertEqual(len(response.data['records']), 0) # 记录列表应该为空
def test_get_log_parameters_api_cancelled_state_with_include_flag(self):
"""测试 include_cancelled=true 可以查看已撤销状态的参数"""
# 推进
response = self.client.post(
f'/api/v1/stateflow/business-objects/{self.business_object.id}/advance/',
{'parameters': {'temperature': '25.5', 'humidity': '60%'}},
format='json'
)
state_log_id = response.data['state_log']['id']
# 回退(撤销状态)
self.client.post(f'/api/v1/stateflow/business-objects/{self.business_object.id}/step_back/')
# 使用 include_cancelled=true 获取参数
response = self.client.get(
f'/api/v1/stateflow/business-objects/{self.business_object.id}/state-logs/{state_log_id}/parameters/?include_cancelled=true'
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertTrue(response.data['is_cancelled'])
self.assertEqual(len(response.data['summary']), 2) # 应该能看到参数
self.assertEqual(response.data['summary']['temperature'], '25.5')
self.assertEqual(response.data['summary']['humidity'], '60%')
def test_get_log_parameters_api_key_history_cancelled_excluded(self):
"""测试已撤销状态的参数历史默认不可见"""
# 推进
response = self.client.post(
f'/api/v1/stateflow/business-objects/{self.business_object.id}/advance/',
{'parameters': {'temperature': '25.5'}},
format='json'
)
state_log_id = response.data['state_log']['id']
# 补充参数
self.client.post(
f'/api/v1/stateflow/business-objects/{self.business_object.id}/state-logs/{state_log_id}/add-parameters/',
{'parameters': {'temperature': '26.0'}, 'remark': '重测'},
format='json'
)
# 回退
self.client.post(f'/api/v1/stateflow/business-objects/{self.business_object.id}/step_back/')
# 获取参数历史(默认应该为空)
response = self.client.get(
f'/api/v1/stateflow/business-objects/{self.business_object.id}/state-logs/{state_log_id}/parameters/?key=temperature'
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data['history']), 0)
# 使用 include_cancelled=true 查看
response = self.client.get(
f'/api/v1/stateflow/business-objects/{self.business_object.id}/state-logs/{state_log_id}/parameters/?key=temperature&include_cancelled=true'
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data['history']), 2)
def test_get_log_parameters_api_nonexistent_log(self):
"""测试获取不存在的记录的参数应该返回 404"""
response = self.client.get(
f'/api/v1/stateflow/business-objects/{self.business_object.id}/state-logs/99999/parameters/'
)
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
self.assertFalse(response.data['success'])
def test_advance_api_with_parameters(self):
"""测试推进 API 携带参数"""
response = self.client.post(
f'/api/v1/stateflow/business-objects/{self.business_object.id}/advance/',
{
'parameters': {
'temperature': '25.5',
'humidity': '60%',
'operator': '张三'
}
},
format='json'
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertTrue(response.data['success'])
self.assertIn('state_log', response.data)
# 验证参数摘要API 返回的是 parameters_summary不是 parameter_records
state_log = response.data['state_log']
self.assertIn('parameters_summary', state_log)
params = state_log['parameters_summary']
self.assertEqual(params['temperature'], '25.5')
self.assertEqual(params['humidity'], '60%')
self.assertEqual(params['operator'], '张三')
def test_advance_api_missing_required_parameter(self):
"""测试推进 API 缺少必填参数应该失败"""
response = self.client.post(
f'/api/v1/stateflow/business-objects/{self.business_object.id}/advance/',
{
'parameters': {
'humidity': '60%' # 只提供可选参数,缺少必填的 temperature
}
},
format='json'
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertFalse(response.data['success'])
self.assertIn('缺失必填参数', response.data['message'])
self.assertIn('temperature', response.data['message'])
def test_timeline_completed_all_states(self):
"""测试完成所有状态后的时间线"""
# 完成所有状态
self.client.post(
f'/api/v1/stateflow/business-objects/{self.business_object.id}/advance/',
{'parameters': {'temperature': '25.5'}},
format='json'
)
self.client.post(f'/api/v1/stateflow/business-objects/{self.business_object.id}/advance/')
self.client.post(f'/api/v1/stateflow/business-objects/{self.business_object.id}/advance/')
# 获取时间线
response = self.client.get(f'/api/v1/stateflow/business-objects/{self.business_object.id}/timeline/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
# 所有状态都应该是已完成
for item in response.data:
self.assertEqual(item['status'], 'completed')
self.assertIsNotNone(item['completed_at'])
self.assertIsNotNone(item['completed_by'])
self.assertFalse(item['is_cancelled'])

View File

@@ -0,0 +1,374 @@
"""
测试参数管理功能
包括必填参数验证、参数记录创建、回退后参数可见性等
"""
from django.test import TestCase
from django.contrib.auth import get_user_model
from stateflow import models, services
User = get_user_model()
class ParameterManagementTestCase(TestCase):
"""测试参数管理功能"""
def setUp(self):
"""设置测试数据"""
# 创建测试用户
self.user = User.objects.create_user(username='testuser', password='testpass')
# 创建状态
self.state1 = models.State.objects.create(name='状态1', description='第一个状态')
self.state2 = models.State.objects.create(name='状态2', description='第二个状态')
self.state3 = models.State.objects.create(name='状态3', description='第三个状态')
# 为状态1添加必填参数
self.required_param = models.StateParameter.objects.create(
key='temperature',
value='',
is_required=True,
description='温度(必填)'
)
self.optional_param = models.StateParameter.objects.create(
key='humidity',
value='',
is_required=False,
description='湿度(可选)'
)
self.state1.parameters.add(self.required_param, self.optional_param)
# 为状态2添加必填参数
self.required_param2 = models.StateParameter.objects.create(
key='pressure',
value='',
is_required=True,
description='压力(必填)'
)
self.state2.parameters.add(self.required_param2)
# 创建流程
self.process = models.Process.objects.create(name='测试流程')
models.ProcessNode.objects.create(process=self.process, state=self.state1, order=0)
models.ProcessNode.objects.create(process=self.process, state=self.state2, order=1)
models.ProcessNode.objects.create(process=self.process, state=self.state3, order=2)
# 创建业务对象
self.business_object = models.BusinessObject.objects.create(
name='测试业务对象',
process=self.process
)
def test_advance_without_required_parameter_fails(self):
"""测试不提供必填参数时推进失败"""
# 尝试推进但不提供必填参数
success, message, state_log = services.advance_to_next_state(
self.business_object,
self.user
)
# 应该失败
self.assertFalse(success)
self.assertIn('缺失必填参数', message)
self.assertIn('temperature', message)
self.assertIsNone(state_log)
# 验证状态未改变
current_state = services.get_business_object_current_state(self.business_object)
self.assertIsNone(current_state)
# 验证没有创建状态流转记录
self.assertEqual(self.business_object.state_logs.count(), 0)
def test_advance_with_only_required_parameter_succeeds(self):
"""测试只提供必填参数时推进成功"""
# 只提供必填参数
success, message, state_log = services.advance_to_next_state(
self.business_object,
self.user,
temperature='25.5' # 只提供必填的
)
# 应该成功
self.assertTrue(success)
self.assertIsNotNone(state_log)
# 验证状态已推进
current_state = services.get_business_object_current_state(self.business_object)
self.assertEqual(current_state.name, self.state1.name)
# 验证参数记录已创建
self.assertEqual(state_log.parameter_records.count(), 1)
param_record = state_log.parameter_records.first()
self.assertEqual(param_record.parameters['temperature'], '25.5')
self.assertNotIn('humidity', param_record.parameters)
def test_advance_with_all_parameters(self):
"""测试提供所有参数时推进成功"""
success, message, state_log = services.advance_to_next_state(
self.business_object,
self.user,
temperature='25.5',
humidity='60%'
)
# 应该成功
self.assertTrue(success)
self.assertIsNotNone(state_log)
# 验证参数记录包含所有提供的参数
param_record = state_log.parameter_records.first()
self.assertEqual(param_record.parameters['temperature'], '25.5')
self.assertEqual(param_record.parameters['humidity'], '60%')
def test_advance_with_extra_parameters(self):
"""测试提供额外参数时也能成功(不做验证)"""
success, message, state_log = services.advance_to_next_state(
self.business_object,
self.user,
temperature='25.5',
humidity='60%',
operator='张三', # 额外参数
notes='测试备注' # 额外参数
)
# 应该成功(不验证参数是否在定义中)
self.assertTrue(success)
# 验证所有参数都被保存
param_record = state_log.parameter_records.first()
self.assertEqual(len(param_record.parameters), 4)
self.assertEqual(param_record.parameters['operator'], '张三')
self.assertEqual(param_record.parameters['notes'], '测试备注')
def test_add_parameters_after_advance(self):
"""测试推进后补充参数"""
# 先推进
success, message, state_log = services.advance_to_next_state(
self.business_object,
self.user,
temperature='25.5'
)
self.assertTrue(success)
# 补充参数
new_record = services.add_parameters_to_state_log(
state_log,
remark='补充测量',
humidity='65%',
inspector='李四'
)
# 验证创建了新的参数记录
self.assertEqual(state_log.parameter_records.count(), 2)
self.assertEqual(new_record.parameters['humidity'], '65%')
self.assertEqual(new_record.parameters['inspector'], '李四')
self.assertEqual(new_record.remark, '补充测量')
def test_parameter_summary_with_updates(self):
"""测试参数汇总(包含更新的值)"""
# 推进并提供初始参数
success, message, state_log = services.advance_to_next_state(
self.business_object,
self.user,
temperature='25.5',
humidity='60%'
)
# 补充参数(更新 temperature
services.add_parameters_to_state_log(
state_log,
temperature='26.0', # 重新测量
remark='温度重新测量'
)
# 获取参数汇总
summary = state_log.get_all_parameters_summary()
# 验证最新值
self.assertEqual(summary['temperature'], '26.0') # 应该是更新后的值
self.assertEqual(summary['humidity'], '60%') # 保持原值
def test_parameter_history(self):
"""测试参数历史记录"""
# 推进
success, message, state_log = services.advance_to_next_state(
self.business_object,
self.user,
temperature='25.5'
)
# 多次更新同一个参数
services.add_parameters_to_state_log(
state_log,
temperature='26.0',
remark='第一次重测'
)
services.add_parameters_to_state_log(
state_log,
temperature='26.5',
remark='第二次重测'
)
# 获取历史
history = state_log.get_parameter_history('temperature')
# 验证历史记录
self.assertEqual(len(history), 3)
self.assertEqual(history[0]['value'], '25.5')
self.assertEqual(history[1]['value'], '26.0')
self.assertEqual(history[1]['remark'], '第一次重测')
self.assertEqual(history[2]['value'], '26.5')
self.assertEqual(history[2]['remark'], '第二次重测')
def test_step_back_excludes_cancelled_state_parameters(self):
"""测试回退后,默认情况下查询参数时应排除已撤销状态的参数"""
# 推进两次,每次都提供参数
success, msg, state_log1 = services.advance_to_next_state(
self.business_object,
self.user,
temperature='25.5',
humidity='60%'
)
self.assertTrue(success)
success, msg, state_log2 = services.advance_to_next_state(
self.business_object,
self.user,
pressure='1013'
)
self.assertTrue(success)
# 验证两个状态都有参数记录
self.assertEqual(state_log1.parameter_records.count(), 1)
self.assertEqual(state_log2.parameter_records.count(), 1)
# 回退一步(撤销 state2
success, message = services.step_back_one_state(self.business_object, self.user)
self.assertTrue(success)
# 刷新 state_log2
state_log2.refresh_from_db()
self.assertTrue(state_log2.is_cancelled)
# 默认情况下,已撤销状态的参数不可见
summary = state_log2.get_all_parameters_summary()
self.assertEqual(len(summary), 0) # 应该返回空字典
# 使用 include_cancelled=True 可以查看已撤销状态的参数
summary_with_cancelled = state_log2.get_all_parameters_summary(include_cancelled=True)
self.assertEqual(len(summary_with_cancelled), 1)
self.assertEqual(summary_with_cancelled['pressure'], '1013')
# 验证 state_log1 的参数仍然可见(未撤销)
summary1 = state_log1.get_all_parameters_summary()
self.assertEqual(len(summary1), 2)
self.assertEqual(summary1['temperature'], '25.5')
self.assertEqual(summary1['humidity'], '60%')
def test_cancelled_state_parameter_history(self):
"""测试已撤销状态的参数历史记录不可见"""
# 推进并提供参数
success, msg, state_log = services.advance_to_next_state(
self.business_object,
self.user,
temperature='25.5'
)
self.assertTrue(success)
# 补充参数
services.add_parameters_to_state_log(
state_log,
temperature='26.0',
remark='重新测量'
)
# 验证参数历史存在
history = state_log.get_parameter_history('temperature')
self.assertEqual(len(history), 2)
# 回退(撤销状态)
services.step_back_one_state(self.business_object, self.user)
state_log.refresh_from_db()
# 默认情况下,已撤销状态的参数历史不可见
history = state_log.get_parameter_history('temperature')
self.assertEqual(len(history), 0)
# 使用 include_cancelled=True 可以查看
history_with_cancelled = state_log.get_parameter_history('temperature', include_cancelled=True)
self.assertEqual(len(history_with_cancelled), 2)
def test_cancelled_state_get_latest_parameter(self):
"""测试已撤销状态无法获取最新参数值"""
# 推进并提供参数
success, msg, state_log = services.advance_to_next_state(
self.business_object,
self.user,
temperature='25.5'
)
self.assertTrue(success)
# 验证可以获取参数
temp = state_log.get_latest_parameter('temperature')
self.assertEqual(temp, '25.5')
# 回退(撤销状态)
services.step_back_one_state(self.business_object, self.user)
state_log.refresh_from_db()
# 默认情况下返回默认值
temp = state_log.get_latest_parameter('temperature', default='N/A')
self.assertEqual(temp, 'N/A')
# 使用 include_cancelled=True 可以获取
temp_with_cancelled = state_log.get_latest_parameter('temperature', include_cancelled=True)
self.assertEqual(temp_with_cancelled, '25.5')
def test_multiple_states_with_different_required_parameters(self):
"""测试多个状态各自的必填参数验证"""
# 推进到 state1需要 temperature
success, msg, _ = services.advance_to_next_state(
self.business_object,
self.user,
temperature='25.5'
)
self.assertTrue(success)
# 尝试推进到 state2 但不提供 pressure
success, msg, _ = services.advance_to_next_state(
self.business_object,
self.user,
temperature='26.0' # 提供了 state1 的参数,但这是 state2
)
# 应该失败(缺少 state2 的必填参数 pressure
self.assertFalse(success)
self.assertIn('缺失必填参数', msg)
self.assertIn('pressure', msg)
# 提供正确的必填参数
success, msg, _ = services.advance_to_next_state(
self.business_object,
self.user,
pressure='1013'
)
self.assertTrue(success)
def test_state_without_required_parameters_allows_advance_without_params(self):
"""测试没有必填参数的状态允许不提供参数推进"""
# 先推进两次到达 state3没有任何参数要求
services.advance_to_next_state(self.business_object, self.user, temperature='25')
services.advance_to_next_state(self.business_object, self.user, pressure='1013')
# 推进到 state3不提供任何参数
success, msg, state_log = services.advance_to_next_state(
self.business_object,
self.user
)
# 应该成功
self.assertTrue(success)
self.assertIsNotNone(state_log)
# 验证没有创建参数记录
self.assertEqual(state_log.parameter_records.count(), 0)

View File

@@ -0,0 +1,288 @@
"""
Stateflow 服务层单元测试
测试新增的服务函数以及 current_state 语义修正
"""
from django.test import TestCase
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from stateflow import models, services
User = get_user_model()
class StateFlowServicesTestCase(TestCase):
"""测试订单状态流转逻辑和新增服务函数"""
def setUp(self):
"""设置测试数据"""
# 创建测试用户
self.user = User.objects.create_user(username='testuser', password='testpass')
# 创建状态节点
self.state1 = models.State.objects.create(name='状态1', description='第一个状态')
self.state2 = models.State.objects.create(name='状态2', description='第二个状态')
self.state3 = models.State.objects.create(name='状态3', description='第三个状态')
# 创建流程
self.process = models.Process.objects.create(name='测试流程', description='用于测试的流程')
# 添加流程节点
models.ProcessNode.objects.create(process=self.process, state=self.state1, order=0)
models.ProcessNode.objects.create(process=self.process, state=self.state2, order=1)
models.ProcessNode.objects.create(process=self.process, state=self.state3, order=2)
# 创建业务对象
ct = ContentType.objects.get_for_model(models.Process)
self.business_object = models.BusinessObject.objects.create(
name='测试订单',
process=self.process,
description='测试订单描述',
content_type=ct,
object_id=self.process.id
)
def test_initial_state(self):
"""测试初始状态 - 应该是 None未开始"""
current_state = self.business_object.get_current_state()
self.assertIsNone(current_state, '初始状态应该是 None未开始')
self.assertEqual(self.business_object.get_progress_percentage(), 0.0)
# 整体状态应该是 not_started
status = services.get_overall_status(self.business_object)
self.assertEqual(status, 'not_started')
def test_advance_to_next_state(self):
"""测试推进到下一个状态"""
# 第一次推进应该完成第一个状态
success, message, _ = services.advance_to_next_state(self.business_object, self.user)
self.assertTrue(success)
# current_state 应该是最后完成的状态state1
current_state = self.business_object.get_current_state()
self.assertEqual(current_state.name, self.state1.name)
self.assertAlmostEqual(self.business_object.get_progress_percentage(), 33.33, places=1)
# 推进到状态2完成state2
success, message, _ = services.advance_to_next_state(self.business_object, self.user)
self.assertTrue(success)
# current_state 应该是 state2
current_state = self.business_object.get_current_state()
self.assertEqual(current_state.name, self.state2.name)
self.assertAlmostEqual(self.business_object.get_progress_percentage(), 66.67, places=1)
# 完成最后一个状态state3
success, message, _ = services.advance_to_next_state(self.business_object, self.user)
self.assertTrue(success)
self.assertEqual(self.business_object.get_progress_percentage(), 100.0)
# current_state 应该是 state3最后完成的状态
current_state = self.business_object.get_current_state()
self.assertEqual(current_state.name, self.state3.name)
# 整体状态应该是 completed
status = services.get_overall_status(self.business_object)
self.assertEqual(status, 'completed')
def test_state_status(self):
"""测试状态的状态"""
# 初始状态,所有状态都应该是 not_started
self.assertEqual(services.get_business_object_state_status(self.business_object, self.state1), 'not_started')
self.assertEqual(services.get_business_object_state_status(self.business_object, self.state2), 'not_started')
# 完成第一个状态
services.advance_to_next_state(self.business_object, self.user)
self.assertEqual(services.get_business_object_state_status(self.business_object, self.state1), 'completed')
self.assertEqual(services.get_business_object_state_status(self.business_object, self.state2), 'not_started')
self.assertEqual(services.get_business_object_state_status(self.business_object, self.state3), 'not_started')
# 完成第二个状态
services.advance_to_next_state(self.business_object, self.user)
self.assertEqual(services.get_business_object_state_status(self.business_object, self.state1), 'completed')
self.assertEqual(services.get_business_object_state_status(self.business_object, self.state2), 'completed')
self.assertEqual(services.get_business_object_state_status(self.business_object, self.state3), 'not_started')
def test_timeline(self):
"""测试时间线"""
timeline = self.business_object.get_timeline()
self.assertEqual(len(timeline), 3)
# 所有状态都应该是 not_started
for item in timeline:
self.assertEqual(item['status'], 'not_started')
# 完成第一个状态
services.advance_to_next_state(self.business_object, self.user)
timeline = self.business_object.get_timeline()
self.assertEqual(timeline[0]['status'], 'completed')
self.assertEqual(timeline[1]['status'], 'not_started')
self.assertEqual(timeline[2]['status'], 'not_started')
self.assertIsNotNone(timeline[0]['completed_by'])
self.assertEqual(timeline[0]['completed_by'].id, self.user.id)
self.assertFalse(timeline[0]['is_cancelled'])
def test_reset_progress(self):
"""测试重置进度 - 不删除记录,而是标记为已撤销"""
# 完成两个状态
services.advance_to_next_state(self.business_object, self.user)
services.advance_to_next_state(self.business_object, self.user)
# 确认有2条日志记录
self.assertEqual(self.business_object.state_logs.count(), 2)
# 重置进度
services.reset_business_object_progress(self.business_object)
# 日志记录应该还在(不删除)
self.assertEqual(self.business_object.state_logs.count(), 2)
# 但所有记录都应该标记为已撤销
cancelled_count = self.business_object.state_logs.filter(is_cancelled=True).count()
self.assertEqual(cancelled_count, 2)
# 应该回到初始状态(未开始)
current_state = self.business_object.get_current_state()
self.assertIsNone(current_state)
# 所有撤销的记录都应该有撤销时间
for log in self.business_object.state_logs.all():
self.assertTrue(log.is_cancelled)
self.assertIsNotNone(log.cancelled_at)
def test_timeline_with_cancelled(self):
"""测试包含撤销记录的时间线"""
# 推进并完成所有状态
services.advance_to_next_state(self.business_object, self.user)
services.advance_to_next_state(self.business_object, self.user)
services.advance_to_next_state(self.business_object, self.user)
# 重置进度
services.reset_business_object_progress(self.business_object)
timeline = self.business_object.get_timeline()
# 所有状态都应该显示为 cancelled
for item in timeline:
self.assertEqual(item['status'], 'cancelled')
self.assertTrue(item['is_cancelled'])
self.assertIsNotNone(item['cancelled_at'])
def test_get_next_pending_state(self):
"""测试获取下一个待执行节点(新增功能)"""
# 创建参数
param1 = models.StateParameter.objects.create(
key='param1', value='value1', is_required=True
)
param2 = models.StateParameter.objects.create(
key='param2', value='value2', is_required=False
)
self.state1.parameters.add(param1, param2)
# 初始状态 - 应该返回第一个节点
next_state = services.get_next_pending_state(self.business_object)
self.assertIsNotNone(next_state)
self.assertEqual(next_state['state'].name, self.state1.name)
self.assertEqual(next_state['order'], 0)
self.assertIn('parameters', next_state)
self.assertEqual(len(next_state['parameters']), 2)
# 测试不包含参数
next_state_no_params = services.get_next_pending_state(
self.business_object, include_parameters=False
)
self.assertIsNotNone(next_state_no_params)
self.assertNotIn('parameters', next_state_no_params)
# 完成第一个状态(提供必填参数)
success, msg, _ = services.advance_to_next_state(
self.business_object,
self.user,
param1='value1' # 提供必填参数
)
self.assertTrue(success)
# 应该返回第二个节点
next_state = services.get_next_pending_state(self.business_object)
self.assertIsNotNone(next_state)
self.assertEqual(next_state['state'].name, self.state2.name)
self.assertEqual(next_state['order'], 1)
# 完成所有状态
services.advance_to_next_state(self.business_object, self.user)
services.advance_to_next_state(self.business_object, self.user)
# 应该返回 None已完成
next_state = services.get_next_pending_state(self.business_object)
self.assertIsNone(next_state)
def test_get_all_pending_states(self):
"""测试获取所有待执行节点列表(新增功能)"""
# 初始状态 - 所有节点都待执行
pending_states = services.get_all_pending_states(self.business_object)
self.assertEqual(len(pending_states), 3)
self.assertEqual(pending_states[0]['state'].name, self.state1.name)
self.assertEqual(pending_states[1]['state'].name, self.state2.name)
self.assertEqual(pending_states[2]['state'].name, self.state3.name)
# 验证不包含 parameters 字段
self.assertNotIn('parameters', pending_states[0])
# 完成第一个状态
services.advance_to_next_state(self.business_object, self.user)
# 应该只剩下两个待执行节点
pending_states = services.get_all_pending_states(self.business_object)
self.assertEqual(len(pending_states), 2)
self.assertEqual(pending_states[0]['state'].name, self.state2.name)
self.assertEqual(pending_states[1]['state'].name, self.state3.name)
# 完成第二个状态
services.advance_to_next_state(self.business_object, self.user)
# 应该只剩下一个待执行节点
pending_states = services.get_all_pending_states(self.business_object)
self.assertEqual(len(pending_states), 1)
self.assertEqual(pending_states[0]['state'].name, self.state3.name)
# 完成所有状态
services.advance_to_next_state(self.business_object, self.user)
# 应该没有待执行节点
pending_states = services.get_all_pending_states(self.business_object)
self.assertEqual(len(pending_states), 0)
def test_get_state_parameters(self):
"""测试获取节点参数列表(新增功能)"""
# 创建必填和可选参数
required_param = models.StateParameter.objects.create(
key='required_param', value='required_value', is_required=True
)
optional_param1 = models.StateParameter.objects.create(
key='optional_param1', value='optional_value1', is_required=False
)
optional_param2 = models.StateParameter.objects.create(
key='optional_param2', value='optional_value2', is_required=False
)
# 关联参数到状态
self.state1.parameters.add(required_param, optional_param1, optional_param2)
# 获取所有参数
all_params = services.get_state_parameters(self.state1)
self.assertEqual(len(all_params), 3)
# 只获取必填参数
required_params = services.get_state_parameters(self.state1, required_only=True)
self.assertEqual(len(required_params), 1)
self.assertEqual(required_params[0].key, 'required_param')
self.assertTrue(required_params[0].is_required)
# 测试没有参数的状态
no_params = services.get_state_parameters(self.state2)
self.assertEqual(len(no_params), 0)
# 测试只有必填参数的状态
self.state3.parameters.add(required_param)
params = services.get_state_parameters(self.state3, required_only=True)
self.assertEqual(len(params), 1)

View File

@@ -24,9 +24,9 @@ class StepBackTestCase(TestCase):
self.process = models.Process.objects.create(name='测试流程', description='三个状态的测试流程')
# 创建流程节点
models.ProcessNode.objects.create(process=self.process, state=self.state1, order=1)
models.ProcessNode.objects.create(process=self.process, state=self.state2, order=2)
models.ProcessNode.objects.create(process=self.process, state=self.state3, order=3)
models.ProcessNode.objects.create(process=self.process, state=self.state1, order=0)
models.ProcessNode.objects.create(process=self.process, state=self.state2, order=1)
models.ProcessNode.objects.create(process=self.process, state=self.state3, order=2)
# 创建业务对象
self.business_object = models.BusinessObject.objects.create(
@@ -47,13 +47,13 @@ class StepBackTestCase(TestCase):
def test_step_back_from_first_state(self):
"""测试从第一个状态回退到未开始"""
# 推进第一个状态
success, _ = services.advance_to_next_state(self.business_object, self.user)
# 推进(完成第一个状态
success, _, _ = services.advance_to_next_state(self.business_object, self.user)
self.assertTrue(success)
# 验证当前在第一个状态(进行中,准备完成第二个状态)
# 验证 current_state 是 state1最后完成的状态)
current_state = services.get_business_object_current_state(self.business_object)
self.assertEqual(current_state.id, self.state2.id)
self.assertEqual(current_state.name, self.state1.name)
# 回退一步
success, message = services.step_back_one_state(self.business_object, self.user)
@@ -62,6 +62,8 @@ class StepBackTestCase(TestCase):
# 验证回到未开始状态
self.assertEqual(services.get_overall_status(self.business_object), 'not_started')
current_state = services.get_business_object_current_state(self.business_object)
self.assertIsNone(current_state)
# 验证状态1的记录已被撤销
record = models.StateFlowRecord.objects.filter(
@@ -73,22 +75,22 @@ class StepBackTestCase(TestCase):
def test_step_back_from_middle_state(self):
"""测试从中间状态回退"""
# 推进到第二个状态
# 推进两次(完成 state1 和 state2
services.advance_to_next_state(self.business_object, self.user)
services.advance_to_next_state(self.business_object, self.user)
# 验证当前在第二个状态(进行中,准备完成第三个状态)
# 验证 current_state 是 state2最后完成的状态)
current_state = services.get_business_object_current_state(self.business_object)
self.assertEqual(current_state.id, self.state3.id)
self.assertEqual(current_state.name, self.state2.name)
# 回退一步
success, message = services.step_back_one_state(self.business_object, self.user)
self.assertTrue(success)
self.assertIn('状态2', message)
# 验证回到第一个状态(进行中,准备完成第二个状态)
# 验证 current_state 变为state1
current_state = services.get_business_object_current_state(self.business_object)
self.assertEqual(current_state.id, self.state2.id)
self.assertEqual(current_state.name, self.state1.name)
# 验证状态2的记录已被撤销
record = models.StateFlowRecord.objects.filter(
@@ -99,23 +101,25 @@ class StepBackTestCase(TestCase):
def test_step_back_from_completed(self):
"""测试从已完成状态回退"""
# 推进到完成
# 推进到完成(完成所有三个状态)
services.advance_to_next_state(self.business_object, self.user)
services.advance_to_next_state(self.business_object, self.user)
services.advance_to_next_state(self.business_object, self.user)
# 验证已完成
# 验证已完成current_state 是 state3
self.assertEqual(services.get_overall_status(self.business_object), 'completed')
current_state = services.get_business_object_current_state(self.business_object)
self.assertEqual(current_state.name, self.state3.name)
# 回退一步
success, message = services.step_back_one_state(self.business_object, self.user)
self.assertTrue(success)
self.assertIn('状态3', message)
# 验证回到进行中状态(准备完成第三个状态)
# 验证回到进行中状态current_state 变为state2
self.assertEqual(services.get_overall_status(self.business_object), 'in_progress')
current_state = services.get_business_object_current_state(self.business_object)
self.assertEqual(current_state.id, self.state3.id)
self.assertEqual(current_state.name, self.state2.name)
def test_step_back_multiple_times(self):
"""测试多次回退"""
@@ -124,17 +128,17 @@ class StepBackTestCase(TestCase):
services.advance_to_next_state(self.business_object, self.user)
services.advance_to_next_state(self.business_object, self.user)
# 第一次回退
# 第一次回退(撤销 state3
success, _ = services.step_back_one_state(self.business_object, self.user)
self.assertTrue(success)
current = services.get_business_object_current_state(self.business_object)
self.assertEqual(current.id, self.state3.id)
self.assertEqual(current.name, self.state2.name)
# 第二次回退
# 第二次回退(撤销 state2
success, _ = services.step_back_one_state(self.business_object, self.user)
self.assertTrue(success)
current = services.get_business_object_current_state(self.business_object)
self.assertEqual(current.id, self.state2.id)
self.assertEqual(current.name, self.state1.name)
# 第三次回退
success, _ = services.step_back_one_state(self.business_object, self.user)
@@ -148,20 +152,20 @@ class StepBackTestCase(TestCase):
def test_advance_after_step_back(self):
"""测试回退后再前进"""
# 推进两步
# 推进两步(完成 state1 和 state2
services.advance_to_next_state(self.business_object, self.user)
services.advance_to_next_state(self.business_object, self.user)
# 回退一步
# 回退一步(撤销 state2
services.step_back_one_state(self.business_object, self.user)
# 再前进
success, _ = services.advance_to_next_state(self.business_object, self.user)
# 再前进(重新完成 state2
success, _, _ = services.advance_to_next_state(self.business_object, self.user)
self.assertTrue(success)
# 验证当前在第二个状态
# 验证 current_state 是 state2最后完成的
current_state = services.get_business_object_current_state(self.business_object)
self.assertEqual(current_state.id, self.state3.id)
self.assertEqual(current_state.name, self.state2.name)
def test_step_back_preserves_history(self):
"""测试回退操作保留历史记录"""

View File

@@ -27,9 +27,9 @@ class StepBackAPITestCase(TestCase):
# 创建流程
self.process = models.Process.objects.create(name='测试流程')
models.ProcessNode.objects.create(process=self.process, state=self.state1, order=1)
models.ProcessNode.objects.create(process=self.process, state=self.state2, order=2)
models.ProcessNode.objects.create(process=self.process, state=self.state3, order=3)
models.ProcessNode.objects.create(process=self.process, state=self.state1, order=0)
models.ProcessNode.objects.create(process=self.process, state=self.state2, order=1)
models.ProcessNode.objects.create(process=self.process, state=self.state3, order=2)
# 创建业务对象
self.business_object = models.BusinessObject.objects.create(