diff --git a/chat-access/src/main/kotlin/interview/chataccess/ChatAccessApplication.kt b/chat-access/src/main/kotlin/interview/chataccess/ChatAccessApplication.kt index 952c614..6433903 100644 --- a/chat-access/src/main/kotlin/interview/chataccess/ChatAccessApplication.kt +++ b/chat-access/src/main/kotlin/interview/chataccess/ChatAccessApplication.kt @@ -10,7 +10,7 @@ import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories @ConfigurationPropertiesScan @EntityScan("interview.chataccess.dbmodel") @EnableR2dbcRepositories("interview.chataccess.dbmodel") -open class ChatAccessApplication +class ChatAccessApplication fun main(args: Array) { runApplication(*args) diff --git a/chat-access/src/main/kotlin/interview/chataccess/controller/MessageController.kt b/chat-access/src/main/kotlin/interview/chataccess/controller/MessageController.kt index f5dfe12..c988715 100644 --- a/chat-access/src/main/kotlin/interview/chataccess/controller/MessageController.kt +++ b/chat-access/src/main/kotlin/interview/chataccess/controller/MessageController.kt @@ -1,6 +1,5 @@ package interview.chataccess.controller - import interview.chataccess.controller.UtilsRestController.getPrincipal import interview.chataccess.controller.exceptions.ChatSessionNotFoundException import interview.chataccess.controller.exceptions.GenericRestException @@ -10,6 +9,7 @@ import interview.chataccess.dto.* import interview.chataccess.service.ChatService import interview.chataccess.service.MessageService import interview.chataccess.utils.logger +import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.* @@ -59,7 +59,8 @@ class MessageController( suspend fun sendMessage(@RequestBody input: ChatMessageSendDTO): Flow = coroutineScope { val principal = getPrincipal() - val sId = async { + // Those two coroutines do not need to be LAZY, but it doesn't hurt. + val sId = async(start = CoroutineStart.LAZY) { chatService .findChatByIdForUsername(input.chatSessionId, principal) ?: throw ChatSessionNotFoundException( @@ -74,7 +75,7 @@ class MessageController( // both of the queries asynchronously for all the other cases. // We take advantage of the coroutines property that error on one of them will cancel // the parent context (when not using supervisor). - val pastMessages = async { + val pastMessages = async(start = CoroutineStart.LAZY) { messageService .findMessagesInChatSessionForUsername(input.chatSessionId, principal) .map { message -> diff --git a/chat-access/src/main/kotlin/interview/chataccess/controller/UtilsRestController.kt b/chat-access/src/main/kotlin/interview/chataccess/controller/UtilsRestController.kt index 47b9975..ffa0d7b 100644 --- a/chat-access/src/main/kotlin/interview/chataccess/controller/UtilsRestController.kt +++ b/chat-access/src/main/kotlin/interview/chataccess/controller/UtilsRestController.kt @@ -15,12 +15,12 @@ object UtilsRestController { .awaitSingle() .authentication.principal .let { authPrincipal -> - when { - authPrincipal is User -> { + when (authPrincipal) { + is User -> { authPrincipal.username } - authPrincipal is Jwt -> { + is Jwt -> { authPrincipal .claims["email"] .toString() diff --git a/chat-access/src/test/kotlin/interview/chataccess/BaseTests.kt b/chat-access/src/test/kotlin/interview/chataccess/BaseTests.kt index bd64b56..d0783b8 100644 --- a/chat-access/src/test/kotlin/interview/chataccess/BaseTests.kt +++ b/chat-access/src/test/kotlin/interview/chataccess/BaseTests.kt @@ -13,23 +13,18 @@ open class BaseTests { private var internalCounter: Int = 0 - fun genChatSession(): ChatSession { - internalCounter++ - return ChatSession.from( - name = internalCounter.toString(), + fun genChatSession() = + ChatSession.from( + name = (++internalCounter).toString(), model = TESTS_DEFAULT_MODEL, username = TESTS_DEFAULT_USERNAME ) - } - fun genChatMessage(chatSession: ChatSession): ChatMessage { - internalCounter++ - return ChatMessage.from( + fun genChatMessage(chatSession: ChatSession) = + ChatMessage.from( chatSession, ChatRoles.USER, - "Test message ${internalCounter}", + "Test message ${(++internalCounter)}", true ) - } - } \ No newline at end of file diff --git a/chat-access/src/test/kotlin/interview/chataccess/applicationevents/ApplicationStartupEventTests.kt b/chat-access/src/test/kotlin/interview/chataccess/applicationevents/ApplicationStartupEventTests.kt index 6cef1c8..0f46b75 100644 --- a/chat-access/src/test/kotlin/interview/chataccess/applicationevents/ApplicationStartupEventTests.kt +++ b/chat-access/src/test/kotlin/interview/chataccess/applicationevents/ApplicationStartupEventTests.kt @@ -34,6 +34,7 @@ class ApplicationStartupEventTests : BaseOllamaClientTest() { .setBody("{\"status\":\"success\"}") ) } + ApplicationStartupEvent(appConfig, mockWebClient!!) .loadTestData() } @@ -46,7 +47,7 @@ class ApplicationStartupEventTests : BaseOllamaClientTest() { // The StartupEven will retry given number of times, // we need to return error for all the retires. assertThrows { - (0..ApplicationStartupEvent.MAX_RETRIES ) + (0..ApplicationStartupEvent.MAX_RETRIES) .forEach { log.debug("Enqueued $it...") mockWebServer!!.enqueue( @@ -57,7 +58,6 @@ class ApplicationStartupEventTests : BaseOllamaClientTest() { ApplicationStartupEvent(appConfig, mockWebClient!!) .loadTestData() - } } diff --git a/chat-access/src/test/kotlin/interview/chataccess/controller/ChatControllerTests.kt b/chat-access/src/test/kotlin/interview/chataccess/controller/ChatControllerTests.kt index fe8bd4a..c003734 100644 --- a/chat-access/src/test/kotlin/interview/chataccess/controller/ChatControllerTests.kt +++ b/chat-access/src/test/kotlin/interview/chataccess/controller/ChatControllerTests.kt @@ -50,293 +50,273 @@ class ChatControllerTests : BaseControllerTests() { @Test @DisplayName("ChatControllerTest: fetch models list") - fun getModelsList() { - runBlocking { - log.debug("Models in the test scenario: {}", appConfig.availableModels) + fun getModelsList(): Unit = runBlocking { + log.debug("Models in the test scenario: {}", appConfig.availableModels) - getWebTestClient() - .get() - .uri("/chat/models") - .accept(MediaType.APPLICATION_JSON) - .exchange() - .expectStatus() - .is2xxSuccessful - .expectBodyList(ChatSessionModelDTO::class.java) - .hasSize(appConfig.availableModels.size) - .contains( - *(appConfig.availableModels - .map { m -> - ChatSessionModelDTO(model = m) - }.toTypedArray()) - ) - } + getWebTestClient() + .get() + .uri("/chat/models") + .accept(MediaType.APPLICATION_JSON) + .exchange() + .expectStatus() + .is2xxSuccessful + .expectBodyList(ChatSessionModelDTO::class.java) + .hasSize(appConfig.availableModels.size) + .contains( + *(appConfig.availableModels + .map { m -> + ChatSessionModelDTO(model = m) + }.toTypedArray()) + ) } @Test @DisplayName("ChatControllerTest: fetch chat sessions list") - fun getSessions() { - runBlocking { - (1..5).map { genChatSession() } - .toTypedArray() - .also { chatSessions -> - given( - chatService - .findChatSessionsForUsername(TESTS_DEFAULT_USERNAME) - ).willReturn(Flux.just(*chatSessions).asFlow()) - } - .map { chatSession -> - ChatSessionDTO( - id = chatSession.id, - name = chatSession.name, - model = chatSession.model, - createdDate = chatSession.createdDate - ) - } - .toTypedArray() - .also { chatSessions -> - getWebTestClient() - .get() - .uri("/chat/session") - .accept(MediaType.TEXT_EVENT_STREAM) - .exchange() - .expectStatus() - .is2xxSuccessful - .expectBodyList(ChatSessionDTO::class.java) - .hasSize(chatSessions.size) - .contains(*chatSessions) - } - } + fun getSessions(): Unit = runBlocking { + (1..5).map { genChatSession() } + .toTypedArray() + .also { chatSessions -> + given( + chatService + .findChatSessionsForUsername(TESTS_DEFAULT_USERNAME) + ).willReturn(Flux.just(*chatSessions).asFlow()) + } + .map { chatSession -> + ChatSessionDTO( + id = chatSession.id, + name = chatSession.name, + model = chatSession.model, + createdDate = chatSession.createdDate + ) + } + .toTypedArray() + .also { chatSessions -> + getWebTestClient() + .get() + .uri("/chat/session") + .accept(MediaType.TEXT_EVENT_STREAM) + .exchange() + .expectStatus() + .is2xxSuccessful + .expectBodyList(ChatSessionDTO::class.java) + .hasSize(chatSessions.size) + .contains(*chatSessions) + } } @Test @DisplayName("ChatControllerTest: fetch chat session details") - fun getSessionDetails() { - runBlocking { - genChatSession() - .apply { - this.id = Random.nextInt(0, Int.MAX_VALUE) - } - .also { chatSession -> - given( - chatService - .findChatByIdForUsername(chatSession.id!!, TESTS_DEFAULT_USERNAME) - ).willReturn(chatSession) - } - .also { chatSession -> - getWebTestClient() - .get() - .uri("/chat/session/${chatSession.id}") - .exchange() - .expectStatus() - .is2xxSuccessful - .expectBody(ChatSessionDTO::class.java) - .value { response -> - response.id == chatSession.id - && response.name == chatSession.name - && response.model == chatSession.model - && response.createdDate == chatSession.createdDate - } - } - } + fun getSessionDetails(): Unit = runBlocking { + genChatSession() + .apply { + this.id = Random.nextInt(0, Int.MAX_VALUE) + } + .also { chatSession -> + given( + chatService + .findChatByIdForUsername(chatSession.id!!, TESTS_DEFAULT_USERNAME) + ).willReturn(chatSession) + } + .also { chatSession -> + getWebTestClient() + .get() + .uri("/chat/session/${chatSession.id}") + .exchange() + .expectStatus() + .is2xxSuccessful + .expectBody(ChatSessionDTO::class.java) + .value { response -> + response.id == chatSession.id + && response.name == chatSession.name + && response.model == chatSession.model + && response.createdDate == chatSession.createdDate + } + } } @Test @DisplayName("ChatControllerTest: fetch non-existent chat session details") - fun getSessionDetailsNonExist() { - runBlocking { - genChatSession() - .apply { - // We do not store it, i.e., not mock storing, just simulating, - // like it would be deleted in the meantime. - this.id = Random.nextInt(0, Int.MAX_VALUE) - } - .also { chatSession -> - given( - chatService - .findChatByIdForUsername(chatSession.id!!, TESTS_DEFAULT_USERNAME) - ).willReturn(null) - } - .also { chatSession -> - getWebTestClient() - .get() - .uri("/chat/session/${chatSession.id}") - .exchange() - .expectStatus() - .is4xxClientError - .expectBody(String::class.java) - .isEqualTo( - RestControllerAdvice.messageForSessionNotFound(chatSession.id!!) - ) - } - } + fun getSessionDetailsNonExist(): Unit = runBlocking { + genChatSession() + .apply { + // We do not store it, i.e., not mock storing, just simulating, + // like it would be deleted in the meantime. + this.id = Random.nextInt(0, Int.MAX_VALUE) + } + .also { chatSession -> + given( + chatService + .findChatByIdForUsername(chatSession.id!!, TESTS_DEFAULT_USERNAME) + ).willReturn(null) + } + .also { chatSession -> + getWebTestClient() + .get() + .uri("/chat/session/${chatSession.id}") + .exchange() + .expectStatus() + .is4xxClientError + .expectBody(String::class.java) + .isEqualTo( + RestControllerAdvice.messageForSessionNotFound(chatSession.id!!) + ) + } } - @Test @DisplayName("ChatControllerTest: delete non-existent chat session") - fun deleteChatSessionNotExist() { - runBlocking { - genChatSession() - .apply { - this.id = Random.nextInt(0, Int.MAX_VALUE) - } - .also { chatSession -> - given( - chatService - .delete(chatSession.id!!, TESTS_DEFAULT_USERNAME) - ).willReturn(false) - } - .also { chatSession -> - getWebTestClient() - .delete() - .uri("/chat/session/${chatSession.id}") - .exchange() - .expectStatus() - .is4xxClientError - .expectBody(String::class.java) - .isEqualTo( - RestControllerAdvice.messageForSessionNotFound(chatSession.id!!) - ) - } - } + fun deleteChatSessionNotExist(): Unit = runBlocking { + genChatSession() + .apply { + this.id = Random.nextInt(0, Int.MAX_VALUE) + } + .also { chatSession -> + given( + chatService + .delete(chatSession.id!!, TESTS_DEFAULT_USERNAME) + ).willReturn(false) + } + .also { chatSession -> + getWebTestClient() + .delete() + .uri("/chat/session/${chatSession.id}") + .exchange() + .expectStatus() + .is4xxClientError + .expectBody(String::class.java) + .isEqualTo( + RestControllerAdvice.messageForSessionNotFound(chatSession.id!!) + ) + } } @Test @DisplayName("ChatControllerTest: delete chat session") - fun deleteChatSession() { - runBlocking { - genChatSession() - .apply { - this.id = Random.nextInt(0, Int.MAX_VALUE) - } - .also { chatSession -> - given( - chatService - .delete(chatSession.id!!, TESTS_DEFAULT_USERNAME) - ).willReturn(true) - } - .also { chatSession -> - getWebTestClient() - .delete() - .uri("/chat/session/${chatSession.id}") - .exchange() - .expectStatus() - .is2xxSuccessful - .expectBody(String::class.java) - .isEqualTo("Deleted") - } - } + fun deleteChatSession(): Unit = runBlocking { + genChatSession() + .apply { + this.id = Random.nextInt(0, Int.MAX_VALUE) + } + .also { chatSession -> + given( + chatService + .delete(chatSession.id!!, TESTS_DEFAULT_USERNAME) + ).willReturn(true) + } + .also { chatSession -> + getWebTestClient() + .delete() + .uri("/chat/session/${chatSession.id}") + .exchange() + .expectStatus() + .is2xxSuccessful + .expectBody(String::class.java) + .isEqualTo("Deleted") + } } @Test @DisplayName("ChatControllerTest: create new chat session with wrong model") - fun createChatSessionWrongModel() { - runBlocking { - getWebTestClient() - .put() - .uri("/chat/session") - .bodyValue( - ChatSessionCreateNewDTO( - model = TESTS_DEFAULT_WRONG_MODEL, - name = "non-empty-name" - ) + fun createChatSessionWrongModel(): Unit = runBlocking { + getWebTestClient() + .put() + .uri("/chat/session") + .bodyValue( + ChatSessionCreateNewDTO( + model = TESTS_DEFAULT_WRONG_MODEL, + name = "non-empty-name" ) - .exchange() - .expectStatus() - .is4xxClientError - .expectBody(String::class.java) - .isEqualTo( - RestControllerAdvice.messageForUnsupportedMode(TESTS_DEFAULT_WRONG_MODEL) - ) - } + ) + .exchange() + .expectStatus() + .is4xxClientError + .expectBody(String::class.java) + .isEqualTo( + RestControllerAdvice.messageForUnsupportedMode(TESTS_DEFAULT_WRONG_MODEL) + ) } @Test @DisplayName("ChatControllerTest: create new chat session with empty name") - fun createChatSessionEmptyName() { - runBlocking { - getWebTestClient() - .put() - .uri("/chat/session") - .bodyValue( - ChatSessionCreateNewDTO( - model = TESTS_DEFAULT_MODEL, - name = "" // Empty by design for this test - ) + fun createChatSessionEmptyName(): Unit = runBlocking { + getWebTestClient() + .put() + .uri("/chat/session") + .bodyValue( + ChatSessionCreateNewDTO( + model = TESTS_DEFAULT_MODEL, + name = "" // Empty by design for this test ) - .exchange() - .expectStatus() - .is4xxClientError - .expectBody(String::class.java) - .isEqualTo( - RestControllerAdvice.messageForBlankSessionName() - ) - } + ) + .exchange() + .expectStatus() + .is4xxClientError + .expectBody(String::class.java) + .isEqualTo( + RestControllerAdvice.messageForBlankSessionName() + ) } @Test @DisplayName("ChatControllerTest: create new chat session with wrong model") - fun createChatSession() { - runBlocking { - // This one looked hideous when written functionally, so semi-imperative it goes. + fun createChatSession(): Unit = runBlocking { + // This one looked hideous when written functionally, so semi-imperative it goes. - val chatSession = genChatSession() + val chatSession = genChatSession() - val savedChatSession = chatSession - .copy() - .apply { - this.id = Random.nextInt(0, Int.MAX_VALUE) - this.createdDate = Instant.now() - this.modifiedDate = Instant.now() - } + val savedChatSession = chatSession + .copy() + .apply { + this.id = Random.nextInt(0, Int.MAX_VALUE) + this.createdDate = Instant.now() + this.modifiedDate = Instant.now() + } - val chatMessage = ChatMessage.from( - session = savedChatSession, - role = ChatRoles.BOT, - message = appConfig.defaultWelcomeMessage, - done = true + val chatMessage = ChatMessage.from( + session = savedChatSession, + role = ChatRoles.BOT, + message = appConfig.defaultWelcomeMessage, + done = true + ) + + val savedChatMessage = chatMessage + .copy() + .apply { + this.id = Random.nextInt(0, Int.MAX_VALUE) + this.createdDate = Instant.now() + this.modifiedDate = Instant.now() + } + + given( + chatService + .save(chatSession) + ).willReturn(savedChatSession) + + given( + messageService + .save(chatMessage) + ).willReturn(savedChatMessage) + + getWebTestClient() + .put() + .uri("/chat/session") + .bodyValue( + ChatSessionCreateNewDTO( + model = chatSession.model, + name = chatSession.name + ) ) - - val savedChatMessage = chatMessage - .copy() - .apply { - this.id = Random.nextInt(0, Int.MAX_VALUE) - this.createdDate = Instant.now() - this.modifiedDate = Instant.now() - } - - given( - chatService - .save(chatSession) - ).willReturn(savedChatSession) - - given( - messageService - .save(chatMessage) - ).willReturn(savedChatMessage) - - getWebTestClient() - .put() - .uri("/chat/session") - .bodyValue( - ChatSessionCreateNewDTO( - model = chatSession.model, - name = chatSession.name - ) + .exchange() + .expectStatus() + .is2xxSuccessful + .expectBodyList(ChatSessionDTO::class.java) + .contains( + ChatSessionDTO( + id = savedChatSession.id, + name = savedChatSession.name, + model = savedChatSession.model, + createdDate = savedChatSession.createdDate, ) - .exchange() - .expectStatus() - .is2xxSuccessful - .expectBodyList(ChatSessionDTO::class.java) - .contains( - ChatSessionDTO( - id = savedChatSession.id, - name = savedChatSession.name, - model = savedChatSession.model, - createdDate = savedChatSession.createdDate, - ) - ) - } - + ) } } diff --git a/chat-access/src/test/kotlin/interview/chataccess/controller/MessageControllerTests.kt b/chat-access/src/test/kotlin/interview/chataccess/controller/MessageControllerTests.kt index 0913408..f4d739d 100644 --- a/chat-access/src/test/kotlin/interview/chataccess/controller/MessageControllerTests.kt +++ b/chat-access/src/test/kotlin/interview/chataccess/controller/MessageControllerTests.kt @@ -47,375 +47,362 @@ class MessageControllerTests : BaseOllamaClientTest() { @Test @DisplayName("MessageControllerTest: fetch previous non-existent messages") - fun getPreviousMessagesNotExist() { - runBlocking { - val chatSessionId = Random.nextInt(0, Int.MAX_VALUE) + fun getPreviousMessagesNotExist(): Unit = runBlocking { + val chatSessionId = Random.nextInt(0, Int.MAX_VALUE) - given( - messageService - .findMessagesInChatSessionForUsername(chatSessionId, TESTS_DEFAULT_USERNAME) - ).willReturn(Mono.empty().asFlow()) + given( + messageService + .findMessagesInChatSessionForUsername(chatSessionId, TESTS_DEFAULT_USERNAME) + ).willReturn(Mono.empty().asFlow()) - getWebTestClient() - .get() - .uri("/message/previous/${chatSessionId}") - .exchange() - .expectStatus() - .is4xxClientError - .expectBody(String::class.java) - .isEqualTo( - RestControllerAdvice.messageForSessionNotFound(chatSessionId) - ) - } + getWebTestClient() + .get() + .uri("/message/previous/${chatSessionId}") + .exchange() + .expectStatus() + .is4xxClientError + .expectBody(String::class.java) + .isEqualTo( + RestControllerAdvice.messageForSessionNotFound(chatSessionId) + ) } @Test @DisplayName("MessageControllerTest: fetch previous non-existent messages") - fun getPrevious() { - runBlocking { - val chatSession = genChatSession() - .apply { - this.id = Random.nextInt(0, Int.MAX_VALUE) - } + fun getPrevious(): Unit = runBlocking { + val chatSession = genChatSession() + .apply { + this.id = Random.nextInt(0, Int.MAX_VALUE) + } - val chatMessages = (1..5).map { genChatMessage(chatSession) } - .toTypedArray() - .also { chatMessages -> - given( - messageService - .findMessagesInChatSessionForUsername(chatSession.id!!, chatSession.username) - ).willReturn(Flux.just(*chatMessages).asFlow()) - } + val chatMessages = (1..5).map { genChatMessage(chatSession) } + .toTypedArray() + .also { chatMessages -> + given( + messageService + .findMessagesInChatSessionForUsername(chatSession.id!!, chatSession.username) + ).willReturn(Flux.just(*chatMessages).asFlow()) + } - val expectedReply = chatMessages - .map { message -> - ChatMessagePastReplyDTO( - content = message.message, - role = message.role - ) - } - .reversed() - .toTypedArray() + val expectedReply = chatMessages + .map { message -> + ChatMessagePastReplyDTO( + content = message.message, + role = message.role + ) + } + .reversed() + .toTypedArray() - getWebTestClient() - .get() - .uri("/message/previous/${chatSession.id}") - .exchange() - .expectStatus() - .is2xxSuccessful - .expectBodyList(ChatMessagePastReplyDTO::class.java) - .hasSize(chatMessages.size) - .contains(*expectedReply) - } + getWebTestClient() + .get() + .uri("/message/previous/${chatSession.id}") + .exchange() + .expectStatus() + .is2xxSuccessful + .expectBodyList(ChatMessagePastReplyDTO::class.java) + .hasSize(chatMessages.size) + .contains(*expectedReply) } @Test @DisplayName("MessageControllerTest: send message to non-existent chat session") - fun sendMessageChatNotExist() { - runBlocking { - val chatSessionId = Random.nextInt(0, Int.MAX_VALUE) + fun sendMessageChatNotExist(): Unit = runBlocking { + val chatSessionId = Random.nextInt(0, Int.MAX_VALUE) - given( - chatService - .findChatByIdForUsername(chatSessionId, TESTS_DEFAULT_USERNAME) - ).willReturn(null) + given( + chatService + .findChatByIdForUsername(chatSessionId, TESTS_DEFAULT_USERNAME) + ).willReturn(null) - getWebTestClient() - .post() - .uri("/message/") - .bodyValue( - ChatMessageSendDTO( - chatSessionId = chatSessionId, - message = "non empty message", - ) + getWebTestClient() + .post() + .uri("/message/") + .bodyValue( + ChatMessageSendDTO( + chatSessionId = chatSessionId, + message = "non empty message", ) - .exchange() - .expectStatus() - .is4xxClientError - .expectBody(String::class.java) - .isEqualTo( - RestControllerAdvice.messageForSessionNotFound(chatSessionId) - ) - } + ) + .exchange() + .expectStatus() + .is4xxClientError + .expectBody(String::class.java) + .isEqualTo( + RestControllerAdvice.messageForSessionNotFound(chatSessionId) + ) } @Test @DisplayName("MessageControllerTest: send chat message (non-streaming reply)") - fun sendChatMessage() { - runBlocking { - val chatSession = genChatSession() - .apply { - this.id = Random.nextInt(0, Int.MAX_VALUE) - } + fun sendChatMessage(): Unit = runBlocking { + val chatSession = genChatSession() + .apply { + this.id = Random.nextInt(0, Int.MAX_VALUE) + } - given( - chatService - .findChatByIdForUsername(chatSession.id!!, TESTS_DEFAULT_USERNAME) - ).willReturn(chatSession) + given( + chatService + .findChatByIdForUsername(chatSession.id!!, TESTS_DEFAULT_USERNAME) + ).willReturn(chatSession) - // Simulate past messages for the chat - (1..5).map { genChatMessage(chatSession) } - .toTypedArray() - .also { chatMessages -> - given( - messageService - .findMessagesInChatSessionForUsername(chatSession.id!!, chatSession.username) - ).willReturn(Flux.just(*chatMessages).asFlow()) - } + // Simulate past messages for the chat + (1..5).map { genChatMessage(chatSession) } + .toTypedArray() + .also { chatMessages -> + given( + messageService + .findMessagesInChatSessionForUsername(chatSession.id!!, chatSession.username) + ).willReturn(Flux.just(*chatMessages).asFlow()) + } - val expectedSavedMessages = arrayOf( - ChatMessage.from( - chatSession, - ChatRoles.USER, - "Hello!", - done = true - ), - ChatMessage.from( - chatSession, - ChatRoles.BOT, - "Hello! How can I assist you today?", - done = true + val expectedSavedMessages = arrayOf( + ChatMessage.from( + chatSession, + ChatRoles.USER, + "Hello!", + done = true + ), + ChatMessage.from( + chatSession, + ChatRoles.BOT, + "Hello! How can I assist you today?", + done = true + ) + ) + given( + messageService + .saveAll(*expectedSavedMessages) + ).willReturn(Flux.just(*expectedSavedMessages).asFlow()) + + // Ollama llama3.2 generated response for "Hello!" + val mockResp = MockResponse() + mockResp.setResponseCode(200) + mockResp.addHeader("Content-Type", "application/json") + mockResp.setBody( + "{\"model\":\"llama3.2\"," + + "\"created_at\":\"2024-11-05T17:54:16.793334686Z\"," + + "\"message\":{" + + "\"role\":\"assistant\"," + + "\"content\":\"Hello! How can I assist you today?\"}," + + "\"done_reason\":\"stop\"," + + "\"done\":true," + + "\"total_duration\":698458587," + + "\"load_duration\":33044513," + + "\"prompt_eval_count\":27," + + "\"prompt_eval_duration\":182712000," + + "\"eval_count\":10,\"eval_duration\":440171000}" + ) + mockWebServer!!.enqueue(mockResp) + + val expectedReplies = arrayOf( + ChatMessagePartialReplyDTO(content = "Hello! How can I assist you today?", done = true), + ) + getWebTestClient() + .post() + .uri("/message/") + .bodyValue( + ChatMessageSendDTO( + chatSessionId = chatSession.id!!, + message = "Hello!", ) ) - given( - messageService - .saveAll(*expectedSavedMessages) - ).willReturn(Flux.just(*expectedSavedMessages).asFlow()) - - // Ollama llama3.2 generated response for "Hello!" - val mockResp = MockResponse() - mockResp.setResponseCode(200) - mockResp.addHeader("Content-Type", "application/json") - mockResp.setBody( - "{\"model\":\"llama3.2\"," + - "\"created_at\":\"2024-11-05T17:54:16.793334686Z\"," + - "\"message\":{" + - "\"role\":\"assistant\"," + - "\"content\":\"Hello! How can I assist you today?\"}," + - "\"done_reason\":\"stop\"," + - "\"done\":true," + - "\"total_duration\":698458587," + - "\"load_duration\":33044513," + - "\"prompt_eval_count\":27," + - "\"prompt_eval_duration\":182712000," + - "\"eval_count\":10,\"eval_duration\":440171000}" - ) - mockWebServer!!.enqueue(mockResp) - - val expectedReplies = arrayOf( - ChatMessagePartialReplyDTO(content = "Hello! How can I assist you today?", done = true), - ) - getWebTestClient() - .post() - .uri("/message/") - .bodyValue( - ChatMessageSendDTO( - chatSessionId = chatSession.id!!, - message = "Hello!", - ) - ) - .exchange() - .expectStatus() - .is2xxSuccessful - .expectBodyList(ChatMessagePartialReplyDTO::class.java) - .hasSize(1) - .contains(*expectedReplies) - } + .exchange() + .expectStatus() + .is2xxSuccessful + .expectBodyList(ChatMessagePartialReplyDTO::class.java) + .hasSize(1) + .contains(*expectedReplies) } @Test @DisplayName("MessageControllerTest: send chat message (streaming reply)") - fun sendChatMessageStreaming() { - runBlocking { - val chatSession = genChatSession() - .apply { - this.id = Random.nextInt(0, Int.MAX_VALUE) - } + fun sendChatMessageStreaming(): Unit = runBlocking { + val chatSession = genChatSession() + .apply { + this.id = Random.nextInt(0, Int.MAX_VALUE) + } - given( - chatService - .findChatByIdForUsername(chatSession.id!!, TESTS_DEFAULT_USERNAME) - ).willReturn(chatSession) + given( + chatService + .findChatByIdForUsername(chatSession.id!!, TESTS_DEFAULT_USERNAME) + ).willReturn(chatSession) - // Simulate past messages for the chat - (1..5).map { genChatMessage(chatSession) } - .toTypedArray() - .also { chatMessages -> - given( - messageService - .findMessagesInChatSessionForUsername(chatSession.id!!, chatSession.username) - ).willReturn(Flux.just(*chatMessages).asFlow()) - } + // Simulate past messages for the chat + (1..5).map { genChatMessage(chatSession) } + .toTypedArray() + .also { chatMessages -> + given( + messageService + .findMessagesInChatSessionForUsername(chatSession.id!!, chatSession.username) + ).willReturn(Flux.just(*chatMessages).asFlow()) + } - val expectedSavedMessages = arrayOf( - ChatMessage.from( - chatSession, - ChatRoles.USER, - "Hello!", - done = true - ), - ChatMessage.from( - chatSession, - ChatRoles.BOT, - "Hello! How can I assist you today?", - done = true + val expectedSavedMessages = arrayOf( + ChatMessage.from( + chatSession, + ChatRoles.USER, + "Hello!", + done = true + ), + ChatMessage.from( + chatSession, + ChatRoles.BOT, + "Hello! How can I assist you today?", + done = true + ) + ) + given( + messageService + .saveAll(*expectedSavedMessages) + ).willReturn(Flux.just(*expectedSavedMessages).asFlow()) + + // Ollama llama3.2 generated response for "Hello!" (streaming) + val mockResp = MockResponse() + mockResp.setResponseCode(200) + mockResp.addHeader("Content-Type", "application/x-ndjson") + mockResp.setBody( + "{\"model\":\"llama3.2\",\"created_at\":\"2024-11-05T18:10:43.800822113Z\",\"message\":{\"role\":\"assistant\",\"content\":\"Hello\"},\"done\":false}\n" + + "{\"model\":\"llama3.2\",\"created_at\":\"2024-11-05T18:10:43.851380825Z\",\"message\":{\"role\":\"assistant\",\"content\":\"!\"},\"done\":false}\n" + + "{\"model\":\"llama3.2\",\"created_at\":\"2024-11-05T18:10:43.905787498Z\",\"message\":{\"role\":\"assistant\",\"content\":\" How\"},\"done\":false}\n" + + "{\"model\":\"llama3.2\",\"created_at\":\"2024-11-05T18:10:43.968348153Z\",\"message\":{\"role\":\"assistant\",\"content\":\" can\"},\"done\":false}\n" + + "{\"model\":\"llama3.2\",\"created_at\":\"2024-11-05T18:10:44.024798921Z\",\"message\":{\"role\":\"assistant\",\"content\":\" I\"},\"done\":false}\n" + + "{\"model\":\"llama3.2\",\"created_at\":\"2024-11-05T18:10:44.077283283Z\",\"message\":{\"role\":\"assistant\",\"content\":\" assist\"},\"done\":false}\n" + + "{\"model\":\"llama3.2\",\"created_at\":\"2024-11-05T18:10:44.128354748Z\",\"message\":{\"role\":\"assistant\",\"content\":\" you\"},\"done\":false}\n" + + "{\"model\":\"llama3.2\",\"created_at\":\"2024-11-05T18:10:44.175314511Z\",\"message\":{\"role\":\"assistant\",\"content\":\" today\"},\"done\":false}\n" + + "{\"model\":\"llama3.2\",\"created_at\":\"2024-11-05T18:10:44.222567229Z\",\"message\":{\"role\":\"assistant\",\"content\":\"?\"},\"done\":false}\n" + + "{\"model\":\"llama3.2\",\"created_at\":\"2024-11-05T18:10:44.269542861Z\",\"message\":{\"role\":\"assistant\",\"content\":\"\"},\"done_reason\":\"stop\",\"done\":true,\"total_duration\":6914935430,\"load_duration\":5889542121,\"prompt_eval_count\":27,\"prompt_eval_duration\":514096000,\"eval_count\":10,\"eval_duration\":468746000}\n" + ) + mockWebServer!!.enqueue(mockResp) + + val expectedReplies = arrayOf( + ChatMessagePartialReplyDTO(content = "Hello", done = false), + ChatMessagePartialReplyDTO(content = "!", done = false), + ChatMessagePartialReplyDTO(content = " How", done = false), + ChatMessagePartialReplyDTO(content = " can", done = false), + ChatMessagePartialReplyDTO(content = " I", done = false), + ChatMessagePartialReplyDTO(content = " assist", done = false), + ChatMessagePartialReplyDTO(content = " you", done = false), + ChatMessagePartialReplyDTO(content = " today", done = false), + ChatMessagePartialReplyDTO(content = "?", done = false), + ChatMessagePartialReplyDTO(content = "", done = true), + ) + + getWebTestClient() + .post() + .uri("/message/") + .bodyValue( + ChatMessageSendDTO( + chatSessionId = chatSession.id!!, + message = "Hello!", ) ) - given( - messageService - .saveAll(*expectedSavedMessages) - ).willReturn(Flux.just(*expectedSavedMessages).asFlow()) - - // Ollama llama3.2 generated response for "Hello!" (streaming) - val mockResp = MockResponse() - mockResp.setResponseCode(200) - mockResp.addHeader("Content-Type", "application/x-ndjson") - mockResp.setBody( - "{\"model\":\"llama3.2\",\"created_at\":\"2024-11-05T18:10:43.800822113Z\",\"message\":{\"role\":\"assistant\",\"content\":\"Hello\"},\"done\":false}\n" + - "{\"model\":\"llama3.2\",\"created_at\":\"2024-11-05T18:10:43.851380825Z\",\"message\":{\"role\":\"assistant\",\"content\":\"!\"},\"done\":false}\n" + - "{\"model\":\"llama3.2\",\"created_at\":\"2024-11-05T18:10:43.905787498Z\",\"message\":{\"role\":\"assistant\",\"content\":\" How\"},\"done\":false}\n" + - "{\"model\":\"llama3.2\",\"created_at\":\"2024-11-05T18:10:43.968348153Z\",\"message\":{\"role\":\"assistant\",\"content\":\" can\"},\"done\":false}\n" + - "{\"model\":\"llama3.2\",\"created_at\":\"2024-11-05T18:10:44.024798921Z\",\"message\":{\"role\":\"assistant\",\"content\":\" I\"},\"done\":false}\n" + - "{\"model\":\"llama3.2\",\"created_at\":\"2024-11-05T18:10:44.077283283Z\",\"message\":{\"role\":\"assistant\",\"content\":\" assist\"},\"done\":false}\n" + - "{\"model\":\"llama3.2\",\"created_at\":\"2024-11-05T18:10:44.128354748Z\",\"message\":{\"role\":\"assistant\",\"content\":\" you\"},\"done\":false}\n" + - "{\"model\":\"llama3.2\",\"created_at\":\"2024-11-05T18:10:44.175314511Z\",\"message\":{\"role\":\"assistant\",\"content\":\" today\"},\"done\":false}\n" + - "{\"model\":\"llama3.2\",\"created_at\":\"2024-11-05T18:10:44.222567229Z\",\"message\":{\"role\":\"assistant\",\"content\":\"?\"},\"done\":false}\n" + - "{\"model\":\"llama3.2\",\"created_at\":\"2024-11-05T18:10:44.269542861Z\",\"message\":{\"role\":\"assistant\",\"content\":\"\"},\"done_reason\":\"stop\",\"done\":true,\"total_duration\":6914935430,\"load_duration\":5889542121,\"prompt_eval_count\":27,\"prompt_eval_duration\":514096000,\"eval_count\":10,\"eval_duration\":468746000}\n" - ) - mockWebServer!!.enqueue(mockResp) - - val expectedReplies = arrayOf( - ChatMessagePartialReplyDTO(content = "Hello", done = false), - ChatMessagePartialReplyDTO(content = "!", done = false), - ChatMessagePartialReplyDTO(content = " How", done = false), - ChatMessagePartialReplyDTO(content = " can", done = false), - ChatMessagePartialReplyDTO(content = " I", done = false), - ChatMessagePartialReplyDTO(content = " assist", done = false), - ChatMessagePartialReplyDTO(content = " you", done = false), - ChatMessagePartialReplyDTO(content = " today", done = false), - ChatMessagePartialReplyDTO(content = "?", done = false), - ChatMessagePartialReplyDTO(content = "", done = true), - ) - - getWebTestClient() - .post() - .uri("/message/") - .bodyValue( - ChatMessageSendDTO( - chatSessionId = chatSession.id!!, - message = "Hello!", - ) - ) - .exchange() - .expectStatus() - .is2xxSuccessful - .expectBodyList(ChatMessagePartialReplyDTO::class.java) - .hasSize(expectedReplies.size) - .contains(*expectedReplies) - } + .exchange() + .expectStatus() + .is2xxSuccessful + .expectBodyList(ChatMessagePartialReplyDTO::class.java) + .hasSize(expectedReplies.size) + .contains(*expectedReplies) } @Test @DisplayName("MessageControllerTest: Ollama API returns error") - fun sendChatMessageButGot4xx() { - runBlocking { - val chatSession = genChatSession() - .apply { - this.id = Random.nextInt(0, Int.MAX_VALUE) - } + fun sendChatMessageButGot4xx(): Unit = runBlocking { + val chatSession = genChatSession() + .apply { + this.id = Random.nextInt(0, Int.MAX_VALUE) + } - given( - chatService - .findChatByIdForUsername(chatSession.id!!, TESTS_DEFAULT_USERNAME) - ).willReturn(chatSession) + given( + chatService + .findChatByIdForUsername(chatSession.id!!, TESTS_DEFAULT_USERNAME) + ).willReturn(chatSession) - // Simulate past messages for the chat - (1..5).map { genChatMessage(chatSession) } - .toTypedArray() - .also { chatMessages -> - given( - messageService - .findMessagesInChatSessionForUsername(chatSession.id!!, chatSession.username) - ).willReturn(Flux.just(*chatMessages).asFlow()) - } + // Simulate past messages for the chat + (1..5).map { genChatMessage(chatSession) } + .toTypedArray() + .also { chatMessages -> + given( + messageService + .findMessagesInChatSessionForUsername(chatSession.id!!, chatSession.username) + ).willReturn(Flux.just(*chatMessages).asFlow()) + } - val mockResp = MockResponse() - mockResp.setResponseCode(404) - mockWebServer!!.enqueue(mockResp) + val mockResp = MockResponse() + mockResp.setResponseCode(404) + mockWebServer!!.enqueue(mockResp) - getWebTestClient() - .post() - .uri("/message/") - .bodyValue( - ChatMessageSendDTO( - chatSessionId = chatSession.id!!, - message = "Hello!", - ) + getWebTestClient() + .post() + .uri("/message/") + .bodyValue( + ChatMessageSendDTO( + chatSessionId = chatSession.id!!, + message = "Hello!", ) - .exchange() - .expectStatus() - .is5xxServerError - .expectBody(String::class.java) - .value { - it.startsWith(RestControllerAdvice.messagePrefixForGenericRestException()) - } - } + ) + .exchange() + .expectStatus() + .is5xxServerError + .expectBody(String::class.java) + .value { + it.startsWith(RestControllerAdvice.messagePrefixForGenericRestException()) + } } @Test @DisplayName("MessageControllerTest: send chat message (streaming reply with error in middle)") - fun sendChatMessageStreamingError() { - runBlocking { - val chatSession = genChatSession() - .apply { - this.id = Random.nextInt(0, Int.MAX_VALUE) - } + fun sendChatMessageStreamingError(): Unit = runBlocking { + val chatSession = genChatSession() + .apply { + this.id = Random.nextInt(0, Int.MAX_VALUE) + } - given( - chatService - .findChatByIdForUsername(chatSession.id!!, TESTS_DEFAULT_USERNAME) - ).willReturn(chatSession) + given( + chatService + .findChatByIdForUsername(chatSession.id!!, TESTS_DEFAULT_USERNAME) + ).willReturn(chatSession) - // Simulate past messages for the chat - (1..3).map { genChatMessage(chatSession) } - .toTypedArray() - .also { chatMessages -> - given( - messageService - .findMessagesInChatSessionForUsername(chatSession.id!!, chatSession.username) - ).willReturn(Flux.just(*chatMessages).asFlow()) - } + // Simulate past messages for the chat + (1..3).map { genChatMessage(chatSession) } + .toTypedArray() + .also { chatMessages -> + given( + messageService + .findMessagesInChatSessionForUsername(chatSession.id!!, chatSession.username) + ).willReturn(Flux.just(*chatMessages).asFlow()) + } - // Ollama llama3.2 generated response for "Hello!" (streaming) - val mockResp = MockResponse() - mockResp.setResponseCode(200) - mockResp.addHeader("Content-Type", "application/x-ndjson") - mockResp.setBody( - "{\"model\":\"llama3.2\",\"created_at\":\"2024-11-05T18:10:43.800822113Z\",\"message\":{\"role\":\"assistant\",\"content\":\"Hello\"},\"done\":false}\n" + - "{\"model\":\"llama3.2\",\"created_at\":\"2024-11-05T18:10:43.851380825Z\",\"message\":{\"role\":\"assistant\",\"content\":\"!\"},\"done\":false}\n" + - "{\"model\":\"llama3.2\",\"created_at\":\"2024-11-05T18:10:43.905787498Z\",\"message\":{\"role\":\"assistant\",\"content\":\" How\"},\"done\":false}\n" + - "{\"model\":\"llama3.2\",\"created_at\":\"2024-11-05T18:10:43.968348153Z\",\"message\":{\"role\":\"assistant\",\"content\":\" can\"},\"done\":false}\n" + - "{\"model\":\"llama3.2\",\"created_at\":\"2024-11-05T18:10:44.024798921Z\",\"message\":{\"role\":\"assistant\",\"content\":\" I\"},\"done\":false}\n" + - "malformed input in the middle" - ) - mockWebServer!!.enqueue(mockResp) + // Ollama llama3.2 generated response for "Hello!" (streaming) + val mockResp = MockResponse() + mockResp.setResponseCode(200) + mockResp.addHeader("Content-Type", "application/x-ndjson") + mockResp.setBody( + "{\"model\":\"llama3.2\",\"created_at\":\"2024-11-05T18:10:43.800822113Z\",\"message\":{\"role\":\"assistant\",\"content\":\"Hello\"},\"done\":false}\n" + + "{\"model\":\"llama3.2\",\"created_at\":\"2024-11-05T18:10:43.851380825Z\",\"message\":{\"role\":\"assistant\",\"content\":\"!\"},\"done\":false}\n" + + "{\"model\":\"llama3.2\",\"created_at\":\"2024-11-05T18:10:43.905787498Z\",\"message\":{\"role\":\"assistant\",\"content\":\" How\"},\"done\":false}\n" + + "{\"model\":\"llama3.2\",\"created_at\":\"2024-11-05T18:10:43.968348153Z\",\"message\":{\"role\":\"assistant\",\"content\":\" can\"},\"done\":false}\n" + + "{\"model\":\"llama3.2\",\"created_at\":\"2024-11-05T18:10:44.024798921Z\",\"message\":{\"role\":\"assistant\",\"content\":\" I\"},\"done\":false}\n" + + "malformed input in the middle" + ) + mockWebServer!!.enqueue(mockResp) - getWebTestClient() - .post() - .uri("/message/") - .bodyValue( - ChatMessageSendDTO( - chatSessionId = chatSession.id!!, - message = "Hello!", - ) + getWebTestClient() + .post() + .uri("/message/") + .bodyValue( + ChatMessageSendDTO( + chatSessionId = chatSession.id!!, + message = "Hello!", ) - .exchange() - .expectStatus() - .is5xxServerError - .expectBody(String::class.java) - .value { - it.startsWith(RestControllerAdvice.messagePrefixForGenericRestException()) - } - } + ) + .exchange() + .expectStatus() + .is5xxServerError + .expectBody(String::class.java) + .value { + it.startsWith(RestControllerAdvice.messagePrefixForGenericRestException()) + } } } + diff --git a/chat-access/src/test/kotlin/interview/chataccess/controller/RestSecurityTests.kt b/chat-access/src/test/kotlin/interview/chataccess/controller/RestSecurityTests.kt index fd7639d..aa16d52 100644 --- a/chat-access/src/test/kotlin/interview/chataccess/controller/RestSecurityTests.kt +++ b/chat-access/src/test/kotlin/interview/chataccess/controller/RestSecurityTests.kt @@ -10,10 +10,7 @@ import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows -import org.junit.jupiter.api.extension.ExtendWith import org.mockito.Mock -import org.mockito.Mockito.`when` -import org.mockito.junit.jupiter.MockitoExtension import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest import org.springframework.context.annotation.Import @@ -27,7 +24,6 @@ import org.springframework.security.web.server.WebFilterChainProxy import org.springframework.test.web.reactive.server.WebTestClient import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RestController -import reactor.core.publisher.Mono @Import(TestApplicationConfig::class, SecurityConfig::class) @WebFluxTest(RestSecurityTests.Http200Controller::class) @@ -41,14 +37,8 @@ class RestSecurityTests : BaseControllerTests() { @RestController class Http200Controller { - val log = logger() - @GetMapping("/") - suspend fun getOK(): Mono { - return Mono.just("OK") - } - @GetMapping("/user") suspend fun getUsername(): String = getPrincipal().also { log.debug("Principal: ${it} ") } } @@ -71,70 +61,61 @@ class RestSecurityTests : BaseControllerTests() { @Test @DisplayName("Authentication required") - fun getModelsList() { - runBlocking { - getWebTestClient() - .get() - .uri("/") - .accept(MediaType.APPLICATION_JSON) - .exchange() - .expectStatus() - .is4xxClientError - } + fun getModelsList(): Unit = runBlocking { + getWebTestClient() + .get() + .uri("/") + .accept(MediaType.APPLICATION_JSON) + .exchange() + .expectStatus() + .is4xxClientError } @Test @DisplayName("getPrincipal works for mock users") @WithMockUser(username = TESTS_DEFAULT_USERNAME) - fun getPrincipalForMockUser() { + fun getPrincipalForMockUser(): Unit = runBlocking { // This one is just to make sure other tests with @WithMockUser do behave. - runBlocking { - getWebTestClient() - .get() - .uri("/user") - .accept(MediaType.APPLICATION_JSON) - .exchange() - .expectStatus() - .is2xxSuccessful - } + getWebTestClient() + .get() + .uri("/user") + .accept(MediaType.APPLICATION_JSON) + .exchange() + .expectStatus() + .is2xxSuccessful } @Test @DisplayName("getPrincipal works for mocked OAuth2") - fun getPrincipalForMockOAuth2() { - runBlocking { - getWebTestClient() - .mutateWith( - SecurityMockServerConfigurers.mockJwt().jwt { jwt -> - jwt.claims { claims -> claims["email"] = TESTS_DEFAULT_USERNAME } - } - ) - .get() - .uri("/user") - .accept(MediaType.APPLICATION_JSON) - .exchange() - .expectStatus() - .is2xxSuccessful - .expectBody(String::class.java) - .isEqualTo(TESTS_DEFAULT_USERNAME) - } + fun getPrincipalForMockOAuth2(): Unit = runBlocking { + getWebTestClient() + .mutateWith( + SecurityMockServerConfigurers.mockJwt().jwt { jwt -> + jwt.claims { claims -> claims["email"] = TESTS_DEFAULT_USERNAME } + } + ) + .get() + .uri("/user") + .accept(MediaType.APPLICATION_JSON) + .exchange() + .expectStatus() + .is2xxSuccessful + .expectBody(String::class.java) + .isEqualTo(TESTS_DEFAULT_USERNAME) } - @Mock private val auth: Authentication? = null @Test @DisplayName("getPrincipal reacts to non-supported authentication context") - fun getPrincipalWithUnsupportedContext() { - runBlocking { - assertThrows { - SecurityContextHolder - .getContext() - .authentication = auth + fun getPrincipalWithUnsupportedContext(): Unit = runBlocking { + assertThrows { + SecurityContextHolder + .getContext() + .authentication = auth - getPrincipal() - } + getPrincipal() } } } \ No newline at end of file diff --git a/chat-access/src/test/kotlin/interview/chataccess/service/ChatServiceTests.kt b/chat-access/src/test/kotlin/interview/chataccess/service/ChatServiceTests.kt index a9f0247..768f9cf 100644 --- a/chat-access/src/test/kotlin/interview/chataccess/service/ChatServiceTests.kt +++ b/chat-access/src/test/kotlin/interview/chataccess/service/ChatServiceTests.kt @@ -45,133 +45,118 @@ class ChatServiceTests : BaseServiceTests() { @Test @DisplayName("ChatServiceTest: create chat session") - fun createChatSession() { - runBlocking { - genChatSession() - .let { chatSession -> - chatService - .save(chatSession) - .apply { - assertNotNull(id) - assertEquals(name, chatSession.name) - assertEquals(model, chatSession.model) - assertEquals(username, chatSession.username) - } - } - } - } - - @Test - @DisplayName("ChatServiceTest: delete chat session") - fun deleteChatSession() { - runBlocking { - chatService - .save(genChatSession()) - .let { chatSession -> - assertNotNull(chatRepository.findById(chatSession.id!!)) - - chatService.delete(chatSession.id!!, chatSession.username) - - assertNull(chatRepository.findById(chatSession.id!!)) - } - } - } - - @Test - @DisplayName("ChatServiceTest: delete non-existent chat session") - fun deleteChatSessionNotExist() { - runBlocking { - val id = Random.nextInt(0, Int.MAX_VALUE) - assertFalse( - chatService.delete(id, TESTS_DEFAULT_USERNAME) - ) - } - } - - @Test - @DisplayName("ChatServiceTest: fetching chat sessions by id and username") - fun findOneByIdAndUsername() { - runBlocking { - chatRepository.saveAll( - (1..3).map { genChatSession() } - ).onEach { chatSession -> + fun createChatSession(): Unit = runBlocking { + genChatSession() + .let { chatSession -> chatService - .findChatByIdForUsername(chatSession.id!!, chatSession.username) - ?.apply { + .save(chatSession) + .apply { assertNotNull(id) - assertNotNull(createdDate) - assertNotNull(modifiedDate) - assertNotNull(version) assertEquals(name, chatSession.name) assertEquals(model, chatSession.model) assertEquals(username, chatSession.username) } - ?: fail("findChatByIdForUsername returned null when shouldn't") - }.lastOrNull() - } + } + } + + @Test + @DisplayName("ChatServiceTest: delete chat session") + fun deleteChatSession(): Unit = runBlocking { + chatService + .save(genChatSession()) + .let { chatSession -> + assertNotNull(chatRepository.findById(chatSession.id!!)) + + chatService.delete(chatSession.id!!, chatSession.username) + + assertNull(chatRepository.findById(chatSession.id!!)) + } + } + + @Test + @DisplayName("ChatServiceTest: delete non-existent chat session") + fun deleteChatSessionNotExist(): Unit = runBlocking { + val id = Random.nextInt(0, Int.MAX_VALUE) + assertFalse( + chatService.delete(id, TESTS_DEFAULT_USERNAME) + ) + } + + @Test + @DisplayName("ChatServiceTest: fetching chat sessions by id and username") + fun findOneByIdAndUsername(): Unit = runBlocking { + chatRepository.saveAll( + (1..3).map { genChatSession() } + ).onEach { chatSession -> + chatService + .findChatByIdForUsername(chatSession.id!!, chatSession.username) + ?.apply { + assertNotNull(id) + assertNotNull(createdDate) + assertNotNull(modifiedDate) + assertNotNull(version) + assertEquals(name, chatSession.name) + assertEquals(model, chatSession.model) + assertEquals(username, chatSession.username) + } + ?: fail("findChatByIdForUsername returned null when shouldn't") + }.lastOrNull() } @Test @DisplayName("ChatServiceTest: trying to fetch another user chat session") - fun findOneByIdAndUsernameButDifferentUser() { - runBlocking { - chatService - .save(genChatSession()) - .let { chatSession -> - assertNull( - chatService - .findChatByIdForUsername(chatSession.id!!, TESTS_DEFAULT_ANOTHER_USERNAME) - ) - } - } + fun findOneByIdAndUsernameButDifferentUser(): Unit = runBlocking { + chatService + .save(genChatSession()) + .let { chatSession -> + assertNull( + chatService + .findChatByIdForUsername(chatSession.id!!, TESTS_DEFAULT_ANOTHER_USERNAME) + ) + } } @Test @DisplayName("ChatServiceTest: trying to fetch non existing chat session") - fun findOneByIdAndUsernameButNonExisting() { - runBlocking { - chatService - .save(genChatSession()) - .let { chatSession -> - assertNull( - chatService - .findChatByIdForUsername(chatSession.id!! + 1, chatSession.username) - ) - } - } + fun findOneByIdAndUsernameButNonExisting(): Unit = runBlocking { + chatService + .save(genChatSession()) + .let { chatSession -> + assertNull( + chatService + .findChatByIdForUsername(chatSession.id!! + 1, chatSession.username) + ) + } } @Test @DisplayName("ChatServiceTest: checking the order of queried chat sessions for username") - fun findAllByUsernameAndCheckOrder() { - runBlocking { - // Order should be reversed from the order of inserting into database. Last first. - (1..5).map { genChatSession() } - .also { csList -> - chatRepository.saveAll(csList) - .last() - } - .also { csList -> - chatService - .findChatSessionsForUsername(TESTS_DEFAULT_USERNAME) - .test { - csList - .reversed() - .forEach { cs -> - with(this.awaitItem()) { - assertNotNull(id) - assertNotNull(createdDate) - assertNotNull(modifiedDate) - assertNotNull(version) - assertEquals(cs.name, name) - assertEquals(cs.model, model) - assertEquals(cs.username, username) - } + fun findAllByUsernameAndCheckOrder(): Unit = runBlocking { + // Order should be reversed from the order of inserting into database. Last first. + (1..5).map { genChatSession() } + .also { csList -> + chatRepository.saveAll(csList) + .last() + } + .also { csList -> + chatService + .findChatSessionsForUsername(TESTS_DEFAULT_USERNAME) + .test { + csList + .reversed() + .forEach { cs -> + with(this.awaitItem()) { + assertNotNull(id) + assertNotNull(createdDate) + assertNotNull(modifiedDate) + assertNotNull(version) + assertEquals(cs.name, name) + assertEquals(cs.model, model) + assertEquals(cs.username, username) } - awaitComplete() - } - } - } + } + awaitComplete() + } + } } - } \ No newline at end of file diff --git a/chat-access/src/test/kotlin/interview/chataccess/service/MessageServiceTests.kt b/chat-access/src/test/kotlin/interview/chataccess/service/MessageServiceTests.kt index 2ce0c41..6089b31 100644 --- a/chat-access/src/test/kotlin/interview/chataccess/service/MessageServiceTests.kt +++ b/chat-access/src/test/kotlin/interview/chataccess/service/MessageServiceTests.kt @@ -42,91 +42,85 @@ class MessageServiceTests : BaseServiceTests() { @Test @DisplayName("MessageServiceTest: create message") - fun createMessage() { - runBlocking { - genChatSession() - .let { chatSession -> - genChatMessage( - chatService - .save(chatSession) - ).let { msg -> - messageService.save(msg) - .apply { - assertNotNull(id) - assertEquals(message, msg.message) - assertEquals(role, msg.role) - assertEquals(done, msg.done) - assertEquals(chatSessionId, msg.chatSessionId) - } - } + fun createMessage(): Unit = runBlocking { + genChatSession() + .let { chatSession -> + genChatMessage( + chatService + .save(chatSession) + ).let { msg -> + messageService.save(msg) + .apply { + assertNotNull(id) + assertEquals(message, msg.message) + assertEquals(role, msg.role) + assertEquals(done, msg.done) + assertEquals(chatSessionId, msg.chatSessionId) + } } - } + } } @Test @DisplayName("MessageServiceTest: find all previous messages and verify order") - fun findAllByChatSessionIdAndUsername() { - runBlocking { - val chatSession = genChatSession() - .let { chatSession -> - chatService - .save(chatSession) - } + fun findAllByChatSessionIdAndUsername(): Unit = runBlocking { + val chatSession = genChatSession() + .let { chatSession -> + chatService + .save(chatSession) + } - val messageList = (1..5).map { - genChatMessage( - chatSession - ) - }.toTypedArray() + val messageList = (1..5).map { + genChatMessage( + chatSession + ) + }.toTypedArray() - messageService - .saveAll(*messageList) - .last() + messageService + .saveAll(*messageList) + .last() - messageService - .findMessagesInChatSessionForUsername(chatSession.id!!, TESTS_DEFAULT_USERNAME) - .test { - messageList - .forEach { - val item = awaitItem() - assertNotNull(item.id) - assertNotNull(item.createdDate) - assertNotNull(item.modifiedDate) - assertEquals(item.message, it.message) - assertEquals(item.role, it.role) - assertEquals(item.done, it.done) - assertEquals(item.chatSessionId, it.chatSessionId) - } - awaitComplete() - } - } + messageService + .findMessagesInChatSessionForUsername(chatSession.id!!, TESTS_DEFAULT_USERNAME) + .test { + messageList + .forEach { + val item = awaitItem() + assertNotNull(item.id) + assertNotNull(item.createdDate) + assertNotNull(item.modifiedDate) + assertEquals(item.message, it.message) + assertEquals(item.role, it.role) + assertEquals(item.done, it.done) + assertEquals(item.chatSessionId, it.chatSessionId) + } + awaitComplete() + } } @Test @DisplayName("MessageServiceTest: whether we can retrieve messages from another user's chat session") - fun findAllForAnotherUser() { - runBlocking { - val chatSession = genChatSession() - .let { chatSession -> - chatService - .save(chatSession) - } + fun findAllForAnotherUser(): Unit = runBlocking { + val chatSession = genChatSession() + .let { chatSession -> + chatService + .save(chatSession) + } - val messageList = (1..5).map { - genChatMessage( - chatSession - ) - }.toTypedArray() + val messageList = (1..5).map { + genChatMessage( + chatSession + ) + }.toTypedArray() - messageService - .saveAll(*messageList) - .last() + messageService + .saveAll(*messageList) + .last() - messageService - .findMessagesInChatSessionForUsername(chatSession.id!!, TESTS_DEFAULT_ANOTHER_USERNAME) - .test { - awaitComplete() - } - } + messageService + .findMessagesInChatSessionForUsername(chatSession.id!!, TESTS_DEFAULT_ANOTHER_USERNAME) + .test { + awaitComplete() + } } } \ No newline at end of file