Skip to content

Commit f89cea0

Browse files
committed
FIN-348 update budget analysis process to simplified setup.
1 parent af75691 commit f89cea0

File tree

5 files changed

+151
-211
lines changed

5 files changed

+151
-211
lines changed

bpmn-process/src/main/resources/bpmn/analysis/budget-analysis.bpmn

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
<bpmn:extensionElements />
1818
<bpmn:incoming>Flow_0tlql11</bpmn:incoming>
1919
<bpmn:outgoing>Flow_0bh4qbk</bpmn:outgoing>
20-
<bpmn:multiInstanceLoopCharacteristics isSequential="true" camunda:asyncBefore="true" camunda:collection="${budgets}" camunda:elementVariable="budget" />
20+
<bpmn:multiInstanceLoopCharacteristics isSequential="true" camunda:collection="${budgets}" camunda:elementVariable="budget" />
2121
<bpmn:startEvent id="budget_start">
2222
<bpmn:outgoing>Flow_1lfnzjr</bpmn:outgoing>
2323
</bpmn:startEvent>
Lines changed: 69 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,32 @@
11
package com.jongsoft.finance.bpmn;
22

33
import com.jongsoft.finance.ResultPage;
4+
import com.jongsoft.finance.bpmn.process.ProcessExtension;
5+
import com.jongsoft.finance.bpmn.process.RuntimeContext;
6+
import com.jongsoft.finance.domain.account.Account;
7+
import com.jongsoft.finance.domain.transaction.Transaction;
48
import com.jongsoft.finance.domain.user.Budget;
5-
import com.jongsoft.finance.factory.FilterFactory;
6-
import com.jongsoft.finance.providers.BudgetProvider;
79
import com.jongsoft.finance.providers.SettingProvider;
8-
import com.jongsoft.finance.providers.TransactionProvider;
910
import com.jongsoft.lang.Collections;
10-
import com.jongsoft.lang.Control;
1111
import jakarta.inject.Inject;
12-
import org.camunda.bpm.engine.ProcessEngine;
13-
import org.junit.jupiter.api.BeforeEach;
12+
import org.assertj.core.api.Assertions;
13+
import org.camunda.bpm.engine.variable.Variables;
1414
import org.junit.jupiter.api.DisplayName;
1515
import org.junit.jupiter.api.Test;
1616
import org.mockito.Mockito;
17-
import org.mockito.invocation.InvocationOnMock;
1817

19-
import static org.assertj.core.api.Assertions.assertThat;
20-
21-
public class BudgetAnalysisIT extends ProcessTestSetup {
22-
23-
@Inject
24-
private BudgetProvider budgetProvider;
25-
26-
@Inject
27-
private ProcessEngine processEngine;
28-
29-
@Inject
30-
private FilterFactory filterFactory;
31-
32-
@Inject
33-
private TransactionProvider transactionProvider;
18+
@ProcessExtension
19+
@DisplayName("Budget analysis feature")
20+
public class BudgetAnalysisIT {
3421

3522
@Inject
3623
private SettingProvider applicationSettings;
3724

38-
private TransactionProvider.FilterCommand filterCommand;
39-
40-
@BeforeEach
41-
void setup() {
42-
Mockito.reset(budgetProvider, filterFactory, transactionProvider, applicationSettings);
43-
44-
Mockito.when(applicationSettings.getBudgetAnalysisMonths()).thenReturn(3);
45-
46-
Mockito.when(filterFactory.transaction())
47-
.thenReturn(filterCommand = Mockito.mock(TransactionProvider.FilterCommand.class, InvocationOnMock::getMock));
48-
49-
Mockito.when(budgetProvider.lookup(2019, 1)).thenReturn(Control.Option(
50-
Budget.builder()
25+
@Test
26+
@DisplayName("Budget analysis without a recorded deviation")
27+
void budgetWithoutDeviation(RuntimeContext context) {
28+
context
29+
.withBudget(2019, 1, Budget.builder()
5130
.expenses(Collections.List(
5231
Budget.Expense.builder()
5332
.id(1L)
@@ -56,14 +35,8 @@ void setup() {
5635
.name("Groceries")
5736
.build()))
5837
.id(1L)
59-
.build()
60-
));
61-
}
62-
63-
@Test
64-
@DisplayName("Budget analysis without a recorded deviation")
65-
void budgetWithoutDeviation() {
66-
Mockito.when(transactionProvider.lookup(filterCommand))
38+
.build())
39+
.withTransactionPages()
6740
.thenReturn(ResultPage.of(
6841
buildTransaction(50.2, "Groceries", "My Account", "To Account"),
6942
buildTransaction(20.2, "Groceries", "My Account", "To Account"),
@@ -79,41 +52,35 @@ void budgetWithoutDeviation() {
7952
buildTransaction(12, "Groceries", "My Account", "To Account"),
8053
buildTransaction(10, "Groceries", "My Account", "To Account")
8154
));
82-
Mockito.when(applicationSettings.getMaximumBudgetDeviation()).thenReturn(0.50);
83-
84-
var response = processEngine.getRuntimeService()
85-
.createProcessInstanceByKey("budget_analysis")
86-
.setVariable("id", 1L)
87-
.setVariable("scheduled", "2019-01-01")
88-
.execute();
8955

90-
waitForSuspended(processEngine, response.getProcessInstanceId());
91-
92-
// Validate the process completed successfully
93-
var variables = processEngine.getHistoryService()
94-
.createHistoricVariableInstanceQuery()
95-
.processInstanceId(response.getProcessInstanceId())
96-
.variableName("deviates")
97-
.list();
98-
99-
assertThat(variables)
100-
.hasSize(2)
101-
.anySatisfy(variable -> assertThat(variable.getValue()).isEqualTo(false));
56+
Mockito.when(applicationSettings.getMaximumBudgetDeviation()).thenReturn(0.50);
57+
Mockito.when(applicationSettings.getBudgetAnalysisMonths()).thenReturn(3);
10258

103-
// Validate no tasks have been created
104-
var tasks = processEngine.getTaskService()
105-
.createTaskQuery()
106-
.processInstanceId(response.getProcessInstanceId())
107-
.active()
108-
.list();
59+
context.execute("budget_analysis", Variables.createVariables()
60+
.putValue("id", 1L)
61+
.putValue("scheduled", "2019-01-01"))
62+
.verifyCompleted()
63+
.<Boolean>yankVariables("deviates", value ->
64+
value.hasSize(2)
65+
.allMatch(a-> !a));
10966

110-
assertThat(tasks).isEmpty();
11167
}
11268

11369
@Test
11470
@DisplayName("Budget analysis with a recorded deviation")
115-
void budgetWithDeviation() {
116-
Mockito.when(transactionProvider.lookup(filterCommand))
71+
void budgetWithDeviation(RuntimeContext context) {
72+
context
73+
.withBudget(2019, 1, Budget.builder()
74+
.expenses(Collections.List(
75+
Budget.Expense.builder()
76+
.id(1L)
77+
.lowerBound(75)
78+
.upperBound(100)
79+
.name("Groceries")
80+
.build()))
81+
.id(1L)
82+
.build())
83+
.withTransactionPages()
11784
.thenReturn(ResultPage.of(
11885
buildTransaction(50.2, "Groceries", "My Account", "To Account"),
11986
buildTransaction(20.2, "Groceries", "My Account", "To Account"),
@@ -129,47 +96,40 @@ void budgetWithDeviation() {
12996
buildTransaction(12, "Groceries", "My Account", "To Account"),
13097
buildTransaction(13, "Groceries", "My Account", "To Account")
13198
));
132-
Mockito.when(applicationSettings.getMaximumBudgetDeviation()).thenReturn(0.05);
133-
134-
var response = processEngine.getRuntimeService()
135-
.createProcessInstanceByKey("budget_analysis")
136-
.setVariable("id", 1L)
137-
.setVariable("scheduled", "2019-01-01")
138-
.execute();
13999

140-
waitForSuspended(processEngine, response.getProcessInstanceId());
141-
142-
// Validate the process completed successfully
143-
var variables = processEngine.getHistoryService()
144-
.createHistoricVariableInstanceQuery()
145-
.processInstanceId(response.getProcessInstanceId())
146-
.variableName("deviation")
147-
.list();
148-
149-
assertThat(variables)
150-
.hasSize(2)
151-
.anySatisfy(variable -> assertThat(variable.getValue()).isEqualTo(-14.23));
100+
Mockito.when(applicationSettings.getBudgetAnalysisMonths()).thenReturn(3);
101+
Mockito.when(applicationSettings.getMaximumBudgetDeviation()).thenReturn(0.05);
152102

153-
// Validate the tasks have been created according to the number of months
103+
var execution = context.execute("budget_analysis", Variables.createVariables()
104+
.putValue("id", 1L)
105+
.putValue("scheduled", "2019-01-01"))
106+
.<Boolean>yankVariables("deviates", value -> value.hasSize(2).allMatch(a-> a))
107+
.<Number>yankVariables("deviation", value -> value.allMatch(v -> v.equals(-14.23)));
154108

155-
var tasks = processEngine.getTaskService()
156-
.createTaskQuery()
157-
.processInstanceId(response.getProcessInstanceId())
158-
.active()
159-
.taskDefinitionKey("handle_deviation")
160-
.list();
109+
execution.task("handle_deviation")
110+
.verifyVariable("needed_correction", a -> Assertions.assertThat(a).isEqualTo(-14.23));
111+
}
161112

162-
assertThat(tasks)
163-
.hasSize(1)
164-
.anySatisfy(task -> {
165-
assertThat(task.getName()).isEqualTo("Handle deviation");
166-
assertThat(processEngine.getRuntimeService()
167-
.createVariableInstanceQuery()
168-
.processInstanceIdIn(response.getProcessInstanceId())
169-
.variableName("needed_correction")
170-
.list())
171-
.hasSize(1)
172-
.anySatisfy(variable -> assertThat(variable.getValue()).isEqualTo(-14.23));
173-
});
113+
public static Transaction buildTransaction(double amount, String description, String to, String from) {
114+
return Transaction.builder()
115+
.description(description)
116+
.transactions(Collections.List(
117+
Transaction.Part.builder()
118+
.amount(amount)
119+
.account(Account.builder()
120+
.id(1L)
121+
.name(to)
122+
.build())
123+
.build(),
124+
Transaction.Part.builder()
125+
.amount(-amount)
126+
.account(Account.builder()
127+
.id(2L)
128+
.name(from)
129+
.build())
130+
.build()
131+
))
132+
.build();
174133
}
134+
175135
}
Lines changed: 39 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,119 +1,57 @@
11
package com.jongsoft.finance.bpmn;
22

3-
import com.jongsoft.finance.StorageService;
3+
import com.jongsoft.finance.bpmn.process.ProcessExtension;
4+
import com.jongsoft.finance.bpmn.process.RuntimeContext;
45
import com.jongsoft.finance.domain.account.Account;
56
import com.jongsoft.finance.domain.transaction.ScheduledTransaction;
6-
import com.jongsoft.finance.domain.transaction.Transaction;
7-
import com.jongsoft.finance.messaging.commands.transaction.CreateTransactionCommand;
8-
import com.jongsoft.finance.messaging.handlers.TransactionCreationHandler;
9-
import com.jongsoft.finance.providers.AccountProvider;
10-
import com.jongsoft.finance.providers.TransactionProvider;
11-
import com.jongsoft.finance.providers.TransactionRuleProvider;
12-
import com.jongsoft.finance.providers.TransactionScheduleProvider;
13-
import com.jongsoft.lang.Collections;
14-
import com.jongsoft.lang.Control;
15-
import io.micronaut.core.reflect.ReflectionUtils;
16-
import jakarta.inject.Inject;
17-
import org.apache.commons.lang3.mutable.MutableLong;
18-
import org.apache.commons.lang3.mutable.MutableObject;
197
import org.assertj.core.api.Assertions;
20-
import org.camunda.bpm.engine.ProcessEngine;
21-
import org.junit.jupiter.api.BeforeEach;
8+
import org.camunda.bpm.engine.variable.Variables;
9+
import org.junit.jupiter.api.DisplayName;
2210
import org.junit.jupiter.api.Test;
23-
import org.mockito.Mockito;
24-
import org.mockito.stubbing.Answer;
2511

2612
import java.time.LocalDate;
27-
import java.util.UUID;
2813

29-
class ScheduledTransactionIT extends ProcessTestSetup {
14+
@ProcessExtension
15+
@DisplayName("Scheduled Transaction feature")
16+
class ScheduledTransactionIT {
3017

31-
@Inject
32-
private StorageService storageService;
33-
34-
@Inject
35-
private TransactionProvider transactionProvider;
36-
37-
@Inject
38-
private TransactionScheduleProvider transactionScheduleProvider;
39-
40-
@Inject
41-
private TransactionCreationHandler transactionCreationHandler;
42-
43-
@Inject
44-
private TransactionRuleProvider transactionRuleProvider;
45-
46-
@Inject
47-
private AccountProvider accountProvider;
48-
49-
@Inject
50-
private ProcessEngine processEngine;
51-
52-
@BeforeEach
53-
void setup() {
54-
Mockito.reset(transactionProvider, storageService, accountProvider, transactionCreationHandler, transactionScheduleProvider, transactionRuleProvider);
55-
56-
Mockito.when(transactionScheduleProvider.lookup(1L)).thenReturn(Control.Option(
57-
ScheduledTransaction.builder()
18+
@Test
19+
void scheduleRun(RuntimeContext context) {
20+
context
21+
.withStorage()
22+
.withTransactions()
23+
.withAccount(Account.builder()
24+
.id(1L)
25+
.name("Source account")
26+
.type("checking")
27+
.build())
28+
.withAccount(Account.builder()
29+
.id(2L)
30+
.name("Cable Company")
31+
.type("creditor")
32+
.build())
33+
.withTransactionSchedule(ScheduledTransaction.builder()
5834
.id(1L)
5935
.name("CableCom")
6036
.amount(29.99)
6137
.description("Monthly TV")
6238
.source(Account.builder().id(1L).build())
6339
.destination(Account.builder().id(2L).build())
64-
.build()));
65-
Mockito.when(transactionProvider.similar(Mockito.any(), Mockito.any(), Mockito.anyDouble(), Mockito.any()))
66-
.thenReturn(Collections.List());
67-
Mockito.when(transactionRuleProvider.lookup()).thenReturn(Collections.List());
68-
69-
Mockito.when(accountProvider.lookup(1L)).thenReturn(Control.Option(Account.builder()
70-
.id(1L)
71-
.name("Source account")
72-
.type("checking")
73-
.build()));
74-
Mockito.when(accountProvider.lookup(2L)).thenReturn(Control.Option(Account.builder()
75-
.id(2L)
76-
.name("Cable Company")
77-
.type("creditor")
78-
.build()));
79-
80-
Mockito.when(storageService.store(Mockito.any())).thenAnswer((Answer<String>) invocation -> {
81-
byte[] original = invocation.getArgument(0);
82-
String token = UUID.randomUUID().toString();
83-
Mockito.when(storageService.read(token)).thenReturn(Control.Option(original));
84-
return token;
85-
});
86-
}
87-
88-
@Test
89-
void scheduleRun() {
90-
MutableLong id = new MutableLong(1);
91-
MutableObject<Transaction> transaction = new MutableObject<>();
92-
93-
Mockito.when(transactionCreationHandler.handleCreatedEvent(Mockito.any())).thenAnswer((Answer<Long>) invocation -> {
94-
CreateTransactionCommand event = invocation.getArgument(0);
95-
long transactionId = id.getAndAdd(1);
96-
var field = ReflectionUtils.getRequiredField(Transaction.class, "id");
97-
field.setAccessible(true);
98-
field.set(event.transaction(), transactionId);
99-
Mockito.when(transactionProvider.lookup(transactionId)).thenReturn(Control.Option(event.transaction()));
100-
transaction.setValue(event.transaction());
101-
return transactionId;
102-
});
103-
104-
processEngine.getRuntimeService()
105-
.createProcessInstanceByKey("ScheduledTransaction")
106-
.setVariable("id", 1L)
107-
.setVariable("scheduled", "2019-01-01")
108-
.execute();
109-
110-
Mockito.verify(accountProvider).lookup(1L);
111-
Mockito.verify(accountProvider).lookup(2L);
112-
113-
Assertions.assertThat(transaction.getValue().computeTo().getId()).isEqualTo(2L);
114-
Assertions.assertThat(transaction.getValue().computeFrom().getId()).isEqualTo(1L);
115-
Assertions.assertThat(transaction.getValue().computeAmount(transaction.getValue().computeTo())).isEqualTo(29.99);
116-
Assertions.assertThat(transaction.getValue().getDescription()).isEqualTo("CableCom: Monthly TV");
117-
Assertions.assertThat(transaction.getValue().getDate()).isEqualTo(LocalDate.of(2019, 1, 1));
40+
.build());
41+
42+
context.execute("ScheduledTransaction", Variables.createVariables()
43+
.putValue("id", 1L)
44+
.putValue("scheduled", "2019-01-01"))
45+
.verifyCompleted();
46+
47+
context.verifyTransactions(transactions ->
48+
transactions.hasSize(1)
49+
.anySatisfy(transaction -> {
50+
Assertions.assertThat(transaction.computeTo().getId()).isEqualTo(2L);
51+
Assertions.assertThat(transaction.computeFrom().getId()).isEqualTo(1L);
52+
Assertions.assertThat(transaction.computeAmount(transaction.computeTo())).isEqualTo(29.99);
53+
Assertions.assertThat(transaction.getDescription()).isEqualTo("CableCom: Monthly TV");
54+
Assertions.assertThat(transaction.getDate()).isEqualTo(LocalDate.of(2019, 1, 1));
55+
}));
11856
}
11957
}

0 commit comments

Comments
 (0)