Skip to content

Commit 4f6f4c2

Browse files
feat: add integration test for transactional and non-transactional worker with idempotency
1 parent 6ff8b39 commit 4f6f4c2

File tree

12 files changed

+133
-8
lines changed

12 files changed

+133
-8
lines changed

spring-boot-starter/src/main/kotlin/dev/bpmcrafters/processengine/worker/configuration/ProcessEngineWorkerAutoConfiguration.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package dev.bpmcrafters.processengine.worker.configuration
33
import com.fasterxml.jackson.databind.ObjectMapper
44
import dev.bpmcrafters.processengine.worker.idempotency.IdempotencyRegistry
55
import dev.bpmcrafters.processengine.worker.idempotency.InMemoryIdempotencyRegistry
6+
import dev.bpmcrafters.processengine.worker.idempotency.NoOpIdempotencyRegistry
67
import dev.bpmcrafters.processengine.worker.registrar.*
78
import dev.bpmcrafters.processengine.worker.registrar.metrics.ProcessEngineWorkerMetricsMicrometer
89
import dev.bpmcrafters.processengine.worker.registrar.metrics.ProcessEngineWorkerMetricsNoOp
@@ -69,10 +70,10 @@ class ProcessEngineWorkerAutoConfiguration {
6970
}
7071

7172
/**
72-
* Fallback to in-memory idempotency repository.
73+
* Fallback to no-op idempotency registry.
7374
*/
7475
@Bean
7576
@ConditionalOnMissingBean(IdempotencyRegistry::class)
76-
fun idempotencyRegistryFallback(): IdempotencyRegistry = InMemoryIdempotencyRegistry()
77+
fun idempotencyRegistry(): IdempotencyRegistry = NoOpIdempotencyRegistry()
7778

7879
}

spring-boot-starter/src/main/kotlin/dev/bpmcrafters/processengine/worker/idempotency/InMemoryIdempotencyRegistry.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import java.util.concurrent.ConcurrentHashMap
88

99
/**
1010
* In-memory local implementation of the registry.
11+
*
12+
* Usage is discouraged because it can only take into account a single process instance.
13+
* I.e., don't use it in a clustered environment.
1114
*/
1215
class InMemoryIdempotencyRegistry : IdempotencyRegistry {
1316

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package dev.bpmcrafters.processengine.worker.idempotency
2+
3+
import dev.bpmcrafters.processengineapi.task.TaskInformation
4+
5+
class NoOpIdempotencyRegistry : IdempotencyRegistry {
6+
7+
override fun register(taskInformation: TaskInformation, result: Map<String, Any?>) {}
8+
9+
override fun getTaskResult(taskInformation: TaskInformation): Map<String, Any?>? = null
10+
11+
}

spring-boot-starter/src/test/kotlin/dev/bpmcrafters/processengine/worker/itest/camunda7/external/AbstractTransactionalBehaviorTest.kt renamed to spring-boot-starter/src/test/kotlin/dev/bpmcrafters/processengine/worker/itest/camunda7/external/AbstractBehaviorTest.kt

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@ import dev.bpmcrafters.processengineapi.process.StartProcessByDefinitionCmd
1313
import org.assertj.core.api.Assertions.assertThat
1414
import org.camunda.community.rest.client.api.ExternalTaskApiClient
1515
import org.camunda.community.rest.client.api.HistoryApiClient
16+
import org.camunda.community.rest.client.api.IncidentApiClient
1617
import org.camunda.community.rest.client.api.ProcessInstanceApiClient
1718
import org.camunda.community.rest.client.model.ExternalTaskDto
1819
import org.camunda.community.rest.client.model.ExternalTaskQueryDto
1920
import org.camunda.community.rest.client.model.HistoricActivityInstanceDto
2021
import org.camunda.community.rest.client.model.HistoricActivityInstanceQueryDto
22+
import org.camunda.community.rest.client.model.IncidentDto
2123
import org.junit.jupiter.api.AfterEach
2224
import org.junit.jupiter.api.BeforeEach
2325
import org.springframework.beans.factory.annotation.Autowired
@@ -31,7 +33,7 @@ import org.testcontainers.junit.jupiter.Testcontainers
3133
@SpringBootTest(classes = [TestApplication::class])
3234
@Testcontainers
3335
@ActiveProfiles("itest")
34-
abstract class AbstractTransactionalBehaviorTest {
36+
abstract class AbstractBehaviorTest {
3537
companion object {
3638

3739
@Container
@@ -60,6 +62,8 @@ abstract class AbstractTransactionalBehaviorTest {
6062
@Autowired
6163
private lateinit var externalTaskApiClient: ExternalTaskApiClient
6264
@Autowired
65+
private lateinit var incidentApiClient: IncidentApiClient
66+
@Autowired
6367
private lateinit var historyApiClient: HistoryApiClient
6468
@Autowired
6569
private lateinit var processInstanceApiClient: ProcessInstanceApiClient
@@ -111,6 +115,34 @@ abstract class AbstractTransactionalBehaviorTest {
111115
.body as List<ExternalTaskDto>
112116
}
113117

118+
protected fun unlockExternalTask(taskId: String) {
119+
externalTaskApiClient.unlock(taskId)
120+
}
121+
122+
protected fun getIncidents(processInstanceId: String) = incidentApiClient.getIncidents(
123+
null,
124+
null,
125+
null,
126+
null,
127+
null,
128+
null,
129+
processInstanceId,
130+
null,
131+
null,
132+
null,
133+
null,
134+
null,
135+
null,
136+
null,
137+
null,
138+
null,
139+
null,
140+
null,
141+
null,
142+
null,
143+
null
144+
).body as List<IncidentDto>
145+
114146
protected fun getHistoricActivityInstances(processInstanceId: String, activityId: String): List<HistoricActivityInstanceDto> {
115147
return historyApiClient.queryHistoricActivityInstances(
116148
0,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package dev.bpmcrafters.processengine.worker.itest.camunda7.external
2+
3+
import dev.bpmcrafters.processengine.worker.idempotency.IdempotencyRegistry
4+
import dev.bpmcrafters.processengineapi.task.ServiceTaskCompletionApi
5+
import org.assertj.core.api.Assertions.assertThat
6+
import org.awaitility.Awaitility.await
7+
import org.junit.jupiter.api.Test
8+
import org.mockito.kotlin.*
9+
import org.springframework.beans.factory.annotation.Autowired
10+
import org.springframework.context.annotation.Import
11+
import org.springframework.test.context.TestPropertySource
12+
import org.springframework.test.context.bean.override.mockito.MockitoSpyBean
13+
import java.util.*
14+
import java.util.concurrent.TimeUnit.SECONDS
15+
16+
@Import(InMemoryIdempotencyRegistryConfiguration::class)
17+
@TestPropertySource(properties = [
18+
"dev.bpm-crafters.process-api.worker.complete-tasks-before-commit=false"
19+
])
20+
@MockitoSpyBean(types = [IdempotencyRegistry::class])
21+
@MockitoSpyBean(name = "c7remote-service-task-completion-api", types = [ServiceTaskCompletionApi::class])
22+
abstract class AbstractIdempotencyTest : AbstractBehaviorTest() {
23+
24+
@Autowired
25+
private lateinit var idempotencyRegistry: IdempotencyRegistry
26+
27+
@Autowired
28+
private lateinit var serviceTaskCompletionApi: ServiceTaskCompletionApi
29+
30+
@Test
31+
fun `fetching the same task does not execute business logic again`() {
32+
val name = "Big or Lil' Someone ${UUID.randomUUID()}"
33+
doThrow(IllegalStateException("Many things have gone wrong while completing a task"))
34+
.`when`(serviceTaskCompletionApi)
35+
.completeTask(any())
36+
val processInstanceId = startProcess(name, verified = true)
37+
assertThat(processInstanceIsRunning(processInstanceId)).isTrue()
38+
await().atMost(30, SECONDS).untilAsserted {
39+
val entity = findEntityByName(name)
40+
assertThat(entity).isNotNull
41+
}
42+
doCallRealMethod().`when`(serviceTaskCompletionApi).completeTask(any())
43+
unlockExternalTask(getExternalTasks(processInstanceId).first().id!!)
44+
await().atMost(30, SECONDS).untilAsserted {
45+
assertThat(processInstanceIsRunning(processInstanceId)).isFalse
46+
}
47+
val inOrder = inOrder(idempotencyRegistry)
48+
inOrder.verify(idempotencyRegistry).getTaskResult(any())
49+
inOrder.verify(idempotencyRegistry).register(any(), any())
50+
inOrder.verify(idempotencyRegistry).getTaskResult(any())
51+
inOrder.verify(idempotencyRegistry, never()).register(any(), any())
52+
}
53+
54+
}

spring-boot-starter/src/test/kotlin/dev/bpmcrafters/processengine/worker/itest/camunda7/external/ExternalTaskCompletionAfterCommitTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ import java.util.concurrent.TimeUnit.SECONDS
1212
@TestPropertySource(properties = [
1313
"dev.bpm-crafters.process-api.worker.complete-tasks-before-commit=false"
1414
])
15-
class ExternalTaskCompletionAfterCommitTest : AbstractTransactionalBehaviorTest() {
15+
class ExternalTaskCompletionAfterCommitTest : AbstractBehaviorTest() {
1616

1717
@Test
18-
fun `successful worker completes task after of transaction has been committed`() {
18+
fun `successful worker completes task after transaction has been committed`() {
1919
val name = "Big or Lil' Someone ${UUID.randomUUID()}"
2020
val pi = startProcess(name = name, verified = true)
2121
assertThat(processInstanceIsRunning(pi)).isTrue()

spring-boot-starter/src/test/kotlin/dev/bpmcrafters/processengine/worker/itest/camunda7/external/ExternalTaskCompletionBeforeCommitTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import java.util.concurrent.TimeUnit.SECONDS
1212
@TestPropertySource(properties = [
1313
"dev.bpm-crafters.process-api.worker.complete-tasks-before-commit=true"
1414
])
15-
class ExternalTaskCompletionBeforeCommitTest : AbstractTransactionalBehaviorTest() {
15+
class ExternalTaskCompletionBeforeCommitTest : AbstractBehaviorTest() {
1616

1717
@Test
1818
fun `happy path create two verified valid entity`() {

spring-boot-starter/src/test/kotlin/dev/bpmcrafters/processengine/worker/itest/camunda7/external/ExternalTaskCompletionWithoutTransactionTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import java.util.*
1414
import java.util.concurrent.TimeUnit.SECONDS
1515

1616
@Import(ExternalTaskCompletionWithoutTransactionTest.WorkerWithoutTransactionalAnnotation::class)
17-
class ExternalTaskCompletionWithoutTransactionTest : AbstractTransactionalBehaviorTest() {
17+
class ExternalTaskCompletionWithoutTransactionTest : AbstractBehaviorTest() {
1818

1919
class WorkerWithoutTransactionalAnnotation(
2020
myEntityService: MyEntityService,

spring-boot-starter/src/test/kotlin/dev/bpmcrafters/processengine/worker/itest/camunda7/external/ExternalTaskFailJobExceptionTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import java.util.concurrent.TimeUnit.SECONDS
2020
@TestPropertySource(properties = [
2121
"dev.bpm-crafters.process-api.worker.complete-tasks-before-commit=true"
2222
])
23-
class ExternalTaskFailJobExceptionTest : AbstractTransactionalBehaviorTest() {
23+
class ExternalTaskFailJobExceptionTest : AbstractBehaviorTest() {
2424

2525
class WorkerWithFailJobException(
2626
myEntityService: MyEntityService,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package dev.bpmcrafters.processengine.worker.itest.camunda7.external
2+
3+
import org.springframework.context.annotation.Import
4+
5+
@Import(WorkerWithTransactionalAnnotation::class)
6+
class IdempotencyWithTransactionTest : AbstractIdempotencyTest()

0 commit comments

Comments
 (0)