Skip to content

Commit 7ad67bf

Browse files
authored
Merge pull request #149 from bpm-crafters/feat/issue-268
Support start process instance at element
2 parents 5b5437d + de9eab0 commit 7ad67bf

File tree

9 files changed

+283
-7
lines changed

9 files changed

+283
-7
lines changed

engine-adapter/adapter-testing/src/main/kotlin/dev/bpmcrafters/processengineapi/test/BaseGivenWhenStage.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package dev.bpmcrafters.processengineapi.test
33
import com.tngtech.jgiven.Stage
44
import com.tngtech.jgiven.annotation.ExpectedScenarioState
55
import com.tngtech.jgiven.annotation.ProvidedScenarioState
6+
import dev.bpmcrafters.processengineapi.process.StartProcessByDefinitionAtElementCmd
67
import dev.bpmcrafters.processengineapi.process.StartProcessByDefinitionCmd
78
import dev.bpmcrafters.processengineapi.process.StartProcessByMessageCmd
89
import dev.bpmcrafters.processengineapi.task.*
@@ -67,6 +68,16 @@ class BaseGivenWhenStage : Stage<BaseGivenWhenStage>() {
6768
).get().instanceId
6869
}
6970

71+
fun `start process by definition at element`(definitionKey: String, elementId: String) = step {
72+
instanceId = processTestHelper.getStartProcessApi().startProcess(
73+
StartProcessByDefinitionAtElementCmd(
74+
definitionKey = definitionKey,
75+
elementId = elementId,
76+
payloadSupplier = { emptyMap() },
77+
)
78+
).get().instanceId
79+
}
80+
7081
fun `a active user task subscription`(taskDescriptionKey: String) = step {
7182
taskSubscription = subscribeTask(TaskType.USER, taskDescriptionKey) { taskInformation, _ ->
7283
run {

engine-adapter/c7-embedded-core/src/main/kotlin/dev/bpmcrafters/processengineapi/adapter/c7/embedded/process/StartProcessApiImpl.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,23 @@ class StartProcessApiImpl(
6868
.toProcessInformation()
6969
}
7070

71+
is StartProcessByDefinitionAtElementCmd ->
72+
CompletableFuture.supplyAsync {
73+
logger.debug { "PROCESS-ENGINE-C7-EMBEDDED-006: starting a new process instance by definition ${cmd.definitionKey} at element ${cmd.elementId}" }
74+
val startProcessCommand = StartProcessByDefinitionCmd(
75+
definitionKey = cmd.definitionKey,
76+
payloadSupplier = cmd.payloadSupplier,
77+
restrictions = cmd.restrictions,
78+
)
79+
val instance = this.startProcess(startProcessCommand).get()
80+
val processDefinitionId = instance.meta[CommonRestrictions.PROCESS_DEFINITION_KEY] as String
81+
runtimeService.createModification(processDefinitionId)
82+
.processInstanceIds(instance.instanceId)
83+
.startBeforeActivity(cmd.elementId)
84+
.execute()
85+
instance
86+
}
87+
7188
else -> throw IllegalArgumentException("Unsupported start command $cmd")
7289
}
7390
}

engine-adapter/c7-embedded-core/src/test/kotlin/dev/bpmcrafters/processengineapi/adapter/c7/embedded/process/C7EmbeddedStartProcessApiITest.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,12 @@ class C7EmbeddedStartProcessApiITest : AbstractC7EmbeddedApiITest(C7EmbeddedProc
5353
.`we should have a running process`()
5454
}
5555

56+
@Test
57+
fun `should start process at element`() {
58+
WHEN
59+
.`start process by definition at element`(KEY, "service-do-action2")
60+
61+
THEN
62+
.`we should have a running process`()
63+
}
5664
}

engine-adapter/c7-embedded-core/src/test/kotlin/dev/bpmcrafters/processengineapi/adapter/c7/embedded/process/StartProcessApiImplTest.kt

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package dev.bpmcrafters.processengineapi.adapter.c7.embedded.process
22

33
import dev.bpmcrafters.processengineapi.CommonRestrictions
4+
import dev.bpmcrafters.processengineapi.process.StartProcessByDefinitionAtElementCmd
45
import dev.bpmcrafters.processengineapi.process.StartProcessByDefinitionCmd
56
import dev.bpmcrafters.processengineapi.process.StartProcessByMessageCmd
67
import org.camunda.bpm.engine.RepositoryService
78
import org.camunda.bpm.engine.RuntimeService
89
import org.camunda.bpm.engine.runtime.MessageCorrelationBuilder
10+
import org.camunda.bpm.engine.runtime.ModificationBuilder
911
import org.camunda.bpm.engine.runtime.ProcessInstance
1012
import org.camunda.community.mockito.QueryMocks
1113
import org.camunda.community.mockito.process.ProcessDefinitionFake
@@ -24,6 +26,7 @@ class StartProcessApiImplTest {
2426

2527
@Mock
2628
private lateinit var repositoryService: RepositoryService
29+
2730
@Mock
2831
private lateinit var runtimeService: RuntimeService
2932

@@ -135,6 +138,36 @@ class StartProcessApiImplTest {
135138
verify(correlationBuilder, times(0)).processInstanceBusinessKey(any())
136139
}
137140

141+
@Test
142+
fun `should start process at element without payload`() {
143+
144+
// given
145+
val businessKey = "myBusinessKey"
146+
val modificationBuilder = modifyProcessInstanceBuilderMock()
147+
val processDefinitionId = "simple-process:1:123"
148+
val processInstance = ProcessInstanceFake.builder().id("instance-123")
149+
.processDefinitionId(processDefinitionId)
150+
.build()
151+
152+
whenever(runtimeService.startProcessInstanceByKey(anyString(), anyOrNull(), anyMap())).thenReturn(processInstance)
153+
whenever(runtimeService.createModification(processDefinitionId)).thenReturn(modificationBuilder)
154+
155+
val cmd = StartProcessByDefinitionAtElementCmd(
156+
definitionKey = "simple-process",
157+
elementId = "user-perform-task",
158+
payloadSupplier = { mapOf(CommonRestrictions.BUSINESS_KEY to businessKey) }
159+
)
160+
161+
// when
162+
startProcessApi.startProcess(cmd).get()
163+
164+
// then
165+
verify(runtimeService).startProcessInstanceByKey("simple-process", businessKey, mapOf("businessKey" to businessKey))
166+
verify(runtimeService).createModification(processDefinitionId)
167+
verify(modificationBuilder).startBeforeActivity("user-perform-task")
168+
verify(modificationBuilder).execute()
169+
}
170+
138171
private fun messageCorrelationMock(): MessageCorrelationBuilder {
139172
val builder: MessageCorrelationBuilder = mock()
140173
lenient().whenever(builder.processInstanceBusinessKey(any())).thenReturn(builder)
@@ -143,4 +176,14 @@ class StartProcessApiImplTest {
143176

144177
return builder
145178
}
179+
180+
private fun modifyProcessInstanceBuilderMock(): ModificationBuilder {
181+
val builder = mock<ModificationBuilder>()
182+
183+
whenever(builder.processInstanceIds(any<String>())).thenReturn(builder)
184+
whenever(builder.startBeforeActivity(any())).thenReturn(builder)
185+
doNothing().`when`(builder).execute()
186+
187+
return builder
188+
}
146189
}

engine-adapter/c7-embedded-spring-boot-starter/src/test/kotlin/dev/bpmcrafters/processengineapi/adapter/c7/embedded/springboot/C7EmbeddedSpringStartProcessApiITest.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,13 @@ class C7EmbeddedSpringStartProcessApiITest(
4646
.`we should have a running process`()
4747
}
4848

49+
@Test
50+
fun `should start process at element`() {
51+
WHEN
52+
.`start process by definition at element`(KEY, "service-do-action2")
53+
54+
THEN
55+
.`we should have a running process`()
56+
}
57+
4958
}

engine-adapter/c7-remote-core/src/main/kotlin/dev/bpmcrafters/processengineapi/adapter/c7/remote/process/StartProcessApiImpl.kt

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,7 @@ class StartProcessApiImpl(
2929
ensureSupported(cmd.restrictions)
3030
val payload = cmd.payloadSupplier.get()
3131
val tenantId = cmd.restrictions[CommonRestrictions.TENANT_ID]
32-
val processDefinitionId = requireNotNull(
33-
processDefinitionMetaDataResolver.getProcessDefinitionId(
34-
processDefinitionKey = cmd.definitionKey,
35-
tenantId = tenantId
36-
)
37-
) { "Could not find process definition id for key ${cmd.definitionKey} and tenant $tenantId." }
32+
val processDefinitionId = getProcessDefinitionId(cmd.definitionKey, tenantId)
3833

3934
val instance = processDefinitionApiClient.startProcessInstance(
4035
processDefinitionId,
@@ -80,6 +75,27 @@ class StartProcessApiImpl(
8075
}
8176
}
8277

78+
is StartProcessByDefinitionAtElementCmd ->
79+
CompletableFuture.supplyAsync {
80+
logger.debug { "PROCESS-ENGINE-C7-REMOTE-006: starting a new process instance by definition ${cmd.definitionKey} at element ${cmd.elementId}" }
81+
val payload = cmd.payloadSupplier.get()
82+
val tenantId = cmd.restrictions[CommonRestrictions.TENANT_ID]
83+
val processDefinitionId = getProcessDefinitionId(cmd.definitionKey, tenantId)
84+
85+
val startInstructionDto = ProcessInstanceModificationInstructionDto()
86+
startInstructionDto.type = ProcessInstanceModificationInstructionDto.TypeEnum.START_BEFORE_ACTIVITY
87+
startInstructionDto.activityId = cmd.elementId
88+
89+
val startProcessInstanceDto = StartProcessInstanceDto()
90+
startProcessInstanceDto.businessKey = payload[CommonRestrictions.BUSINESS_KEY]?.toString()
91+
startProcessInstanceDto.variables = valueMapper.mapValues(payload)
92+
startProcessInstanceDto.startInstructions(listOf(startInstructionDto))
93+
94+
val instance = processDefinitionApiClient.startProcessInstance(processDefinitionId, startProcessInstanceDto)
95+
val processInformation = instance.body?.toProcessInformation()
96+
requireNotNull(processInformation) { "Could not start process instance ${cmd.definitionKey}, resulting status was ${instance.statusCode}" }
97+
}
98+
8399
else -> throw IllegalArgumentException("Unsupported start command $cmd")
84100
}
85101
}
@@ -91,6 +107,12 @@ class StartProcessApiImpl(
91107
override fun meta(instance: MetaInfoAware): MetaInfo {
92108
TODO()
93109
}
110+
111+
private fun getProcessDefinitionId(processKey: String, tenantId: String?): String {
112+
val definitionId = processDefinitionMetaDataResolver.getProcessDefinitionId(processDefinitionKey = processKey, tenantId = tenantId)
113+
return requireNotNull(definitionId) { "Could not find process definition id for key $processKey and tenant $tenantId." }
114+
}
115+
94116
}
95117

96118
fun MessageCorrelationResultWithVariableDto.toProcessInformation() = ProcessInformation(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package dev.bpmcrafters.processengineapi.adapter.c7.remote.process
2+
3+
import dev.bpmcrafters.processengineapi.CommonRestrictions
4+
import dev.bpmcrafters.processengineapi.adapter.c7.remote.TestFixtures
5+
import dev.bpmcrafters.processengineapi.process.StartProcessByDefinitionAtElementCmd
6+
import org.camunda.community.rest.client.api.MessageApiClient
7+
import org.camunda.community.rest.client.api.ProcessDefinitionApiClient
8+
import org.camunda.community.rest.client.model.ProcessDefinitionDto
9+
import org.camunda.community.rest.client.model.ProcessInstanceModificationInstructionDto
10+
import org.camunda.community.rest.client.model.ProcessInstanceModificationInstructionDto.TypeEnum.START_BEFORE_ACTIVITY
11+
import org.camunda.community.rest.client.model.ProcessInstanceWithVariablesDto
12+
import org.camunda.community.rest.client.model.StartProcessInstanceDto
13+
import org.camunda.community.rest.variables.ValueMapper
14+
import org.junit.jupiter.api.BeforeEach
15+
import org.junit.jupiter.api.Test
16+
import org.junit.jupiter.api.extension.ExtendWith
17+
import org.mockito.InjectMocks
18+
import org.mockito.Mockito.*
19+
import org.mockito.Spy
20+
import org.mockito.junit.jupiter.MockitoExtension
21+
import org.mockito.kotlin.verify
22+
import org.mockito.kotlin.whenever
23+
import org.springframework.http.ResponseEntity
24+
25+
@ExtendWith(MockitoExtension::class)
26+
class StartProcessApiImplByDefinitionAtElementTest {
27+
28+
@Suppress("unused")
29+
private val messageApiClient: MessageApiClient = mock()
30+
private val processDefinitionApiClient: ProcessDefinitionApiClient = mock()
31+
32+
@Spy
33+
private val valueMapper: ValueMapper = TestFixtures.valueMapper()
34+
35+
@Spy
36+
@Suppress("unused")
37+
private val processDefinitionMetaDataResolver = CachingProcessDefinitionMetaDataResolver(
38+
processDefinitionApiClient
39+
)
40+
41+
@InjectMocks
42+
private lateinit var startProcessApi: StartProcessApiImpl
43+
44+
@BeforeEach
45+
fun `setup mock`() {
46+
47+
val instance = ProcessInstanceWithVariablesDto()
48+
instance.id = "instanceId"
49+
50+
val processDefinition = ProcessDefinitionDto()
51+
.versionTag("versionTag")
52+
.id("definitionId")
53+
.key("definitionKey")
54+
.tenantId("tenantId")
55+
56+
whenever(processDefinitionApiClient.getLatestProcessDefinitionByTenantId(anyString(), any())).thenReturn(
57+
ResponseEntity.ok(processDefinition)
58+
)
59+
60+
whenever(processDefinitionApiClient.getProcessDefinitionByKey(anyString())).thenReturn(
61+
ResponseEntity.ok(processDefinition)
62+
)
63+
64+
whenever(processDefinitionApiClient.startProcessInstance(anyString(), any())).thenReturn(
65+
ResponseEntity.ok(instance)
66+
)
67+
}
68+
69+
@Test
70+
fun `should start process at element without payload`() {
71+
72+
// given
73+
val startProcessByDefinitionAtElementCmd = StartProcessByDefinitionAtElementCmd(
74+
definitionKey = "definitionKey",
75+
elementId = "myActivity",
76+
payloadSupplier = { emptyMap() },
77+
restrictions = mapOf()
78+
)
79+
80+
// when
81+
startProcessApi.startProcess(startProcessByDefinitionAtElementCmd).get()
82+
83+
// then
84+
verify(processDefinitionApiClient).startProcessInstance(
85+
"definitionId",
86+
StartProcessInstanceDto().apply {
87+
this.variables = mapOf()
88+
this.startInstructions = listOf(
89+
ProcessInstanceModificationInstructionDto().apply {
90+
this.type = START_BEFORE_ACTIVITY
91+
this.activityId = "myActivity"
92+
}
93+
)
94+
}
95+
)
96+
}
97+
98+
@Test
99+
fun `should start process at element with tenant without payload`() {
100+
// given
101+
val startProcessByDefinitionAtElementCmd = StartProcessByDefinitionAtElementCmd(
102+
definitionKey = "definitionKey",
103+
elementId = "userTask1",
104+
payloadSupplier = { emptyMap() },
105+
restrictions = mapOf(
106+
CommonRestrictions.TENANT_ID to "tenantId"
107+
)
108+
)
109+
110+
// when
111+
startProcessApi.startProcess(startProcessByDefinitionAtElementCmd).get()
112+
113+
// then
114+
verify(processDefinitionApiClient).startProcessInstance(
115+
"definitionId",
116+
StartProcessInstanceDto().apply {
117+
this.variables = mapOf()
118+
this.startInstructions = listOf(
119+
ProcessInstanceModificationInstructionDto().apply {
120+
this.type = START_BEFORE_ACTIVITY
121+
this.activityId = "userTask1"
122+
}
123+
)
124+
}
125+
)
126+
}
127+
128+
@Test
129+
fun `should start process at element with payload and business key`() {
130+
// given
131+
val startProcessByDefinitionAtElementCmd = StartProcessByDefinitionAtElementCmd(
132+
definitionKey = "definitionKey",
133+
elementId = "serviceTask1",
134+
payloadSupplier = { mapOf("key" to "value", CommonRestrictions.BUSINESS_KEY to "businessKey") },
135+
restrictions = mapOf()
136+
)
137+
138+
// when
139+
startProcessApi.startProcess(startProcessByDefinitionAtElementCmd).get()
140+
141+
// then
142+
val variables = mapOf("key" to "value", CommonRestrictions.BUSINESS_KEY to "businessKey")
143+
verify(processDefinitionApiClient).startProcessInstance(
144+
"definitionId",
145+
StartProcessInstanceDto().apply {
146+
this.businessKey = "businessKey"
147+
this.variables = valueMapper.mapValues(variables)
148+
this.startInstructions = listOf(
149+
ProcessInstanceModificationInstructionDto().apply {
150+
this.type = START_BEFORE_ACTIVITY
151+
this.activityId = "serviceTask1"
152+
}
153+
)
154+
}
155+
)
156+
}
157+
158+
}

engine-adapter/c7-remote-core/src/test/kotlin/dev/bpmcrafters/processengineapi/adapter/c7/remote/process/StartProcessApiImplByMessageTest.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,5 +106,4 @@ class StartProcessApiImplByMessageTest {
106106
assertThat(exception.cause!!.message).isEqualTo("Only $expected are supported but businessKey were found.")
107107
}
108108

109-
110109
}

engine-adapter/c7-remote-spring-boot-starter/src/test/kotlin/dev/bpmcrafters/processengineapi/adapter/c7/remote/springboot/C7RemoteStartProcessApiITest.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,13 @@ class C7RemoteStartProcessApiITest : AbstractC7RemoteApiITestBase() {
4444
.`we should have a running process`()
4545
}
4646

47+
@Test
48+
fun `should start process at element`() {
49+
WHEN
50+
.`start process by definition at element`(KEY, "service-do-action2")
51+
52+
THEN
53+
.`we should have a running process`()
54+
}
55+
4756
}

0 commit comments

Comments
 (0)