feat: 完善 Skill 管理与发布治理

- 实现标准资源存储、能力绑定及双格式导入导出

- 接入分类、可见范围、审批发布与资源权限校验

- 补充并发、租户隔离、安全边界和迁移契约测试
This commit is contained in:
2026-07-27 18:54:20 +08:00
parent aedefe6b5e
commit 2a9e882ac6
165 changed files with 23737 additions and 1088 deletions

View File

@@ -0,0 +1,158 @@
package tech.easyflow.approval.service.impl;
import com.mybatisflex.core.query.QueryWrapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.junit.MockitoJUnitRunner;
import tech.easyflow.approval.entity.ApprovalInstance;
import tech.easyflow.approval.entity.ApprovalLog;
import tech.easyflow.approval.entity.ApprovalTask;
import tech.easyflow.approval.entity.vo.ApprovalSubmitRequest;
import tech.easyflow.approval.enums.ApprovalInstanceStatus;
import tech.easyflow.approval.enums.ApprovalTaskStatus;
import tech.easyflow.approval.mapper.ApprovalInstanceMapper;
import tech.easyflow.approval.mapper.ApprovalLogMapper;
import tech.easyflow.approval.mapper.ApprovalTaskMapper;
import tech.easyflow.approval.service.ApprovalActionFacade;
import tech.easyflow.approval.service.ApprovalMatchService;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.system.entity.SysAccount;
import tech.easyflow.system.service.SysAccountService;
import java.math.BigInteger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* {@link ApprovalInstanceServiceImpl} 审批提交身份授权测试。
*/
@RunWith(MockitoJUnitRunner.class)
public class ApprovalInstanceServiceImplAccessTest {
@Mock
private ApprovalMatchService approvalMatchService;
@Mock
private SysAccountService sysAccountService;
@Mock
private ApprovalInstanceMapper approvalInstanceMapper;
@Mock
private ApprovalTaskMapper approvalTaskMapper;
@Mock
private ApprovalLogMapper approvalLogMapper;
@Mock
private ApprovalActionFacade approvalActionFacade;
@InjectMocks
private ApprovalInstanceServiceImpl service;
/**
* 验证同租户账号也不能代替当前登录人发起审批。
*/
@Test
public void submitApprovalShouldRejectForgedApplicantBeforeMatchingFlow() {
LoginAccount loginAccount = new LoginAccount();
loginAccount.setId(BigInteger.ONE);
loginAccount.setTenantId(BigInteger.valueOf(42));
ApprovalSubmitRequest request = new ApprovalSubmitRequest();
request.setApplicantId(BigInteger.TWO);
try (MockedStatic<SaTokenUtil> saToken = mockStatic(SaTokenUtil.class)) {
saToken.when(SaTokenUtil::getLoginAccount).thenReturn(loginAccount);
BusinessException exception = assertThrows(BusinessException.class,
() -> service.submitApproval(request));
assertEquals(403, exception.getHttpStatus());
}
verify(sysAccountService, never()).getById(BigInteger.TWO);
verify(approvalMatchService, never()).matchFlow(request);
}
/**
* 验证申请人可撤回进行中的审批,并同步结束当前任务与恢复资源状态。
*/
@Test
public void revokeShouldCompleteCurrentTaskForApplicant() {
BigInteger applicantId = BigInteger.valueOf(7);
BigInteger tenantId = BigInteger.valueOf(42);
BigInteger instanceId = BigInteger.valueOf(101);
SysAccount applicant = tenantAccount(applicantId, tenantId);
ApprovalInstance instance = activeInstance(instanceId, applicantId, tenantId);
ApprovalTask task = new ApprovalTask();
task.setInstanceId(instanceId);
task.setStepNo(1);
task.setStatus(ApprovalTaskStatus.PENDING.getCode());
when(sysAccountService.getById(applicantId)).thenReturn(applicant);
when(approvalInstanceMapper.selectOneByQuery(any(QueryWrapper.class))).thenReturn(instance);
when(approvalTaskMapper.selectOneByQuery(any(QueryWrapper.class))).thenReturn(task);
service.revoke(instanceId, "内容需要调整", applicantId);
assertEquals(ApprovalInstanceStatus.REVOKED.getCode(), instance.getStatus());
assertNotNull(instance.getFinishedAt());
assertEquals(ApprovalTaskStatus.REVOKED.getCode(), task.getStatus());
assertEquals(applicantId, task.getActedBy());
assertEquals("内容需要调整", task.getComment());
verify(approvalLogMapper).insert(any(ApprovalLog.class));
verify(approvalActionFacade).handleRevoked(instance, applicantId, "内容需要调整");
}
/**
* 验证非申请人即使属于同一租户也不能撤回审批。
*/
@Test
public void revokeShouldRejectSameTenantNonApplicant() {
BigInteger applicantId = BigInteger.valueOf(7);
BigInteger operatorId = BigInteger.valueOf(8);
BigInteger tenantId = BigInteger.valueOf(42);
BigInteger instanceId = BigInteger.valueOf(101);
when(sysAccountService.getById(operatorId)).thenReturn(tenantAccount(operatorId, tenantId));
when(approvalInstanceMapper.selectOneByQuery(any(QueryWrapper.class)))
.thenReturn(activeInstance(instanceId, applicantId, tenantId));
BusinessException exception = assertThrows(
BusinessException.class,
() -> service.revoke(instanceId, "尝试撤回", operatorId)
);
assertEquals(403, exception.getHttpStatus());
verify(approvalTaskMapper, never()).selectOneByQuery(any(QueryWrapper.class));
verify(approvalActionFacade, never())
.handleRevoked(any(ApprovalInstance.class), any(BigInteger.class), any(String.class));
}
private ApprovalInstance activeInstance(BigInteger instanceId, BigInteger applicantId, BigInteger tenantId) {
ApprovalInstance instance = new ApprovalInstance();
instance.setId(instanceId);
instance.setApplicantId(applicantId);
instance.setTenantId(tenantId);
instance.setCurrentStepNo(1);
instance.setStatus(ApprovalInstanceStatus.PENDING.getCode());
return instance;
}
private SysAccount tenantAccount(BigInteger accountId, BigInteger tenantId) {
SysAccount account = new SysAccount();
account.setId(accountId);
account.setTenantId(tenantId);
return account;
}
}

View File

@@ -0,0 +1,72 @@
package tech.easyflow.approval.service.impl;
import com.mybatisflex.core.query.QueryWrapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import tech.easyflow.approval.entity.ApprovalInstance;
import tech.easyflow.approval.enums.ApprovalInstanceStatus;
import tech.easyflow.approval.mapper.ApprovalInstanceMapper;
import tech.easyflow.approval.mapper.ApprovalTaskMapper;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.system.entity.SysAccount;
import tech.easyflow.system.service.SysAccountService;
import java.math.BigInteger;
import java.util.Locale;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* {@link ApprovalInstanceServiceImpl} 审批决策并发互斥测试。
*/
@RunWith(MockitoJUnitRunner.class)
public class ApprovalInstanceServiceImplConcurrencyTest {
@Mock
private ApprovalInstanceMapper approvalInstanceMapper;
@Mock
private ApprovalTaskMapper approvalTaskMapper;
@Mock
private SysAccountService sysAccountService;
@InjectMocks
private ApprovalInstanceServiceImpl service;
/**
* 审批实例与当前任务必须在状态判断前加行锁,避免重复执行同一决策。
*/
@Test
public void approvalDecisionLocksInstanceAndCurrentTask() {
BigInteger instanceId = BigInteger.valueOf(101);
BigInteger tenantId = BigInteger.valueOf(42);
ApprovalInstance instance = new ApprovalInstance();
instance.setId(instanceId);
instance.setTenantId(tenantId);
instance.setStatus(ApprovalInstanceStatus.PENDING.getCode());
instance.setCurrentStepNo(1);
SysAccount operator = new SysAccount();
operator.setId(BigInteger.ONE);
operator.setTenantId(tenantId);
when(sysAccountService.getById(BigInteger.ONE)).thenReturn(operator);
when(approvalInstanceMapper.selectOneByQuery(any(QueryWrapper.class))).thenReturn(instance);
when(approvalTaskMapper.selectOneByQuery(any(QueryWrapper.class))).thenReturn(null);
assertThrows(BusinessException.class,
() -> service.approve(instanceId, "通过", BigInteger.ONE));
ArgumentCaptor<QueryWrapper> instanceQuery = ArgumentCaptor.forClass(QueryWrapper.class);
ArgumentCaptor<QueryWrapper> taskQuery = ArgumentCaptor.forClass(QueryWrapper.class);
verify(approvalInstanceMapper).selectOneByQuery(instanceQuery.capture());
verify(approvalTaskMapper).selectOneByQuery(taskQuery.capture());
assertTrue(instanceQuery.getValue().toSQL().toLowerCase(Locale.ROOT).contains("for update"));
assertTrue(instanceQuery.getValue().toSQL().toLowerCase(Locale.ROOT).contains("tenant_id"));
assertTrue(taskQuery.getValue().toSQL().toLowerCase(Locale.ROOT).contains("for update"));
}
}

View File

@@ -0,0 +1,55 @@
package tech.easyflow.approval.service.impl;
import org.junit.Test;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.junit.Assert.assertTrue;
/**
* V32 审批实例租户迁移契约测试。
*/
public class ApprovalInstanceTenantMigrationContractTest {
/**
* 验证历史实例从申请人账号回填租户,且空租户会阻断迁移。
*
* @throws Exception 迁移文件不可读时抛出
*/
@Test
public void migrationShouldBackfillAndGuardApprovalTenant() throws Exception {
String sql = migrationSql();
assertTrue(sql.contains("ADD COLUMN `tenant_id` BIGINT UNSIGNED NULL"));
assertTrue(sql.contains("LEFT JOIN `tb_sys_account` applicant ON applicant.`id` = approval.`applicant_id`"));
assertTrue(sql.contains("applicant.`id` IS NULL OR applicant.`tenant_id` IS NULL"));
assertTrue(sql.contains("JOIN `tb_sys_account` applicant ON applicant.`id` = approval.`applicant_id`"));
assertTrue(sql.contains("SET approval.`tenant_id` = applicant.`tenant_id`"));
assertTrue(sql.contains("tmp_approval_instance_tenant_guard"));
assertTrue(sql.indexOf("tmp_approval_instance_tenant_guard") < sql.indexOf("ADD COLUMN `tenant_id`"));
assertTrue(sql.contains("MODIFY COLUMN `tenant_id` BIGINT UNSIGNED NOT NULL"));
assertTrue(sql.contains("`tenant_id`, `status`, `submitted_at`"));
}
/**
* 读取工作区中的 V32 MySQL 迁移。
*
* @return 迁移 SQL
* @throws Exception 迁移文件不存在或不可读时抛出
*/
private String migrationSql() throws Exception {
Path root = Path.of(System.getProperty("maven.multiModuleProjectDirectory",
Path.of(System.getProperty("user.dir")).toAbsolutePath().toString()));
while (root != null) {
Path migration = root.resolve("easyflow-starter/easyflow-starter-all/src/main/resources/"
+ "db/migration/mysql/V32__mysql_approval_instance_tenant.sql");
if (Files.isRegularFile(migration)) {
return Files.readString(migration, StandardCharsets.UTF_8);
}
root = root.getParent();
}
throw new IllegalStateException("找不到 V32 审批实例租户迁移");
}
}

View File

@@ -0,0 +1,283 @@
package tech.easyflow.approval.service.impl;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.junit.MockitoJUnitRunner;
import tech.easyflow.approval.entity.ApprovalInstance;
import tech.easyflow.approval.entity.ApprovalLog;
import tech.easyflow.approval.entity.ApprovalTask;
import tech.easyflow.approval.entity.vo.ApprovalInstanceDetailVo;
import tech.easyflow.approval.entity.vo.ApprovalInstancePageVo;
import tech.easyflow.approval.enums.ApprovalAssigneeType;
import tech.easyflow.approval.enums.ApprovalEventType;
import tech.easyflow.approval.enums.ApprovalInstanceStatus;
import tech.easyflow.approval.enums.ApprovalTaskStatus;
import tech.easyflow.approval.mapper.ApprovalFlowStepMapper;
import tech.easyflow.approval.mapper.ApprovalInstanceMapper;
import tech.easyflow.approval.mapper.ApprovalLogMapper;
import tech.easyflow.approval.mapper.ApprovalTaskMapper;
import tech.easyflow.approval.service.ApprovalActionFacade;
import tech.easyflow.approval.service.ApprovalAssigneeService;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.system.entity.SysAccount;
import tech.easyflow.system.service.CategoryPermissionService;
import tech.easyflow.system.service.SysAccountService;
import java.math.BigInteger;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.mockStatic;
/**
* {@link ApprovalQueryServiceImpl} 审批详情租户和主体授权回归测试。
*/
@RunWith(MockitoJUnitRunner.class)
public class ApprovalQueryServiceImplAccessTest {
private static final BigInteger INSTANCE_ID = BigInteger.valueOf(101);
private static final BigInteger RESOURCE_ID = BigInteger.valueOf(501);
private static final BigInteger TENANT_ID = BigInteger.valueOf(42);
@Mock
private ApprovalInstanceMapper approvalInstanceMapper;
@Mock
private ApprovalTaskMapper approvalTaskMapper;
@Mock
private ApprovalLogMapper approvalLogMapper;
@Mock
private ApprovalFlowStepMapper approvalFlowStepMapper;
@Mock
private ApprovalAssigneeService approvalAssigneeService;
@Mock
private ApprovalActionFacade approvalActionFacade;
@Mock
private CategoryPermissionService categoryPermissionService;
@Mock
private SysAccountService sysAccountService;
@InjectMocks
private ApprovalQueryServiceImpl service;
/**
* 验证详情查询显式带租户条件,并拒绝 Mapper 异常返回的跨租户实例。
*/
@Test
public void detailShouldRejectCrossTenantInstanceBeforeReadingSnapshot() {
LoginAccount account = account(7, 42);
ApprovalInstance instance = instance(99, 99);
when(approvalInstanceMapper.selectOneByQuery(any(QueryWrapper.class))).thenReturn(instance);
try (MockedStatic<SaTokenUtil> saToken = mockStatic(SaTokenUtil.class)) {
saToken.when(SaTokenUtil::getLoginAccount).thenReturn(account);
BusinessException exception = assertThrows(BusinessException.class,
() -> service.detail(INSTANCE_ID));
assertEquals(404, exception.getHttpStatus());
}
ArgumentCaptor<QueryWrapper> query = ArgumentCaptor.forClass(QueryWrapper.class);
verify(approvalInstanceMapper).selectOneByQuery(query.capture());
assertTrue(query.getValue().toSQL().toLowerCase(Locale.ROOT).contains("tenant_id"));
verify(approvalTaskMapper, never()).selectListByQuery(any(QueryWrapper.class));
verify(approvalLogMapper, never()).selectListByQuery(any(QueryWrapper.class));
}
/**
* 验证同租户普通用户不能仅凭审批查询操作权限读取完整资源快照。
*/
@Test
public void detailShouldRejectSameTenantNonParticipant() {
LoginAccount account = account(7, 42);
ApprovalInstance instance = instance(8, 42);
when(approvalInstanceMapper.selectOneByQuery(any(QueryWrapper.class))).thenReturn(instance);
when(approvalTaskMapper.selectListByQuery(any(QueryWrapper.class))).thenReturn(List.of());
when(approvalAssigneeService.getAvailableRoleIds(account.getId())).thenReturn(Set.of());
when(approvalActionFacade.canAccessApprovalDetail("SKILL", RESOURCE_ID)).thenReturn(false);
try (MockedStatic<SaTokenUtil> saToken = mockStatic(SaTokenUtil.class)) {
saToken.when(SaTokenUtil::getLoginAccount).thenReturn(account);
BusinessException exception = assertThrows(BusinessException.class,
() -> service.detail(INSTANCE_ID));
assertEquals(403, exception.getHttpStatus());
}
verify(approvalLogMapper, never()).selectListByQuery(any(QueryWrapper.class));
}
/**
* 验证申请人仍可读取自己发起的审批快照。
*/
@Test
public void detailShouldAllowApplicant() {
LoginAccount account = account(7, 42);
ApprovalInstance instance = instance(7, 42);
Map<String, Object> snapshot = instance.getSnapshotJson();
stubAuthorizedDetail(instance, account, List.of());
try (MockedStatic<SaTokenUtil> saToken = mockStatic(SaTokenUtil.class)) {
saToken.when(SaTokenUtil::getLoginAccount).thenReturn(account);
ApprovalInstanceDetailVo detail = service.detail(INSTANCE_ID);
assertSame(snapshot, detail.getSnapshotJson());
assertFalse(detail.isCanApprove());
assertFalse(detail.isCanReject());
assertTrue(detail.isCanRevoke());
}
}
/**
* 验证当前待办处理人可查看审批详情。
*/
@Test
public void detailShouldAllowCurrentTaskHandler() {
LoginAccount account = account(7, 42);
ApprovalInstance instance = instance(8, 42);
ApprovalTask task = task(ApprovalTaskStatus.PENDING.getCode(), null);
task.setAssigneeType(ApprovalAssigneeType.USER.getCode());
task.setAssigneeTargetId(account.getId());
stubAuthorizedDetail(instance, account, List.of(task));
when(approvalAssigneeService.canHandleTask(task, account.getId(), Set.of())).thenReturn(true);
try (MockedStatic<SaTokenUtil> saToken = mockStatic(SaTokenUtil.class)) {
saToken.when(SaTokenUtil::getLoginAccount).thenReturn(account);
ApprovalInstanceDetailVo detail = service.detail(INSTANCE_ID);
assertEquals(INSTANCE_ID, detail.getId());
assertTrue(detail.isCanApprove());
assertTrue(detail.isCanReject());
assertFalse(detail.isCanRevoke());
}
}
/**
* 验证实际处理过历史步骤的用户仍可查看审批详情。
*/
@Test
public void detailShouldAllowHistoricalActor() {
LoginAccount account = account(7, 42);
ApprovalInstance instance = instance(8, 42);
ApprovalTask task = task(ApprovalTaskStatus.APPROVED.getCode(), account.getId());
stubAuthorizedDetail(instance, account, List.of(task));
try (MockedStatic<SaTokenUtil> saToken = mockStatic(SaTokenUtil.class)) {
saToken.when(SaTokenUtil::getLoginAccount).thenReturn(account);
assertEquals(INSTANCE_ID, service.detail(INSTANCE_ID).getId());
}
}
/**
* 验证审批说明在详情、审批任务和提交日志中完整透传。
*/
@Test
public void detailShouldExposeApplicationReasonAcrossRelatedViews() {
LoginAccount account = account(7, 42);
ApprovalInstance instance = instance(7, 42);
instance.setApplicationReason("发布新的问答流程");
ApprovalTask task = task(ApprovalTaskStatus.PENDING.getCode(), null);
ApprovalLog log = new ApprovalLog();
log.setEventType(ApprovalEventType.SUBMITTED.getCode());
stubAuthorizedDetail(instance, account, List.of(task));
when(approvalLogMapper.selectListByQuery(any(QueryWrapper.class))).thenReturn(List.of(log));
try (MockedStatic<SaTokenUtil> saToken = mockStatic(SaTokenUtil.class)) {
saToken.when(SaTokenUtil::getLoginAccount).thenReturn(account);
ApprovalInstanceDetailVo detail = service.detail(INSTANCE_ID);
assertEquals("发布新的问答流程", detail.getApplicationReason());
assertEquals("发布新的问答流程", detail.getTasks().get(0).getApplicationReason());
assertEquals("发布新的问答流程", detail.getLogs().get(0).getApplicationReason());
}
}
/**
* 验证审批分页列表返回申请人填写的审批说明和账号信息。
*/
@Test
public void initiatedPageShouldExposeApplicationReasonAndApplicant() {
LoginAccount account = account(7, 42);
ApprovalInstance instance = instance(7, 42);
instance.setApplicationReason("发布新的问答流程");
SysAccount applicant = new SysAccount();
applicant.setId(account.getId());
applicant.setNickname("陈子默");
applicant.setLoginName("czm");
Page<ApprovalInstance> page = new Page<>(List.of(instance), 1L, 10L, 1L);
when(approvalInstanceMapper.paginate(anyLong(), anyLong(), any(QueryWrapper.class))).thenReturn(page);
when(sysAccountService.list(any(QueryWrapper.class))).thenReturn(List.of(applicant));
try (MockedStatic<SaTokenUtil> saToken = mockStatic(SaTokenUtil.class)) {
saToken.when(SaTokenUtil::getLoginAccount).thenReturn(account);
ApprovalInstancePageVo item = service.initiatedPage(null, null, null, 1L, 10L)
.getRecords()
.get(0);
assertEquals("发布新的问答流程", item.getApplicationReason());
assertEquals("陈子默", item.getApplicantName());
assertEquals("czm", item.getApplicantAccount());
assertTrue(item.isCanRevoke());
assertFalse(item.isCanApprove());
assertFalse(item.isCanReject());
}
}
private void stubAuthorizedDetail(ApprovalInstance instance, LoginAccount account, List<ApprovalTask> tasks) {
when(approvalInstanceMapper.selectOneByQuery(any(QueryWrapper.class))).thenReturn(instance);
when(approvalTaskMapper.selectListByQuery(any(QueryWrapper.class))).thenReturn(tasks);
when(approvalAssigneeService.getAvailableRoleIds(account.getId())).thenReturn(Set.of());
when(approvalLogMapper.selectListByQuery(any(QueryWrapper.class))).thenReturn(List.of());
when(sysAccountService.list(any(QueryWrapper.class))).thenReturn(List.of());
}
private ApprovalInstance instance(long applicantId, long tenantId) {
ApprovalInstance instance = new ApprovalInstance();
instance.setId(INSTANCE_ID);
instance.setTenantId(BigInteger.valueOf(tenantId));
instance.setFlowId(BigInteger.valueOf(301));
instance.setFlowVersion(1);
instance.setResourceType("SKILL");
instance.setResourceId(RESOURCE_ID);
instance.setActionType("PUBLISH");
instance.setStatus(ApprovalInstanceStatus.PENDING.getCode());
instance.setCurrentStepNo(1);
instance.setApplicantId(BigInteger.valueOf(applicantId));
instance.setSnapshotJson(Map.of(
"resourceSnapshot", Map.of("skillContent", "private prompt"),
"steps", List.of(Map.of(
"stepNo", 1,
"stepName", "审核",
"assigneeType", ApprovalAssigneeType.USER.getCode(),
"assigneeTargetId", 7))));
return instance;
}
private ApprovalTask task(String status, BigInteger actedBy) {
ApprovalTask task = new ApprovalTask();
task.setInstanceId(INSTANCE_ID);
task.setStepNo(1);
task.setStatus(status);
task.setActedBy(actedBy);
return task;
}
private LoginAccount account(long accountId, long tenantId) {
LoginAccount account = new LoginAccount();
account.setId(BigInteger.valueOf(accountId));
account.setTenantId(BigInteger.valueOf(tenantId));
return account;
}
}