Skip to content

Commit

Permalink
Testing complete for repository interfaces
Browse files Browse the repository at this point in the history
  • Loading branch information
Daniel Villavicencio authored and Daniel Villavicencio committed Jan 31, 2024
1 parent 5f443d7 commit f5be655
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
import io.r2dbc.spi.Row;
import java.util.ArrayList;
import java.util.function.BiFunction;
import org.springframework.stereotype.Component;

@Component
public class BotUserMapper implements BiFunction<Row, Object, BotUser> {

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
import com.danielvm.destiny2bot.entity.UserCharacter;
import io.r2dbc.spi.Row;
import java.util.function.BiFunction;
import org.springframework.stereotype.Component;

@Component
public class UserCharacterMapper implements BiFunction<Row, Object, UserCharacter> {

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.danielvm.destiny2bot.repository;

import com.danielvm.destiny2bot.entity.BotUser;
import com.danielvm.destiny2bot.exception.ResourceNotFoundException;
import com.danielvm.destiny2bot.mapper.BotUserMapper;
import com.danielvm.destiny2bot.mapper.UserCharacterMapper;
import java.util.Map;
Expand All @@ -11,6 +12,10 @@
@Repository
public class BotUserRepositoryImpl implements BotUserRepository {


private static final BotUserMapper BOT_MAPPER = new BotUserMapper();
private static final UserCharacterMapper CHARACTER_MAPPER = new UserCharacterMapper();

private static final String RETRIEVE_CHARACTERS_QUERY = """
SELECT buc.character_id,
buc.light_level,
Expand All @@ -19,8 +24,8 @@ public class BotUserRepositoryImpl implements BotUserRepository {
FROM bot_user bu
INNER JOIN bungie_user_character buc on
bu.discord_id = buc.discord_user_id
WHERE bu.discord_id = :discordId;
""";
WHERE bu.discord_id = :discordId
""";

private static final String BOT_USER_QUERY = """
SELECT bu.discord_id,
Expand All @@ -33,33 +38,31 @@ public class BotUserRepositoryImpl implements BotUserRepository {
WHERE bu.discord_id = :discordId
""";

private static final String INSERT_USER_QUERY = """
public static final String INSERT_USER_QUERY = """
INSERT INTO bot_user (discord_id, discord_username, bungie_membership_id,
bungie_access_token, bungie_refresh_token, bungie_token_expiration)
VALUES (:discordId, :discordUsername, :membershipId, :accessToken,
:refreshToken, :tokenExpiration)
""";

private final DatabaseClient databaseClient;
private final BotUserMapper botUserMapper;
private final UserCharacterMapper userCharacterMapper;

public BotUserRepositoryImpl(
DatabaseClient databaseClient,
BotUserMapper botUserMapper, UserCharacterMapper userCharacterMapper) {
DatabaseClient databaseClient) {
this.databaseClient = databaseClient;
this.botUserMapper = botUserMapper;
this.userCharacterMapper = userCharacterMapper;
}

@Override
public Mono<BotUser> findBotUserByDiscordId(Long discordId) {
return databaseClient.sql(BOT_USER_QUERY)
.bind("discordId", discordId)
.map(botUserMapper::apply)
.first().flatMap(botUser -> databaseClient.sql(RETRIEVE_CHARACTERS_QUERY)
.map(BOT_MAPPER::apply)
.first()
.switchIfEmpty(Mono.error(new ResourceNotFoundException(
"Discord user with Id [%s] not found".formatted(discordId))))
.flatMap(botUser -> databaseClient.sql(RETRIEVE_CHARACTERS_QUERY)
.bind("discordId", discordId)
.map(userCharacterMapper::apply)
.map(CHARACTER_MAPPER::apply)
.all().collectList()
.map(userCharacters -> {
botUser.setCharacters(userCharacters);
Expand All @@ -80,7 +83,7 @@ public Mono<BotUser> save(BotUser botUser) {
);
return databaseClient.sql(INSERT_USER_QUERY)
.bindValues(params)
.map(botUserMapper::apply)
.map(BOT_MAPPER::apply)
.one();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,15 @@
@Repository
public class UserCharacterRepositoryImpl implements UserCharacterRepository {

private static final String INSERT_CHARACTER_QUERY = """
private static final UserCharacterMapper USER_CHARACTER_MAPPER = new UserCharacterMapper();
public static final String INSERT_CHARACTER_QUERY = """
INSERT INTO bungie_user_character (character_id, light_level, destiny_class, discord_user_id)
VALUES (:characterId, :lightLevel, :destinyClass, :discordUserId)""";

private final UserCharacterMapper userCharacterMapper;
private final DatabaseClient databaseClient;

public UserCharacterRepositoryImpl(
UserCharacterMapper userCharacterMapper,
DatabaseClient databaseClient) {
this.userCharacterMapper = userCharacterMapper;
this.databaseClient = databaseClient;
}

Expand All @@ -34,7 +32,7 @@ public Mono<UserCharacter> save(UserCharacter userCharacter) {
);
return databaseClient.sql(INSERT_CHARACTER_QUERY)
.bindValues(saveParameters)
.map(userCharacterMapper::apply)
.map(USER_CHARACTER_MAPPER::apply)
.one();
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package com.danielvm.destiny2bot.repository;

import static com.danielvm.destiny2bot.repository.BotUserRepositoryImpl.INSERT_USER_QUERY;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import com.danielvm.destiny2bot.entity.BotUser;
import com.danielvm.destiny2bot.entity.UserCharacter;
import com.danielvm.destiny2bot.exception.ResourceNotFoundException;
import com.danielvm.destiny2bot.mapper.BotUserMapper;
import io.r2dbc.spi.Row;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import org.junit.jupiter.api.DisplayName;
Expand All @@ -18,6 +22,7 @@
import org.springframework.r2dbc.core.DatabaseClient;
import org.springframework.r2dbc.core.DatabaseClient.GenericExecuteSpec;
import org.springframework.r2dbc.core.RowsFetchSpec;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import reactor.test.StepVerifier.FirstStep;
Expand All @@ -28,18 +33,6 @@ public class BotUserRepositoryImplTest {
@Mock
DatabaseClient databaseClient;

@Mock
GenericExecuteSpec genericExecuteSpec;

@Mock
RowsFetchSpec<BotUser> rowsFetchSpec;

@Mock
BotUserMapper botUserMapper;

@Mock
Row row;

@InjectMocks
BotUserRepositoryImpl sut;

Expand All @@ -49,6 +42,7 @@ public void retrieveBotUserTest() {
// given: a bot user to save to the DB
BotUser user = new BotUser(1234L, "Deahtstroke", 56789L,
"someAccessToken", "someRefreshToken", 3600L, null);
BotUserMapper mapper = new BotUserMapper();

Map<String, Object> params = Map.of(
"discordId", user.getDiscordId(),
Expand All @@ -59,21 +53,17 @@ public void retrieveBotUserTest() {
"tokenExpiration", user.getBungieTokenExpiration()
);

when(databaseClient.sql("""
INSERT INTO bot_user (discord_id, discord_username, bungie_membership_id,
bungie_access_token, bungie_refresh_token, bungie_token_expiration)
VALUES (:discordId, :discordUsername, :membershipId, :accessToken,
:refreshToken, :tokenExpiration)
"""))
RowsFetchSpec<BotUser> botUserRowsFetchSpec = mock(RowsFetchSpec.class);
GenericExecuteSpec genericExecuteSpec = mock(GenericExecuteSpec.class);
when(databaseClient.sql(INSERT_USER_QUERY))
.thenReturn(genericExecuteSpec);

when(genericExecuteSpec.bindValues(params))
.thenReturn(genericExecuteSpec);

when(genericExecuteSpec.map(any(BiFunction.class)))
.thenReturn(rowsFetchSpec);
.thenReturn(botUserRowsFetchSpec);

when(rowsFetchSpec.one())
when(botUserRowsFetchSpec.one())
.thenReturn(Mono.just(user));

// when: save is called with the user
Expand All @@ -88,4 +78,111 @@ INSERT INTO bot_user (discord_id, discord_username, bungie_membership_id,
}).verifyComplete();
}

@Test
@DisplayName("findByDiscordId is successful")
public void findByDiscordIdIsSuccessful() {
// given: some DiscordId
Long discordId = 173312L;

List<UserCharacter> characters = List.of(
new UserCharacter(1L, 1810, "Titan", 12L),
new UserCharacter(2L, 1789, "Hunter", 12L)
);
BotUser user = new BotUser(12L, "deaht", 34L,
"someAccessToken", "someRefreshToken", 3600L, null);

GenericExecuteSpec botUserGenericSpec = mock(GenericExecuteSpec.class);
RowsFetchSpec<BotUser> botUserRowsFetchSpec = mock(RowsFetchSpec.class);
GenericExecuteSpec userCharacterGenericSpec = mock(GenericExecuteSpec.class);
RowsFetchSpec<UserCharacter> userCharacterRowsFetchSpec = mock(RowsFetchSpec.class);
when(databaseClient.sql("""
SELECT bu.discord_id,
bu.discord_username,
bu.bungie_membership_id,
bu.bungie_access_token,
bu.bungie_refresh_token,
bu.bungie_token_expiration
FROM bot_user bu
WHERE bu.discord_id = :discordId
""")).thenReturn(botUserGenericSpec);
when(botUserGenericSpec.bind("discordId", discordId)).thenReturn(botUserGenericSpec);
when(botUserGenericSpec.map(any(BiFunction.class))).thenReturn(botUserRowsFetchSpec);
when(botUserRowsFetchSpec.first()).thenReturn(Mono.just(user));

when(databaseClient.sql("""
SELECT buc.character_id,
buc.light_level,
buc.destiny_class,
buc.discord_user_id
FROM bot_user bu
INNER JOIN bungie_user_character buc on
bu.discord_id = buc.discord_user_id
WHERE bu.discord_id = :discordId
""")).thenReturn(userCharacterGenericSpec);
when(userCharacterGenericSpec.bind("discordId", discordId)).thenReturn(
userCharacterGenericSpec);
when(userCharacterGenericSpec.map(any(BiFunction.class))).thenReturn(
userCharacterRowsFetchSpec);
when(userCharacterRowsFetchSpec.all()).thenReturn(Flux.fromIterable(characters));

// when: findByDiscordId is called
var result = StepVerifier.create(sut.findBotUserByDiscordId(discordId));

// then: the found botUser has the correct fields
result.assertNext(botUser -> {
assertThat(botUser.getBungieTokenExpiration()).isEqualTo(user.getBungieTokenExpiration());
assertThat(botUser.getBungieMembershipId()).isEqualTo(user.getBungieMembershipId());
assertThat(botUser.getBungieRefreshToken()).isEqualTo(user.getBungieRefreshToken());
assertThat(botUser.getBungieAccessToken()).isEqualTo(user.getBungieAccessToken());
assertThat(botUser.getDiscordUsername()).isEqualTo(user.getDiscordUsername());
assertThat(botUser.getDiscordId()).isEqualTo(user.getDiscordId());
botUser.getCharacters().forEach(character -> {
if (character.getCharacterId().equals(characters.get(0).getCharacterId())) {
assertThat(character.getDiscordUserId()).isEqualTo(characters.get(0).getDiscordUserId());
assertThat(character.getDestinyClass()).isEqualTo(characters.get(0).getDestinyClass());
assertThat(character.getLightLevel()).isEqualTo(characters.get(0).getLightLevel());
} else {
assertThat(character.getDiscordUserId()).isEqualTo(characters.get(1).getDiscordUserId());
assertThat(character.getDestinyClass()).isEqualTo(characters.get(1).getDestinyClass());
assertThat(character.getLightLevel()).isEqualTo(characters.get(1).getLightLevel());
}
});
}).verifyComplete();
}

@Test
@DisplayName("findByDiscordId throws exception when Bot User is not found")
public void findByDiscordIdShouldThrowException() {
// given: some DiscordId
Long discordId = 173312L;

GenericExecuteSpec botUserGenericSpec = mock(GenericExecuteSpec.class);
RowsFetchSpec<BotUser> botUserRowsFetchSpec = mock(RowsFetchSpec.class);
when(databaseClient.sql("""
SELECT bu.discord_id,
bu.discord_username,
bu.bungie_membership_id,
bu.bungie_access_token,
bu.bungie_refresh_token,
bu.bungie_token_expiration
FROM bot_user bu
WHERE bu.discord_id = :discordId
""")).thenReturn(botUserGenericSpec);
when(botUserGenericSpec.bind("discordId", discordId)).thenReturn(botUserGenericSpec);
when(botUserGenericSpec.map(any(BiFunction.class))).thenReturn(botUserRowsFetchSpec);
when(botUserRowsFetchSpec.first()).thenReturn(Mono.empty());

// when: findByDiscordId is called
// then: a ResourceNotFoundException is thrown as a result of an empty Mono
StepVerifier.create(sut.findBotUserByDiscordId(discordId))
.expectError(ResourceNotFoundException.class)
.verify();

// and: the appropriate error message is thrown with the exception
StepVerifier.create(sut.findBotUserByDiscordId(discordId))
.expectErrorMessage("Discord user with Id [%s] not found".formatted(discordId))
.verify();

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.danielvm.destiny2bot.repository;

import static com.danielvm.destiny2bot.repository.UserCharacterRepositoryImpl.INSERT_CHARACTER_QUERY;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import com.danielvm.destiny2bot.entity.UserCharacter;
import java.util.Map;
import java.util.function.BiFunction;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.r2dbc.core.DatabaseClient;
import org.springframework.r2dbc.core.DatabaseClient.GenericExecuteSpec;
import org.springframework.r2dbc.core.RowsFetchSpec;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;

@ExtendWith(MockitoExtension.class)
public class UserCharacterRepositoryImplTest {

@Mock
private DatabaseClient databaseClient;

@InjectMocks
private UserCharacterRepositoryImpl sut;

@Test
@DisplayName("Save user character is successful")
public void saveUserCharacterIsSuccessful() {
// given: a user character to save
UserCharacter userCharacter = new UserCharacter(
1L, 1801, "Titan", 12345L);

Map<String, Object> parameters = Map.of(
"characterId", userCharacter.getCharacterId(),
"lightLevel", userCharacter.getLightLevel(),
"discordUserId", userCharacter.getDiscordUserId(),
"destinyClass", userCharacter.getDestinyClass()
);

GenericExecuteSpec genericExecuteSpec = mock(GenericExecuteSpec.class);
RowsFetchSpec<UserCharacter> rowsFetchSpec = mock(RowsFetchSpec.class);

when(databaseClient.sql(INSERT_CHARACTER_QUERY)).thenReturn(genericExecuteSpec);
when(genericExecuteSpec.bindValues(parameters)).thenReturn(genericExecuteSpec);
when(genericExecuteSpec.map(any(BiFunction.class))).thenReturn(rowsFetchSpec);
when(rowsFetchSpec.one()).thenReturn(Mono.just(userCharacter));

// when: save is called for a user character
var result = StepVerifier.create(sut.save(userCharacter));

// then: the saved character is returned
result.assertNext(user -> {
assertThat(user.getDiscordUserId()).isEqualTo(userCharacter.getDiscordUserId());
assertThat(user.getCharacterId()).isEqualTo(userCharacter.getCharacterId());
assertThat(user.getLightLevel()).isEqualTo(userCharacter.getLightLevel());
assertThat(user.getDestinyClass()).isEqualTo(userCharacter.getDestinyClass());
}).verifyComplete();
}

}

0 comments on commit f5be655

Please sign in to comment.