import de.juplo.kafka.chat.backend.domain.InvalidUsernameException;
import de.juplo.kafka.chat.backend.domain.MessageMutationException;
+import de.juplo.kafka.chat.backend.domain.ShardNotOwnedException;
import de.juplo.kafka.chat.backend.domain.UnknownChatroomException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
return problem;
}
+ @ExceptionHandler(ShardNotOwnedException.class)
+ public final ProblemDetail handleException(
+ ShardNotOwnedException e,
+ ServerWebExchange exchange,
+ UriComponentsBuilder uriComponentsBuilder)
+ {
+ final HttpStatus status = HttpStatus.NOT_FOUND;
+ ProblemDetail problem = ProblemDetail.forStatus(status);
+
+ problem.setProperty("timestamp", new Date());
+
+ problem.setProperty("requestId", exchange.getRequest().getId());
+
+ problem.setType(uriComponentsBuilder.replacePath(contextPath).path("/problem/shard-not-owned").build().toUri());
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append(status.getReasonPhrase());
+ stringBuilder.append(" - ");
+ stringBuilder.append(e.getMessage());
+ problem.setTitle(stringBuilder.toString());
+
+ stringBuilder.setLength(0);
+ stringBuilder.append("Shard not owned: ");
+ stringBuilder.append(e.getShard());
+ problem.setDetail(stringBuilder.toString());
+
+ problem.setProperty("shard", e.getShard());
+
+ return problem;
+ }
+
@ExceptionHandler(MessageMutationException.class)
public final ProblemDetail handleException(
MessageMutationException e,
package de.juplo.kafka.chat.backend.api;
+import de.juplo.kafka.chat.backend.ChatBackendProperties;
import de.juplo.kafka.chat.backend.domain.*;
import de.juplo.kafka.chat.backend.persistence.inmemory.InMemoryChatHomeService;
import lombok.extern.slf4j.Slf4j;
import java.time.Clock;
import java.time.LocalDateTime;
+import java.util.Set;
import java.util.UUID;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@SpringBootTest(properties = {
"spring.main.allow-bean-definition-overriding=true",
- "chat.backend.inmemory.sharding-strategy=none" })
+ "chat.backend.inmemory.sharding-strategy=kafkalike",
+ "chat.backend.inmemory.num-shards=10",
+ "chat.backend.inmemory.owned-shards=6",
+ })
@AutoConfigureWebTestClient
@Slf4j
public class ChatBackendControllerTest
{
+ @Autowired
+ ChatBackendProperties properties;
+ @Autowired
+ ShardingStrategy shardingStrategy;
+
@MockBean
InMemoryChatHomeService chatHomeService;
@MockBean
void testUnknownChatroomExceptionForListChatroom(@Autowired WebTestClient client)
{
// Given
- UUID chatroomId = UUID.randomUUID();
+ UUID chatroomId = getRandomIdForOwnedShard();
when(chatHomeService.getChatRoom(anyInt(), any(UUID.class))).thenReturn(Mono.empty());
// When
void testUnknownChatroomExceptionForGetChatroom(@Autowired WebTestClient client)
{
// Given
- UUID chatroomId = UUID.randomUUID();
+ UUID chatroomId = getRandomIdForOwnedShard();
when(chatHomeService.getChatRoom(anyInt(), any(UUID.class))).thenReturn(Mono.empty());
// When
void testUnknownChatroomExceptionForPutMessage(@Autowired WebTestClient client)
{
// Given
- UUID chatroomId = UUID.randomUUID();
+ UUID chatroomId = getRandomIdForOwnedShard();
String username = "foo";
Long messageId = 66l;
when(chatHomeService.getChatRoom(anyInt(), any(UUID.class))).thenReturn(Mono.empty());
void testUnknownChatroomExceptionForGetMessage(@Autowired WebTestClient client)
{
// Given
- UUID chatroomId = UUID.randomUUID();
+ UUID chatroomId = getRandomIdForOwnedShard();
String username = "foo";
Long messageId = 66l;
when(chatHomeService.getChatRoom(anyInt(), any(UUID.class))).thenReturn(Mono.empty());
void testUnknownChatroomExceptionForListenChatroom(@Autowired WebTestClient client)
{
// Given
- UUID chatroomId = UUID.randomUUID();
+ UUID chatroomId = getRandomIdForOwnedShard();
when(chatHomeService.getChatRoom(anyInt(), any(UUID.class))).thenReturn(Mono.empty());
// When
@Test
@DisplayName("Assert expected problem-details for message mutation on PUT /put/{chatroomId}/{username}/{messageId}")
- void testMessageMutationException(@Autowired WebTestClient client) throws Exception
+ void testMessageMutationException(@Autowired WebTestClient client)
{
// Given
- UUID chatroomId = UUID.randomUUID();
+ UUID chatroomId = getRandomIdForOwnedShard();
String user = "foo";
Long messageId = 66l;
Message.MessageKey key = Message.MessageKey.of(user, messageId);
@Test
@DisplayName("Assert expected problem-details for invalid username on PUT /put/{chatroomId}/{username}/{messageId}")
- void testInvalidUsernameException(@Autowired WebTestClient client) throws Exception
+ void testInvalidUsernameException(@Autowired WebTestClient client)
{
// Given
- UUID chatroomId = UUID.randomUUID();
+ UUID chatroomId = getRandomIdForOwnedShard();
String user = "Foo";
Long messageId = 66l;
Message.MessageKey key = Message.MessageKey.of(user, messageId);
.jsonPath("$.username").isEqualTo(user);
verify(chatRoomService, never()).persistMessage(eq(key), any(LocalDateTime.class), any(String.class));
}
+
+ @Test
+ @DisplayName("Assert expected problem-details for unknown chatroom on GET /{chatroomId}")
+ void testShardNotOwnedExceptionForGetChatroom(@Autowired WebTestClient client)
+ {
+ // Given
+ UUID chatroomId = getRandomIdForForeignShard();
+
+ // When
+ WebTestClient.ResponseSpec responseSpec = client
+ .get()
+ .uri("/{chatroomId}", chatroomId)
+ .accept(MediaType.APPLICATION_JSON)
+ .exchange();
+
+ // Then
+ assertProblemDetailsForShardNotOwnedException(responseSpec, shardingStrategy.selectShard(chatroomId));
+ }
+
+ @Test
+ @DisplayName("Assert expected problem-details for not owned shard on GET /list/{chatroomId}")
+ void testShardNotOwnedExceptionForListChatroom(@Autowired WebTestClient client)
+ {
+ // Given
+ UUID chatroomId = getRandomIdForForeignShard();
+
+ // When
+ WebTestClient.ResponseSpec responseSpec = client
+ .get()
+ .uri("/{chatroomId}/list", chatroomId)
+ .accept(MediaType.APPLICATION_JSON)
+ .exchange();
+
+ // Then
+ assertProblemDetailsForShardNotOwnedException(responseSpec, shardingStrategy.selectShard(chatroomId));
+ }
+
+ @Test
+ @DisplayName("Assert expected problem-details for now owned shard on PUT /put/{chatroomId}/{username}/{messageId}")
+ void testShardNotOwnedExceptionForPutMessage(@Autowired WebTestClient client)
+ {
+ // Given
+ UUID chatroomId = getRandomIdForForeignShard();
+ String username = "foo";
+ Long messageId = 66l;
+ when(chatHomeService.getChatRoom(anyInt(), any(UUID.class))).thenReturn(Mono.empty());
+
+ // When
+ WebTestClient.ResponseSpec responseSpec = client
+ .put()
+ .uri(
+ "/{chatroomId}/{username}/{messageId}",
+ chatroomId,
+ username,
+ messageId)
+ .bodyValue("bar")
+ .accept(MediaType.APPLICATION_JSON)
+ .exchange();
+
+ // Then
+ assertProblemDetailsForShardNotOwnedException(responseSpec, shardingStrategy.selectShard(chatroomId));
+ }
+
+ @Test
+ @DisplayName("Assert expected problem-details for not owned shard on GET /get/{chatroomId}/{username}/{messageId}")
+ void testShardNotOwnedExceptionForGetMessage(@Autowired WebTestClient client)
+ {
+ // Given
+ UUID chatroomId = getRandomIdForForeignShard();
+ String username = "foo";
+ Long messageId = 66l;
+ when(chatHomeService.getChatRoom(anyInt(), any(UUID.class))).thenReturn(Mono.empty());
+
+ // When
+ WebTestClient.ResponseSpec responseSpec = client
+ .get()
+ .uri(
+ "/{chatroomId}/{username}/{messageId}",
+ chatroomId,
+ username,
+ messageId)
+ .accept(MediaType.APPLICATION_JSON)
+ .exchange();
+
+ // Then
+ assertProblemDetailsForShardNotOwnedException(responseSpec, shardingStrategy.selectShard(chatroomId));
+ }
+
+ @Test
+ @DisplayName("Assert expected problem-details for not owned shard on GET /listen/{chatroomId}")
+ void testShardNotOwnedExceptionForListenChatroom(@Autowired WebTestClient client)
+ {
+ // Given
+ UUID chatroomId = getRandomIdForForeignShard();
+ when(chatHomeService.getChatRoom(anyInt(), any(UUID.class))).thenReturn(Mono.empty());
+
+ // When
+ WebTestClient.ResponseSpec responseSpec = client
+ .get()
+ .uri("/{chatroomId}/listen", chatroomId)
+ // .accept(MediaType.TEXT_EVENT_STREAM, MediaType.APPLICATION_JSON) << TODO: Does not work!
+ .exchange();
+
+ // Then
+ assertProblemDetailsForShardNotOwnedException(responseSpec, shardingStrategy.selectShard(chatroomId));
+ }
+
+ private void assertProblemDetailsForShardNotOwnedException(
+ WebTestClient.ResponseSpec responseSpec,
+ int shard)
+ {
+ responseSpec
+ .expectStatus().isNotFound()
+ .expectBody()
+ .jsonPath("$.type").isEqualTo("/problem/shard-not-owned")
+ .jsonPath("$.shard").isEqualTo(shard);
+ }
+
+ private UUID getRandomIdForOwnedShard()
+ {
+ Set<Integer> ownedShards = ownedShards();
+ UUID randomId;
+
+ do
+ {
+ randomId = UUID.randomUUID();
+ }
+ while (!ownedShards.contains(shardingStrategy.selectShard(randomId)));
+
+ return randomId;
+ }
+
+ private UUID getRandomIdForForeignShard()
+ {
+ Set<Integer> ownedShards = ownedShards();
+ UUID randomId;
+
+ do
+ {
+ randomId = UUID.randomUUID();
+ }
+ while (ownedShards.contains(shardingStrategy.selectShard(randomId)));
+
+ return randomId;
+ }
+
+ private Set<Integer> ownedShards()
+ {
+ return IntStream
+ .of(properties.getInmemory().getOwnedShards())
+ .mapToObj(shard -> Integer.valueOf(shard))
+ .collect(Collectors.toSet());
+ }
}