Skip to content

Commit 53b437e

Browse files
Ovcharenko.AAOvcharenko.AA
authored andcommitted
Transaction
1 parent 835680e commit 53b437e

7 files changed

Lines changed: 226 additions & 0 deletions
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package transactions;
2+
3+
import java.util.ArrayList;
4+
import java.util.HashSet;
5+
import java.util.List;
6+
import java.util.Set;
7+
8+
class InMemoryValidationService implements ValidationService {
9+
10+
11+
/**
12+
* 1. Транзакции обрабатываются по порядку transaction.getId();
13+
* 2. Транзакция с id, который был обработан, считается невалидной;
14+
* 3. Транзакции с таким же orderId, как у невалидной транзакции, тоже считаются невалидными;
15+
* 4. BET транзакция понижает баланс;
16+
* 5. WIN транзакция повышает баланс;
17+
* 6. Если BET транзакция делает баланс отрицательным, то она считается невалидной.
18+
**/
19+
20+
@Override
21+
public ValidationResult validate(List<Transaction> transactions, long balance) {
22+
if (transactions == null || transactions.isEmpty() || balance < 0) {
23+
return ValidationResult.empty();
24+
}
25+
26+
List<Transaction> sortedTransactions = transactions.stream()
27+
.sorted()
28+
.toList();
29+
30+
Set<Long> transactionsIds = new HashSet<>();
31+
Set<Long> failedOrderIds = new HashSet<>();
32+
33+
List<TransactionStatus> transactionStatuses = new ArrayList<>();
34+
35+
for (Transaction transaction : sortedTransactions) {
36+
boolean condition = transactionsIds.contains(transaction.id())
37+
|| failedOrderIds.contains(transaction.orderId());
38+
39+
if (condition) {
40+
transactionStatuses.add((new TransactionStatus(transaction, false)));
41+
failedOrderIds.add(transaction.orderId());
42+
transactionsIds.add(transaction.id());
43+
continue;
44+
}
45+
46+
if (transaction.type() == TransactionType.BET) {
47+
boolean negativeBalance = balance < transaction.amount();
48+
49+
if (negativeBalance) {
50+
transactionStatuses.add(new TransactionStatus(transaction, false));
51+
failedOrderIds.add(transaction.orderId());
52+
} else {
53+
balance -= transaction.amount();
54+
transactionStatuses.add(new TransactionStatus(transaction, true));
55+
}
56+
}
57+
58+
if (transaction.type() == TransactionType.WIN) {
59+
balance += transaction.amount();
60+
transactionStatuses.add(new TransactionStatus(transaction, true));
61+
}
62+
transactionsIds.add(transaction.id());
63+
64+
}
65+
66+
return new ValidationResult(transactionStatuses);
67+
}
68+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package transactions;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import java.util.List;
6+
7+
import static org.junit.jupiter.api.Assertions.assertEquals;
8+
import static org.junit.jupiter.api.Assertions.assertFalse;
9+
import static org.junit.jupiter.api.Assertions.assertTrue;
10+
11+
class InMemoryValidationServiceTest {
12+
13+
private final ValidationService service = ValidationService.getInstance();
14+
15+
private Transaction createTransaction(long id, long orderId, long amount, TransactionType type) {
16+
return new Transaction(id, orderId, amount, type);
17+
}
18+
19+
@Test
20+
void testNullTransactions() {
21+
ValidationResult result = service.validate(null, 100);
22+
assertTrue(result.getTransactions().isEmpty(), "Result should be empty for null transactions");
23+
}
24+
25+
@Test
26+
void testEmptyTransactions() {
27+
ValidationResult result = service.validate(List.of(), 100);
28+
assertTrue(result.getTransactions().isEmpty(), "Result should be empty for empty transactions");
29+
}
30+
31+
@Test
32+
void testNegativeInitialBalance() {
33+
List<Transaction> transactions = List.of(createTransaction(1, 1, 50, TransactionType.BET));
34+
ValidationResult result = service.validate(transactions, -10);
35+
assertTrue(result.getTransactions().isEmpty(), "Result should be empty for negative initial balance");
36+
}
37+
38+
@Test
39+
void testValidBetTransaction() {
40+
List<Transaction> transactions = List.of(createTransaction(1, 1, 50, TransactionType.BET));
41+
ValidationResult result = service.validate(transactions, 100);
42+
List<TransactionStatus> statuses = result.getTransactions();
43+
assertEquals(1, statuses.size(), "There should be one transaction status");
44+
assertTrue(statuses.getFirst().status(), "BET transaction should be valid when balance is sufficient");
45+
}
46+
47+
@Test
48+
void testInvalidBetTransactionDueToInsufficientBalance() {
49+
List<Transaction> transactions = List.of(createTransaction(1, 1, 150, TransactionType.BET));
50+
ValidationResult result = service.validate(transactions, 100);
51+
List<TransactionStatus> statuses = result.getTransactions();
52+
assertEquals(1, statuses.size(), "There should be one transaction status");
53+
assertFalse(statuses.getFirst().status(), "BET transaction should be invalid due to insufficient balance");
54+
}
55+
56+
@Test
57+
void testWinTransactionIncreasesBalance() {
58+
// WIN-транзакция увеличивает баланс, позволяя последующей BET-транзакции пройти валидацию
59+
Transaction win = createTransaction(1, 1, 50, TransactionType.WIN);
60+
Transaction bet = createTransaction(2, 2, 120, TransactionType.BET);
61+
List<Transaction> transactions = List.of(win, bet);
62+
ValidationResult result = service.validate(transactions, 100);
63+
List<TransactionStatus> statuses = result.getTransactions();
64+
assertEquals(2, statuses.size(), "There should be two transaction statuses");
65+
assertTrue(statuses.get(0).status(), "WIN transaction should be valid");
66+
assertTrue(statuses.get(1).status(), "BET transaction should be valid after balance increase");
67+
}
68+
69+
@Test
70+
void testDuplicateTransactionId() {
71+
Transaction t1 = createTransaction(1, 1, 50, TransactionType.WIN);
72+
// Повторное использование id 1 должно пометить вторую транзакцию как невалидную
73+
Transaction t2 = createTransaction(1, 2, 30, TransactionType.BET);
74+
List<Transaction> transactions = List.of(t1, t2);
75+
ValidationResult result = service.validate(transactions, 100);
76+
List<TransactionStatus> statuses = result.getTransactions();
77+
assertEquals(2, statuses.size(), "There should be two transaction statuses");
78+
assertTrue(statuses.get(0).status(), "First transaction should be valid");
79+
assertFalse(statuses.get(1).status(), "Duplicate transaction id should mark second transaction invalid");
80+
}
81+
82+
@Test
83+
void testFailedOrderIdAffectsSubsequentTransactions() {
84+
// Первая транзакция не проходит валидацию из-за недостатка средств
85+
Transaction bet1 = createTransaction(1, 10, 150, TransactionType.BET);
86+
// Вторая транзакция с тем же orderId должна быть невалидной вне зависимости от типа
87+
Transaction win1 = createTransaction(2, 10, 100, TransactionType.WIN);
88+
List<Transaction> transactions = List.of(bet1, win1);
89+
ValidationResult result = service.validate(transactions, 100);
90+
List<TransactionStatus> statuses = result.getTransactions();
91+
assertEquals(2, statuses.size(), "There should be two transaction statuses");
92+
assertFalse(statuses.get(0).status(), "First transaction should be invalid due to insufficient funds");
93+
assertFalse(statuses.get(1).status(), "Transaction with same order id as failed transaction should be invalid");
94+
}
95+
96+
@Test
97+
void testTransactionsProcessedInOrder() {
98+
// Транзакции заданы не по порядку: сначала BET (id 2), затем WIN (id 1).
99+
// После сортировки они должны обрабатываться в правильном порядке.
100+
Transaction bet = createTransaction(2, 1, 50, TransactionType.BET);
101+
Transaction win = createTransaction(1, 2, 100, TransactionType.WIN);
102+
List<Transaction> transactions = List.of(bet, win);
103+
// Начальный баланс 50, после обработки WIN становится 150, затем BET списывает 50 -> итог 100
104+
ValidationResult result = service.validate(transactions, 50);
105+
List<TransactionStatus> statuses = result.getTransactions();
106+
assertEquals(2, statuses.size(), "There should be two transaction statuses");
107+
assertTrue(statuses.get(0).status(), "First processed transaction should be valid");
108+
assertTrue(statuses.get(1).status(), "Second processed transaction should be valid");
109+
}
110+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package transactions;
2+
3+
record Transaction(long id, long orderId, long amount, TransactionType type) implements Comparable<Transaction> {
4+
5+
@Override
6+
public int compareTo(Transaction o) {
7+
return Long.compare(this.id, o.id());
8+
}
9+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package transactions;
2+
3+
record TransactionStatus(Transaction transaction, boolean status) {
4+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package transactions;
2+
3+
enum TransactionType {
4+
BET, WIN
5+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package transactions;
2+
3+
import lombok.Getter;
4+
5+
import java.util.List;
6+
7+
@Getter
8+
final class ValidationResult {
9+
private final List<TransactionStatus> transactions;
10+
11+
public ValidationResult(List<TransactionStatus> transactions) {
12+
this.transactions = transactions;
13+
}
14+
15+
public static ValidationResult empty() {
16+
return new ValidationResult(List.of());
17+
}
18+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package transactions;
2+
3+
import java.util.List;
4+
5+
interface ValidationService {
6+
7+
static ValidationService getInstance() {
8+
return new InMemoryValidationService();
9+
}
10+
11+
ValidationResult validate(List<Transaction> transactions, long balance);
12+
}

0 commit comments

Comments
 (0)