test: 49 tests for economy services (EconomyService, BlackjackService, SlotService, JobService)

This commit is contained in:
loki5512344 2026-06-20 00:36:57 +02:00
parent 0787e98921
commit 4a3bc33356
4 changed files with 696 additions and 0 deletions

View file

@ -0,0 +1,128 @@
package creator.loki.feature.economy.service;
import creator.loki.feature.economy.domain.EconomyAccount;
import creator.loki.feature.economy.repository.EconomyRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class BlackjackServiceTest {
@Mock
private EconomyRepository repository;
@Captor
private ArgumentCaptor<EconomyAccount> accountCaptor;
private BlackjackService service;
private final EconomyAccount account = new EconomyAccount.Builder()
.userId("user1").cash(1000).build();
@BeforeEach
void setUp() {
service = new BlackjackService(repository);
}
@Nested
class StartGame {
@Test
void returnsNullWhenBetIsZeroOrNegative() {
assertNull(service.startGame("user1", 0));
assertNull(service.startGame("user1", -50));
}
@Test
void returnsNullWhenInsufficientCash() {
when(repository.getAccount("user1")).thenReturn(account);
assertNull(service.startGame("user1", 9999));
}
@Test
void deductsBetFromCash() {
when(repository.getAccount("user1")).thenReturn(account);
service.startGame("user1", 200);
verify(repository).save(accountCaptor.capture());
assertEquals(800, accountCaptor.getValue().getCash());
}
}
@Nested
class Hit {
@Test
void returnsNullWhenNoActiveGame() {
assertNull(service.hit("user1"));
}
}
@Nested
class Stand {
@Test
void returnsNullWhenNoActiveGame() {
assertNull(service.stand("user1"));
}
}
@Nested
class GameFlow {
@Test
void startGameThenStandCompletesGame() {
when(repository.getAccount("user1")).thenReturn(account);
BlackjackService.BlackjackState state = service.startGame("user1", 100);
assertNotNull(state);
state = service.stand("user1");
assertNotNull(state);
assertTrue(state.isOver());
assertTrue(state.payout() >= 0);
}
@Test
void playerHitsUntilBust() {
when(repository.getAccount("user1")).thenReturn(account);
BlackjackService.BlackjackState state = service.startGame("user1", 100);
assertNotNull(state);
int maxHits = 20;
while (!state.isOver() && maxHits-- > 0) {
state = service.hit("user1");
if (state == null) break;
}
assertNotNull(state);
assertTrue(state.isOver());
if (state.playerSum() > 21) {
assertEquals(0, state.payout());
}
}
@Test
void startGameThenStandSavesPayout() {
when(repository.getAccount("user1")).thenReturn(account);
service.startGame("user1", 100);
service.stand("user1");
verify(repository, atLeast(2)).save(any());
}
}
}

View file

@ -0,0 +1,252 @@
package creator.loki.feature.economy.service;
import creator.loki.core.security.CooldownManager;
import creator.loki.feature.economy.domain.EconomyAccount;
import creator.loki.feature.economy.repository.EconomyRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.time.LocalDate;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class EconomyServiceTest {
@Mock
private EconomyRepository repository;
@Mock
private CooldownManager cooldownManager;
@Captor
private ArgumentCaptor<EconomyAccount> accountCaptor;
private EconomyService service;
private final EconomyAccount baseAccount = new EconomyAccount.Builder()
.userId("user1").cash(1000).bank(500).totalEarned(200).totalSpent(100)
.lastDaily(LocalDate.now().minusDays(2).toString())
.dailyStreak(0).efficiency(0).fishingLuck(0)
.build();
private final EconomyAccount targetAccount = new EconomyAccount.Builder()
.userId("user2").cash(500).bank(0).build();
@BeforeEach
void setUp() {
service = new EconomyService(repository, cooldownManager);
}
@Nested
class Transfer {
@Test
void returnsFalseWhenAmountIsZeroOrNegative() {
assertFalse(service.transfer("user1", "user2", 0));
assertFalse(service.transfer("user1", "user2", -1));
}
@Test
void returnsFalseWhenSenderHasInsufficientCash() {
when(repository.getAccount("user1")).thenReturn(baseAccount);
assertFalse(service.transfer("user1", "user2", 9999));
verify(repository, never()).save(any());
}
@Test
void returnsTrueAndTransfersCashCorrectly() {
when(repository.getAccount("user1")).thenReturn(baseAccount);
when(repository.getAccount("user2")).thenReturn(targetAccount);
assertTrue(service.transfer("user1", "user2", 200));
verify(repository, times(2)).save(accountCaptor.capture());
assertEquals(800, accountCaptor.getAllValues().get(0).getCash());
assertEquals(700, accountCaptor.getAllValues().get(1).getCash());
}
@Test
void callsRepositorySaveTwice() {
when(repository.getAccount("user1")).thenReturn(baseAccount);
when(repository.getAccount("user2")).thenReturn(targetAccount);
service.transfer("user1", "user2", 100);
verify(repository, times(2)).save(any());
}
}
@Nested
class Deposit {
@Test
void returnsFalseWhenAmountIsZeroOrNegative() {
assertFalse(service.deposit("user1", 0));
assertFalse(service.deposit("user1", -5));
}
@Test
void returnsFalseWhenInsufficientCash() {
when(repository.getAccount("user1")).thenReturn(baseAccount);
assertFalse(service.deposit("user1", 9999));
}
@Test
void returnsTrueAndMovesCashToBank() {
when(repository.getAccount("user1")).thenReturn(baseAccount);
assertTrue(service.deposit("user1", 300));
verify(repository).save(accountCaptor.capture());
EconomyAccount saved = accountCaptor.getValue();
assertEquals(700, saved.getCash());
assertEquals(800, saved.getBank());
}
}
@Nested
class Withdraw {
@Test
void returnsFalseWhenAmountIsZeroOrNegative() {
assertFalse(service.withdraw("user1", 0));
assertFalse(service.withdraw("user1", -10));
}
@Test
void returnsFalseWhenInsufficientBank() {
when(repository.getAccount("user1")).thenReturn(baseAccount);
assertFalse(service.withdraw("user1", 9999));
}
@Test
void returnsTrueAndMovesBankToCash() {
when(repository.getAccount("user1")).thenReturn(baseAccount);
assertTrue(service.withdraw("user1", 200));
verify(repository).save(accountCaptor.capture());
EconomyAccount saved = accountCaptor.getValue();
assertEquals(1200, saved.getCash());
assertEquals(300, saved.getBank());
}
}
@Nested
class EarnCash {
@Test
void addsToCashAndTotalEarned() {
when(repository.getAccount("user1")).thenReturn(baseAccount);
long newCash = service.earnCash("user1", 150);
assertEquals(1150, newCash);
verify(repository).save(accountCaptor.capture());
EconomyAccount saved = accountCaptor.getValue();
assertEquals(1150, saved.getCash());
assertEquals(350, saved.getTotalEarned());
}
}
@Nested
class SpendCash {
@Test
void returnsFalseWhenAmountIsZeroOrNegative() {
assertFalse(service.spendCash("user1", 0));
assertFalse(service.spendCash("user1", -50));
}
@Test
void returnsFalseWhenInsufficientCash() {
when(repository.getAccount("user1")).thenReturn(baseAccount);
assertFalse(service.spendCash("user1", 9999));
}
@Test
void returnsTrueAndSubtractsCashAndAddsToTotalSpent() {
when(repository.getAccount("user1")).thenReturn(baseAccount);
assertTrue(service.spendCash("user1", 400));
verify(repository).save(accountCaptor.capture());
EconomyAccount saved = accountCaptor.getValue();
assertEquals(600, saved.getCash());
assertEquals(500, saved.getTotalSpent());
}
}
@Nested
class ClaimDaily {
@Test
void returnsNullWhenAlreadyClaimedToday() {
EconomyAccount todayClaimed = new EconomyAccount.Builder(baseAccount)
.lastDaily(LocalDate.now().toString())
.build();
when(repository.getAccount("user1")).thenReturn(todayClaimed);
assertNull(service.claimDaily("user1"));
}
@Test
void returnsDailyResultWithStreakOneForNewUser() {
EconomyAccount newUser = new EconomyAccount.Builder()
.userId("user1").cash(100).lastDaily(null).dailyStreak(0).build();
when(repository.getAccount("user1")).thenReturn(newUser);
EconomyService.DailyResult result = service.claimDaily("user1");
assertNotNull(result);
assertEquals(1, result.streak());
assertEquals(50, result.amount());
assertEquals(150, result.newCash());
}
@Test
void returnsDailyResultWithStreakTwoForConsecutiveDays() {
EconomyAccount returning = new EconomyAccount.Builder(baseAccount)
.cash(200).totalEarned(50)
.lastDaily(LocalDate.now().minusDays(1).toString())
.dailyStreak(1).build();
when(repository.getAccount("user1")).thenReturn(returning);
EconomyService.DailyResult result = service.claimDaily("user1");
assertNotNull(result);
assertEquals(2, result.streak());
assertEquals(50, result.amount());
assertEquals(250, result.newCash());
}
@Test
void returnsDailyResultWithAmountSeventyFiveForStreakAtLeastSeven() {
EconomyAccount veteran = new EconomyAccount.Builder(baseAccount)
.cash(500).totalEarned(300)
.lastDaily(LocalDate.now().minusDays(1).toString())
.dailyStreak(6).build();
when(repository.getAccount("user1")).thenReturn(veteran);
EconomyService.DailyResult result = service.claimDaily("user1");
assertNotNull(result);
assertEquals(7, result.streak());
assertEquals(75, result.amount());
assertEquals(575, result.newCash());
}
}
}

View file

@ -0,0 +1,184 @@
package creator.loki.feature.economy.service;
import creator.loki.feature.economy.domain.EconomyAccount;
import creator.loki.feature.economy.domain.Job;
import creator.loki.feature.economy.repository.EconomyRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class JobServiceTest {
@Mock
private EconomyRepository repository;
@Captor
private ArgumentCaptor<EconomyAccount> accountCaptor;
private JobService service;
private final EconomyAccount account = new EconomyAccount.Builder()
.userId("user1").cash(100).totalEarned(0).efficiency(0)
.build();
@BeforeEach
void setUp() {
service = new JobService(repository);
}
@Nested
class ListJobs {
@Test
void returnsSixJobs() {
List<JobService.JobInfo> jobs = service.listJobs();
assertEquals(6, jobs.size());
}
@Test
void containsWoodcutter() {
List<JobService.JobInfo> jobs = service.listJobs();
JobService.JobInfo woodcutter = jobs.stream()
.filter(j -> j.name().equals("WOODCUTTER"))
.findFirst().orElseThrow();
assertEquals("Лесоруб", woodcutter.displayName());
assertEquals(0, woodcutter.requiredLevel());
assertEquals(15, woodcutter.minPayout());
assertEquals(30, woodcutter.maxPayout());
}
}
@Nested
class SetJob {
@Test
void savesJobNameToAccount() {
when(repository.getAccount("user1")).thenReturn(account);
service.setJob("user1", "WOODCUTTER");
verify(repository).save(accountCaptor.capture());
assertEquals("WOODCUTTER", accountCaptor.getValue().getJob());
}
@Test
void doesNothingForInvalidJobName() {
service.setJob("user1", "INVALID");
verify(repository, never()).save(any());
}
}
@Nested
class GetCurrentJob {
@Test
void returnsDisplayNameWhenJobIsSet() {
EconomyAccount withJob = new EconomyAccount.Builder(account)
.job("MINER").build();
when(repository.getAccount("user1")).thenReturn(withJob);
String display = service.getCurrentJob("user1");
assertEquals("Шахтёр", display);
}
@Test
void returnsNullWhenNoJob() {
when(repository.getAccount("user1")).thenReturn(account);
assertNull(service.getCurrentJob("user1"));
}
}
@Nested
class Work {
@Test
void returnsNullWhenNoJobSet() {
when(repository.getAccount("user1")).thenReturn(account);
assertNull(service.work("user1", 0));
}
@Test
void returnsNullWhenOnCooldown() {
EconomyAccount withJob = new EconomyAccount.Builder(account)
.job("WOODCUTTER").lastWork(String.valueOf(System.currentTimeMillis()))
.build();
when(repository.getAccount("user1")).thenReturn(withJob);
assertNull(service.work("user1", 0));
}
@Test
void returnsWorkResultWithPayoutInRange() {
EconomyAccount withJob = new EconomyAccount.Builder(account)
.job("WOODCUTTER").lastWork("0")
.build();
when(repository.getAccount("user1")).thenReturn(withJob);
JobService.WorkResult result = service.work("user1", 0);
assertNotNull(result);
assertTrue(result.amount() >= 15);
assertTrue(result.amount() <= 30);
assertTrue(result.newCash() >= 115);
assertTrue(result.newCash() <= 130);
}
@Test
void multipliesPayoutByEfficiency() {
EconomyAccount efficient = new EconomyAccount.Builder(account)
.job("WOODCUTTER").lastWork("0").efficiency(100)
.build();
when(repository.getAccount("user1")).thenReturn(efficient);
JobService.WorkResult result = service.work("user1", 0);
assertNotNull(result);
assertTrue(result.amount() >= 30);
assertTrue(result.amount() <= 60);
}
@Test
void returnsNullWhenLevelTooLow() {
EconomyAccount withJob = new EconomyAccount.Builder(account)
.job("PROGRAMMER").lastWork("0")
.build();
when(repository.getAccount("user1")).thenReturn(withJob);
assertNull(service.work("user1", 10));
}
@Test
void savesUpdatedAccount() {
EconomyAccount withJob = new EconomyAccount.Builder(account)
.job("WOODCUTTER").lastWork("0")
.build();
when(repository.getAccount("user1")).thenReturn(withJob);
service.work("user1", 0);
verify(repository).save(accountCaptor.capture());
EconomyAccount saved = accountCaptor.getValue();
assertNotNull(saved.getLastWork());
assertTrue(saved.getCash() >= 115);
assertTrue(saved.getTotalEarned() >= 15);
}
}
}

View file

@ -0,0 +1,132 @@
package creator.loki.feature.economy.service;
import creator.loki.feature.economy.domain.EconomyAccount;
import creator.loki.feature.economy.domain.SlotCombination;
import creator.loki.feature.economy.repository.EconomyRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class SlotServiceTest {
@Mock
private EconomyRepository repository;
@Captor
private ArgumentCaptor<EconomyAccount> accountCaptor;
private SlotService service;
private final EconomyAccount account = new EconomyAccount.Builder()
.userId("user1").cash(500).build();
@BeforeEach
void setUp() {
service = new SlotService(repository);
}
@Nested
class Spin {
@Test
void returnsNullWhenBetIsZeroOrNegative() {
assertNull(service.spin("user1", 0));
assertNull(service.spin("user1", -10));
}
@Test
void returnsNullWhenInsufficientCash() {
when(repository.getAccount("user1")).thenReturn(account);
assertNull(service.spin("user1", 9999));
}
@Test
void deductsBetFromCash() {
when(repository.getAccount("user1")).thenReturn(account);
service.spin("user1", 100);
verify(repository, atLeastOnce()).save(accountCaptor.capture());
assertEquals(400, accountCaptor.getAllValues().get(0).getCash());
}
@Test
void returnsNonNullResultWithValidEmojis() {
when(repository.getAccount("user1")).thenReturn(account);
SlotService.SlotResult result = service.spin("user1", 100);
assertNotNull(result);
assertNotNull(result.emoji1());
assertNotNull(result.emoji2());
assertNotNull(result.emoji3());
assertTrue(result.cashAfter() >= 0);
}
@Test
void winAmountIsZeroAndComboNameIsNullWhenNoMatch() {
when(repository.getAccount("user1")).thenReturn(account);
SlotService.SlotResult result = service.spin("user1", 100);
assertNotNull(result);
assertEquals(result.cashAfter(), account.getCash() - 100 + result.winAmount());
}
}
@Nested
class SlotCombinationMatch {
@Test
void returnsNullForNonMatchingEmojis() {
assertNull(SlotCombination.match("\uD83C\uDF52", "\uD83D\uDD14", "\uD83D\uDC8E"));
assertNull(SlotCombination.match("\uD83C\uDF52", "\uD83C\uDF52", "\uD83D\uDD14"));
}
@Test
void returnsCherryForThreeCherries() {
SlotCombination result = SlotCombination.match("\uD83C\uDF52", "\uD83C\uDF52", "\uD83C\uDF52");
assertEquals(SlotCombination.CHERRY, result);
assertEquals(3, result.getMultiplier());
}
@Test
void returnsBellForThreeBells() {
SlotCombination result = SlotCombination.match("\uD83D\uDD14", "\uD83D\uDD14", "\uD83D\uDD14");
assertEquals(SlotCombination.BELL, result);
assertEquals(5, result.getMultiplier());
}
@Test
void returnsDiamondForThreeDiamonds() {
SlotCombination result = SlotCombination.match("\uD83D\uDC8E", "\uD83D\uDC8E", "\uD83D\uDC8E");
assertEquals(SlotCombination.DIAMOND, result);
assertEquals(10, result.getMultiplier());
}
@Test
void returnsCrownForThreeCrowns() {
SlotCombination result = SlotCombination.match("\uD83D\uDC51", "\uD83D\uDC51", "\uD83D\uDC51");
assertEquals(SlotCombination.CROWN, result);
assertEquals(15, result.getMultiplier());
}
@Test
void returnsJackpotForThreeLightningBolts() {
SlotCombination result = SlotCombination.match("\u26A1", "\u26A1", "\u26A1");
assertEquals(SlotCombination.JACKPOT, result);
assertEquals(50, result.getMultiplier());
}
}
}