feat: `UnknownChatroomException` records shard and owned shards
[demos/kafka/chat] / src / test / java / de / juplo / kafka / chat / backend / api / ChatBackendControllerTest.java
index 12d34be..32b1e35 100644 (file)
@@ -3,23 +3,24 @@ 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 de.juplo.kafka.chat.backend.persistence.inmemory.ShardingStrategy;
 import lombok.extern.slf4j.Slf4j;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
 import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.boot.test.context.TestConfiguration;
 import org.springframework.boot.test.mock.mockito.MockBean;
-import org.springframework.context.annotation.Bean;
 import org.springframework.http.MediaType;
 import org.springframework.test.web.reactive.server.WebTestClient;
 import reactor.core.publisher.Mono;
 
 import java.time.Clock;
 import java.time.LocalDateTime;
-import java.util.Arrays;
+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.*;
@@ -27,11 +28,19 @@ import static org.mockito.Mockito.*;
 
 @SpringBootTest(properties = {
     "spring.main.allow-bean-definition-overriding=true",
-    "chat.backend.inmemory.owned-shards=0,1,2,3,4,5,6,7,8,9" })
+    "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
@@ -42,8 +51,9 @@ public class ChatBackendControllerTest
   void testUnknownChatroomExceptionForListChatroom(@Autowired WebTestClient client)
   {
     // Given
-    UUID chatroomId = UUID.randomUUID();
+    UUID chatroomId = getRandomIdForOwnedShard();
     when(chatHomeService.getChatRoom(anyInt(), any(UUID.class))).thenReturn(Mono.empty());
+    when(chatHomeService.getOwnedShards()).thenReturn(new int[] { 6 });
 
     // When
     WebTestClient.ResponseSpec responseSpec = client
@@ -62,8 +72,9 @@ public class ChatBackendControllerTest
   void testUnknownChatroomExceptionForGetChatroom(@Autowired WebTestClient client)
   {
     // Given
-    UUID chatroomId = UUID.randomUUID();
+    UUID chatroomId = getRandomIdForOwnedShard();
     when(chatHomeService.getChatRoom(anyInt(), any(UUID.class))).thenReturn(Mono.empty());
+    when(chatHomeService.getOwnedShards()).thenReturn(new int[] { 6 });
 
     // When
     WebTestClient.ResponseSpec responseSpec = client
@@ -81,10 +92,11 @@ public class ChatBackendControllerTest
   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());
+    when(chatHomeService.getOwnedShards()).thenReturn(new int[] { 6 });
 
     // When
     WebTestClient.ResponseSpec responseSpec = client
@@ -107,10 +119,11 @@ public class ChatBackendControllerTest
   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());
+    when(chatHomeService.getOwnedShards()).thenReturn(new int[] { 6 });
 
     // When
     WebTestClient.ResponseSpec responseSpec = client
@@ -132,8 +145,9 @@ public class ChatBackendControllerTest
   void testUnknownChatroomExceptionForListenChatroom(@Autowired WebTestClient client)
   {
     // Given
-    UUID chatroomId = UUID.randomUUID();
+    UUID chatroomId = getRandomIdForOwnedShard();
     when(chatHomeService.getChatRoom(anyInt(), any(UUID.class))).thenReturn(Mono.empty());
+    when(chatHomeService.getOwnedShards()).thenReturn(new int[] { 6 });
 
     // When
     WebTestClient.ResponseSpec responseSpec = client
@@ -159,10 +173,10 @@ public class ChatBackendControllerTest
 
   @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);
@@ -187,7 +201,7 @@ public class ChatBackendControllerTest
         .thenReturn(Mono.just(existingMessage));
     // Needed for readable error-reports, in case of a bug that leads to according unwanted call
     when(chatRoomService.persistMessage(any(Message.MessageKey.class), any(LocalDateTime.class), any(String.class)))
-        .thenReturn(mock(Message.class));
+        .thenReturn(Mono.just(mock(Message.class)));
 
     // When
     client
@@ -215,10 +229,10 @@ public class ChatBackendControllerTest
 
   @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);
@@ -235,7 +249,7 @@ public class ChatBackendControllerTest
         .thenReturn(Mono.empty());
     // Needed for readable error-reports, in case of a bug that leads to according unwanted call
     when(chatRoomService.persistMessage(any(Message.MessageKey.class), any(LocalDateTime.class), any(String.class)))
-        .thenReturn(mock(Message.class));
+        .thenReturn(Mono.just(mock(Message.class)));
 
     // When
     client
@@ -256,19 +270,156 @@ public class ChatBackendControllerTest
     verify(chatRoomService, never()).persistMessage(eq(key), any(LocalDateTime.class), any(String.class));
   }
 
-  @TestConfiguration
-  static class Config
+  @Test
+  @DisplayName("Assert expected problem-details for not owned shard 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)
   {
-    @Bean
-    ChatHome[] chatHomes(
-        ChatBackendProperties properties,
-        ChatHomeFactory factory)
+    // 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
     {
-      ChatHome[] chatHomes = new ChatHome[properties.getInmemory().getNumShards()];
-      Arrays
-          .stream(properties.getInmemory().getOwnedShards())
-          .forEach(i -> chatHomes[i] = factory.createChatHome(i));
-      return chatHomes;
+      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());
   }
 }