1
0
Fork 0

Code cleanups, mostly runBlocking inlining in tests

This commit is contained in:
Adam Kruszewski 2024-11-19 19:34:04 +01:00
parent 8681fa3465
commit 4a78a894a4
10 changed files with 745 additions and 822 deletions

View file

@ -10,7 +10,7 @@ import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories
@ConfigurationPropertiesScan @ConfigurationPropertiesScan
@EntityScan("interview.chataccess.dbmodel") @EntityScan("interview.chataccess.dbmodel")
@EnableR2dbcRepositories("interview.chataccess.dbmodel") @EnableR2dbcRepositories("interview.chataccess.dbmodel")
open class ChatAccessApplication class ChatAccessApplication
fun main(args: Array<String>) { fun main(args: Array<String>) {
runApplication<ChatAccessApplication>(*args) runApplication<ChatAccessApplication>(*args)

View file

@ -1,6 +1,5 @@
package interview.chataccess.controller package interview.chataccess.controller
import interview.chataccess.controller.UtilsRestController.getPrincipal import interview.chataccess.controller.UtilsRestController.getPrincipal
import interview.chataccess.controller.exceptions.ChatSessionNotFoundException import interview.chataccess.controller.exceptions.ChatSessionNotFoundException
import interview.chataccess.controller.exceptions.GenericRestException import interview.chataccess.controller.exceptions.GenericRestException
@ -10,6 +9,7 @@ import interview.chataccess.dto.*
import interview.chataccess.service.ChatService import interview.chataccess.service.ChatService
import interview.chataccess.service.MessageService import interview.chataccess.service.MessageService
import interview.chataccess.utils.logger import interview.chataccess.utils.logger
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
@ -59,7 +59,8 @@ class MessageController(
suspend fun sendMessage(@RequestBody input: ChatMessageSendDTO): Flow<ChatMessagePartialReplyDTO> = coroutineScope { suspend fun sendMessage(@RequestBody input: ChatMessageSendDTO): Flow<ChatMessagePartialReplyDTO> = coroutineScope {
val principal = getPrincipal() 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 chatService
.findChatByIdForUsername(input.chatSessionId, principal) .findChatByIdForUsername(input.chatSessionId, principal)
?: throw ChatSessionNotFoundException( ?: throw ChatSessionNotFoundException(
@ -74,7 +75,7 @@ class MessageController(
// both of the queries asynchronously for all the other cases. // 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 // We take advantage of the coroutines property that error on one of them will cancel
// the parent context (when not using supervisor). // the parent context (when not using supervisor).
val pastMessages = async { val pastMessages = async(start = CoroutineStart.LAZY) {
messageService messageService
.findMessagesInChatSessionForUsername(input.chatSessionId, principal) .findMessagesInChatSessionForUsername(input.chatSessionId, principal)
.map { message -> .map { message ->

View file

@ -15,12 +15,12 @@ object UtilsRestController {
.awaitSingle() .awaitSingle()
.authentication.principal .authentication.principal
.let { authPrincipal -> .let { authPrincipal ->
when { when (authPrincipal) {
authPrincipal is User -> { is User -> {
authPrincipal.username authPrincipal.username
} }
authPrincipal is Jwt -> { is Jwt -> {
authPrincipal authPrincipal
.claims["email"] .claims["email"]
.toString() .toString()

View file

@ -13,23 +13,18 @@ open class BaseTests {
private var internalCounter: Int = 0 private var internalCounter: Int = 0
fun genChatSession(): ChatSession { fun genChatSession() =
internalCounter++ ChatSession.from(
return ChatSession.from( name = (++internalCounter).toString(),
name = internalCounter.toString(),
model = TESTS_DEFAULT_MODEL, model = TESTS_DEFAULT_MODEL,
username = TESTS_DEFAULT_USERNAME username = TESTS_DEFAULT_USERNAME
) )
}
fun genChatMessage(chatSession: ChatSession): ChatMessage { fun genChatMessage(chatSession: ChatSession) =
internalCounter++ ChatMessage.from(
return ChatMessage.from(
chatSession, chatSession,
ChatRoles.USER, ChatRoles.USER,
"Test message ${internalCounter}", "Test message ${(++internalCounter)}",
true true
) )
}
} }

View file

@ -34,6 +34,7 @@ class ApplicationStartupEventTests : BaseOllamaClientTest() {
.setBody("{\"status\":\"success\"}") .setBody("{\"status\":\"success\"}")
) )
} }
ApplicationStartupEvent(appConfig, mockWebClient!!) ApplicationStartupEvent(appConfig, mockWebClient!!)
.loadTestData() .loadTestData()
} }
@ -46,7 +47,7 @@ class ApplicationStartupEventTests : BaseOllamaClientTest() {
// The StartupEven will retry given number of times, // The StartupEven will retry given number of times,
// we need to return error for all the retires. // we need to return error for all the retires.
assertThrows<GenericRestException> { assertThrows<GenericRestException> {
(0..ApplicationStartupEvent.MAX_RETRIES ) (0..ApplicationStartupEvent.MAX_RETRIES)
.forEach { .forEach {
log.debug("Enqueued $it...") log.debug("Enqueued $it...")
mockWebServer!!.enqueue( mockWebServer!!.enqueue(
@ -57,7 +58,6 @@ class ApplicationStartupEventTests : BaseOllamaClientTest() {
ApplicationStartupEvent(appConfig, mockWebClient!!) ApplicationStartupEvent(appConfig, mockWebClient!!)
.loadTestData() .loadTestData()
} }
} }

View file

@ -50,293 +50,273 @@ class ChatControllerTests : BaseControllerTests() {
@Test @Test
@DisplayName("ChatControllerTest: fetch models list") @DisplayName("ChatControllerTest: fetch models list")
fun getModelsList() { fun getModelsList(): Unit = runBlocking {
runBlocking { log.debug("Models in the test scenario: {}", appConfig.availableModels)
log.debug("Models in the test scenario: {}", appConfig.availableModels)
getWebTestClient() getWebTestClient()
.get() .get()
.uri("/chat/models") .uri("/chat/models")
.accept(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)
.exchange() .exchange()
.expectStatus() .expectStatus()
.is2xxSuccessful .is2xxSuccessful
.expectBodyList(ChatSessionModelDTO::class.java) .expectBodyList(ChatSessionModelDTO::class.java)
.hasSize(appConfig.availableModels.size) .hasSize(appConfig.availableModels.size)
.contains( .contains(
*(appConfig.availableModels *(appConfig.availableModels
.map { m -> .map { m ->
ChatSessionModelDTO(model = m) ChatSessionModelDTO(model = m)
}.toTypedArray()) }.toTypedArray())
) )
}
} }
@Test @Test
@DisplayName("ChatControllerTest: fetch chat sessions list") @DisplayName("ChatControllerTest: fetch chat sessions list")
fun getSessions() { fun getSessions(): Unit = runBlocking {
runBlocking { (1..5).map { genChatSession() }
(1..5).map { genChatSession() } .toTypedArray()
.toTypedArray() .also { chatSessions ->
.also { chatSessions -> given(
given( chatService
chatService .findChatSessionsForUsername(TESTS_DEFAULT_USERNAME)
.findChatSessionsForUsername(TESTS_DEFAULT_USERNAME) ).willReturn(Flux.just(*chatSessions).asFlow())
).willReturn(Flux.just(*chatSessions).asFlow()) }
} .map { chatSession ->
.map { chatSession -> ChatSessionDTO(
ChatSessionDTO( id = chatSession.id,
id = chatSession.id, name = chatSession.name,
name = chatSession.name, model = chatSession.model,
model = chatSession.model, createdDate = chatSession.createdDate
createdDate = chatSession.createdDate )
) }
} .toTypedArray()
.toTypedArray() .also { chatSessions ->
.also { chatSessions -> getWebTestClient()
getWebTestClient() .get()
.get() .uri("/chat/session")
.uri("/chat/session") .accept(MediaType.TEXT_EVENT_STREAM)
.accept(MediaType.TEXT_EVENT_STREAM) .exchange()
.exchange() .expectStatus()
.expectStatus() .is2xxSuccessful
.is2xxSuccessful .expectBodyList(ChatSessionDTO::class.java)
.expectBodyList(ChatSessionDTO::class.java) .hasSize(chatSessions.size)
.hasSize(chatSessions.size) .contains(*chatSessions)
.contains(*chatSessions) }
}
}
} }
@Test @Test
@DisplayName("ChatControllerTest: fetch chat session details") @DisplayName("ChatControllerTest: fetch chat session details")
fun getSessionDetails() { fun getSessionDetails(): Unit = runBlocking {
runBlocking { genChatSession()
genChatSession() .apply {
.apply { this.id = Random.nextInt(0, Int.MAX_VALUE)
this.id = Random.nextInt(0, Int.MAX_VALUE) }
} .also { chatSession ->
.also { chatSession -> given(
given( chatService
chatService .findChatByIdForUsername(chatSession.id!!, TESTS_DEFAULT_USERNAME)
.findChatByIdForUsername(chatSession.id!!, TESTS_DEFAULT_USERNAME) ).willReturn(chatSession)
).willReturn(chatSession) }
} .also { chatSession ->
.also { chatSession -> getWebTestClient()
getWebTestClient() .get()
.get() .uri("/chat/session/${chatSession.id}")
.uri("/chat/session/${chatSession.id}") .exchange()
.exchange() .expectStatus()
.expectStatus() .is2xxSuccessful
.is2xxSuccessful .expectBody(ChatSessionDTO::class.java)
.expectBody(ChatSessionDTO::class.java) .value { response ->
.value { response -> response.id == chatSession.id
response.id == chatSession.id && response.name == chatSession.name
&& response.name == chatSession.name && response.model == chatSession.model
&& response.model == chatSession.model && response.createdDate == chatSession.createdDate
&& response.createdDate == chatSession.createdDate }
} }
}
}
} }
@Test @Test
@DisplayName("ChatControllerTest: fetch non-existent chat session details") @DisplayName("ChatControllerTest: fetch non-existent chat session details")
fun getSessionDetailsNonExist() { fun getSessionDetailsNonExist(): Unit = runBlocking {
runBlocking { genChatSession()
genChatSession() .apply {
.apply { // We do not store it, i.e., not mock storing, just simulating,
// We do not store it, i.e., not mock storing, just simulating, // like it would be deleted in the meantime.
// like it would be deleted in the meantime. this.id = Random.nextInt(0, Int.MAX_VALUE)
this.id = Random.nextInt(0, Int.MAX_VALUE) }
} .also { chatSession ->
.also { chatSession -> given(
given( chatService
chatService .findChatByIdForUsername(chatSession.id!!, TESTS_DEFAULT_USERNAME)
.findChatByIdForUsername(chatSession.id!!, TESTS_DEFAULT_USERNAME) ).willReturn(null)
).willReturn(null) }
} .also { chatSession ->
.also { chatSession -> getWebTestClient()
getWebTestClient() .get()
.get() .uri("/chat/session/${chatSession.id}")
.uri("/chat/session/${chatSession.id}") .exchange()
.exchange() .expectStatus()
.expectStatus() .is4xxClientError
.is4xxClientError .expectBody(String::class.java)
.expectBody(String::class.java) .isEqualTo(
.isEqualTo( RestControllerAdvice.messageForSessionNotFound(chatSession.id!!)
RestControllerAdvice.messageForSessionNotFound(chatSession.id!!) )
) }
}
}
} }
@Test @Test
@DisplayName("ChatControllerTest: delete non-existent chat session") @DisplayName("ChatControllerTest: delete non-existent chat session")
fun deleteChatSessionNotExist() { fun deleteChatSessionNotExist(): Unit = runBlocking {
runBlocking { genChatSession()
genChatSession() .apply {
.apply { this.id = Random.nextInt(0, Int.MAX_VALUE)
this.id = Random.nextInt(0, Int.MAX_VALUE) }
} .also { chatSession ->
.also { chatSession -> given(
given( chatService
chatService .delete(chatSession.id!!, TESTS_DEFAULT_USERNAME)
.delete(chatSession.id!!, TESTS_DEFAULT_USERNAME) ).willReturn(false)
).willReturn(false) }
} .also { chatSession ->
.also { chatSession -> getWebTestClient()
getWebTestClient() .delete()
.delete() .uri("/chat/session/${chatSession.id}")
.uri("/chat/session/${chatSession.id}") .exchange()
.exchange() .expectStatus()
.expectStatus() .is4xxClientError
.is4xxClientError .expectBody(String::class.java)
.expectBody(String::class.java) .isEqualTo(
.isEqualTo( RestControllerAdvice.messageForSessionNotFound(chatSession.id!!)
RestControllerAdvice.messageForSessionNotFound(chatSession.id!!) )
) }
}
}
} }
@Test @Test
@DisplayName("ChatControllerTest: delete chat session") @DisplayName("ChatControllerTest: delete chat session")
fun deleteChatSession() { fun deleteChatSession(): Unit = runBlocking {
runBlocking { genChatSession()
genChatSession() .apply {
.apply { this.id = Random.nextInt(0, Int.MAX_VALUE)
this.id = Random.nextInt(0, Int.MAX_VALUE) }
} .also { chatSession ->
.also { chatSession -> given(
given( chatService
chatService .delete(chatSession.id!!, TESTS_DEFAULT_USERNAME)
.delete(chatSession.id!!, TESTS_DEFAULT_USERNAME) ).willReturn(true)
).willReturn(true) }
} .also { chatSession ->
.also { chatSession -> getWebTestClient()
getWebTestClient() .delete()
.delete() .uri("/chat/session/${chatSession.id}")
.uri("/chat/session/${chatSession.id}") .exchange()
.exchange() .expectStatus()
.expectStatus() .is2xxSuccessful
.is2xxSuccessful .expectBody(String::class.java)
.expectBody(String::class.java) .isEqualTo("Deleted")
.isEqualTo("Deleted") }
}
}
} }
@Test @Test
@DisplayName("ChatControllerTest: create new chat session with wrong model") @DisplayName("ChatControllerTest: create new chat session with wrong model")
fun createChatSessionWrongModel() { fun createChatSessionWrongModel(): Unit = runBlocking {
runBlocking { getWebTestClient()
getWebTestClient() .put()
.put() .uri("/chat/session")
.uri("/chat/session") .bodyValue(
.bodyValue( ChatSessionCreateNewDTO(
ChatSessionCreateNewDTO( model = TESTS_DEFAULT_WRONG_MODEL,
model = TESTS_DEFAULT_WRONG_MODEL, name = "non-empty-name"
name = "non-empty-name"
)
) )
.exchange() )
.expectStatus() .exchange()
.is4xxClientError .expectStatus()
.expectBody(String::class.java) .is4xxClientError
.isEqualTo( .expectBody(String::class.java)
RestControllerAdvice.messageForUnsupportedMode(TESTS_DEFAULT_WRONG_MODEL) .isEqualTo(
) RestControllerAdvice.messageForUnsupportedMode(TESTS_DEFAULT_WRONG_MODEL)
} )
} }
@Test @Test
@DisplayName("ChatControllerTest: create new chat session with empty name") @DisplayName("ChatControllerTest: create new chat session with empty name")
fun createChatSessionEmptyName() { fun createChatSessionEmptyName(): Unit = runBlocking {
runBlocking { getWebTestClient()
getWebTestClient() .put()
.put() .uri("/chat/session")
.uri("/chat/session") .bodyValue(
.bodyValue( ChatSessionCreateNewDTO(
ChatSessionCreateNewDTO( model = TESTS_DEFAULT_MODEL,
model = TESTS_DEFAULT_MODEL, name = "" // Empty by design for this test
name = "" // Empty by design for this test
)
) )
.exchange() )
.expectStatus() .exchange()
.is4xxClientError .expectStatus()
.expectBody(String::class.java) .is4xxClientError
.isEqualTo( .expectBody(String::class.java)
RestControllerAdvice.messageForBlankSessionName() .isEqualTo(
) RestControllerAdvice.messageForBlankSessionName()
} )
} }
@Test @Test
@DisplayName("ChatControllerTest: create new chat session with wrong model") @DisplayName("ChatControllerTest: create new chat session with wrong model")
fun createChatSession() { fun createChatSession(): Unit = runBlocking {
runBlocking { // This one looked hideous when written functionally, so semi-imperative it goes.
// This one looked hideous when written functionally, so semi-imperative it goes.
val chatSession = genChatSession() val chatSession = genChatSession()
val savedChatSession = chatSession val savedChatSession = chatSession
.copy() .copy()
.apply { .apply {
this.id = Random.nextInt(0, Int.MAX_VALUE) this.id = Random.nextInt(0, Int.MAX_VALUE)
this.createdDate = Instant.now() this.createdDate = Instant.now()
this.modifiedDate = Instant.now() this.modifiedDate = Instant.now()
} }
val chatMessage = ChatMessage.from( val chatMessage = ChatMessage.from(
session = savedChatSession, session = savedChatSession,
role = ChatRoles.BOT, role = ChatRoles.BOT,
message = appConfig.defaultWelcomeMessage, message = appConfig.defaultWelcomeMessage,
done = true 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
)
) )
.exchange()
val savedChatMessage = chatMessage .expectStatus()
.copy() .is2xxSuccessful
.apply { .expectBodyList(ChatSessionDTO::class.java)
this.id = Random.nextInt(0, Int.MAX_VALUE) .contains(
this.createdDate = Instant.now() ChatSessionDTO(
this.modifiedDate = Instant.now() id = savedChatSession.id,
} name = savedChatSession.name,
model = savedChatSession.model,
given( createdDate = savedChatSession.createdDate,
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,
)
)
}
} }
} }

View file

@ -47,375 +47,362 @@ class MessageControllerTests : BaseOllamaClientTest() {
@Test @Test
@DisplayName("MessageControllerTest: fetch previous non-existent messages") @DisplayName("MessageControllerTest: fetch previous non-existent messages")
fun getPreviousMessagesNotExist() { fun getPreviousMessagesNotExist(): Unit = runBlocking {
runBlocking { val chatSessionId = Random.nextInt(0, Int.MAX_VALUE)
val chatSessionId = Random.nextInt(0, Int.MAX_VALUE)
given( given(
messageService messageService
.findMessagesInChatSessionForUsername(chatSessionId, TESTS_DEFAULT_USERNAME) .findMessagesInChatSessionForUsername(chatSessionId, TESTS_DEFAULT_USERNAME)
).willReturn(Mono.empty<ChatMessage>().asFlow()) ).willReturn(Mono.empty<ChatMessage>().asFlow())
getWebTestClient() getWebTestClient()
.get() .get()
.uri("/message/previous/${chatSessionId}") .uri("/message/previous/${chatSessionId}")
.exchange() .exchange()
.expectStatus() .expectStatus()
.is4xxClientError .is4xxClientError
.expectBody(String::class.java) .expectBody(String::class.java)
.isEqualTo( .isEqualTo(
RestControllerAdvice.messageForSessionNotFound(chatSessionId) RestControllerAdvice.messageForSessionNotFound(chatSessionId)
) )
}
} }
@Test @Test
@DisplayName("MessageControllerTest: fetch previous non-existent messages") @DisplayName("MessageControllerTest: fetch previous non-existent messages")
fun getPrevious() { fun getPrevious(): Unit = runBlocking {
runBlocking { val chatSession = genChatSession()
val chatSession = genChatSession() .apply {
.apply { this.id = Random.nextInt(0, Int.MAX_VALUE)
this.id = Random.nextInt(0, Int.MAX_VALUE) }
}
val chatMessages = (1..5).map { genChatMessage(chatSession) } val chatMessages = (1..5).map { genChatMessage(chatSession) }
.toTypedArray() .toTypedArray()
.also { chatMessages -> .also { chatMessages ->
given( given(
messageService messageService
.findMessagesInChatSessionForUsername(chatSession.id!!, chatSession.username) .findMessagesInChatSessionForUsername(chatSession.id!!, chatSession.username)
).willReturn(Flux.just(*chatMessages).asFlow()) ).willReturn(Flux.just(*chatMessages).asFlow())
} }
val expectedReply = chatMessages val expectedReply = chatMessages
.map { message -> .map { message ->
ChatMessagePastReplyDTO( ChatMessagePastReplyDTO(
content = message.message, content = message.message,
role = message.role role = message.role
) )
} }
.reversed() .reversed()
.toTypedArray() .toTypedArray()
getWebTestClient() getWebTestClient()
.get() .get()
.uri("/message/previous/${chatSession.id}") .uri("/message/previous/${chatSession.id}")
.exchange() .exchange()
.expectStatus() .expectStatus()
.is2xxSuccessful .is2xxSuccessful
.expectBodyList(ChatMessagePastReplyDTO::class.java) .expectBodyList(ChatMessagePastReplyDTO::class.java)
.hasSize(chatMessages.size) .hasSize(chatMessages.size)
.contains(*expectedReply) .contains(*expectedReply)
}
} }
@Test @Test
@DisplayName("MessageControllerTest: send message to non-existent chat session") @DisplayName("MessageControllerTest: send message to non-existent chat session")
fun sendMessageChatNotExist() { fun sendMessageChatNotExist(): Unit = runBlocking {
runBlocking { val chatSessionId = Random.nextInt(0, Int.MAX_VALUE)
val chatSessionId = Random.nextInt(0, Int.MAX_VALUE)
given( given(
chatService chatService
.findChatByIdForUsername(chatSessionId, TESTS_DEFAULT_USERNAME) .findChatByIdForUsername(chatSessionId, TESTS_DEFAULT_USERNAME)
).willReturn(null) ).willReturn(null)
getWebTestClient() getWebTestClient()
.post() .post()
.uri("/message/") .uri("/message/")
.bodyValue( .bodyValue(
ChatMessageSendDTO( ChatMessageSendDTO(
chatSessionId = chatSessionId, chatSessionId = chatSessionId,
message = "non empty message", message = "non empty message",
)
) )
.exchange() )
.expectStatus() .exchange()
.is4xxClientError .expectStatus()
.expectBody(String::class.java) .is4xxClientError
.isEqualTo( .expectBody(String::class.java)
RestControllerAdvice.messageForSessionNotFound(chatSessionId) .isEqualTo(
) RestControllerAdvice.messageForSessionNotFound(chatSessionId)
} )
} }
@Test @Test
@DisplayName("MessageControllerTest: send chat message (non-streaming reply)") @DisplayName("MessageControllerTest: send chat message (non-streaming reply)")
fun sendChatMessage() { fun sendChatMessage(): Unit = runBlocking {
runBlocking { val chatSession = genChatSession()
val chatSession = genChatSession() .apply {
.apply { this.id = Random.nextInt(0, Int.MAX_VALUE)
this.id = Random.nextInt(0, Int.MAX_VALUE) }
}
given( given(
chatService chatService
.findChatByIdForUsername(chatSession.id!!, TESTS_DEFAULT_USERNAME) .findChatByIdForUsername(chatSession.id!!, TESTS_DEFAULT_USERNAME)
).willReturn(chatSession) ).willReturn(chatSession)
// Simulate past messages for the chat // Simulate past messages for the chat
(1..5).map { genChatMessage(chatSession) } (1..5).map { genChatMessage(chatSession) }
.toTypedArray() .toTypedArray()
.also { chatMessages -> .also { chatMessages ->
given( given(
messageService messageService
.findMessagesInChatSessionForUsername(chatSession.id!!, chatSession.username) .findMessagesInChatSessionForUsername(chatSession.id!!, chatSession.username)
).willReturn(Flux.just(*chatMessages).asFlow()) ).willReturn(Flux.just(*chatMessages).asFlow())
} }
val expectedSavedMessages = arrayOf( val expectedSavedMessages = arrayOf(
ChatMessage.from( ChatMessage.from(
chatSession, chatSession,
ChatRoles.USER, ChatRoles.USER,
"Hello!", "Hello!",
done = true done = true
), ),
ChatMessage.from( ChatMessage.from(
chatSession, chatSession,
ChatRoles.BOT, ChatRoles.BOT,
"Hello! How can I assist you today?", "Hello! How can I assist you today?",
done = true 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( .exchange()
messageService .expectStatus()
.saveAll(*expectedSavedMessages) .is2xxSuccessful
).willReturn(Flux.just(*expectedSavedMessages).asFlow()) .expectBodyList(ChatMessagePartialReplyDTO::class.java)
.hasSize(1)
// Ollama llama3.2 generated response for "Hello!" .contains(*expectedReplies)
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)
}
} }
@Test @Test
@DisplayName("MessageControllerTest: send chat message (streaming reply)") @DisplayName("MessageControllerTest: send chat message (streaming reply)")
fun sendChatMessageStreaming() { fun sendChatMessageStreaming(): Unit = runBlocking {
runBlocking { val chatSession = genChatSession()
val chatSession = genChatSession() .apply {
.apply { this.id = Random.nextInt(0, Int.MAX_VALUE)
this.id = Random.nextInt(0, Int.MAX_VALUE) }
}
given( given(
chatService chatService
.findChatByIdForUsername(chatSession.id!!, TESTS_DEFAULT_USERNAME) .findChatByIdForUsername(chatSession.id!!, TESTS_DEFAULT_USERNAME)
).willReturn(chatSession) ).willReturn(chatSession)
// Simulate past messages for the chat // Simulate past messages for the chat
(1..5).map { genChatMessage(chatSession) } (1..5).map { genChatMessage(chatSession) }
.toTypedArray() .toTypedArray()
.also { chatMessages -> .also { chatMessages ->
given( given(
messageService messageService
.findMessagesInChatSessionForUsername(chatSession.id!!, chatSession.username) .findMessagesInChatSessionForUsername(chatSession.id!!, chatSession.username)
).willReturn(Flux.just(*chatMessages).asFlow()) ).willReturn(Flux.just(*chatMessages).asFlow())
} }
val expectedSavedMessages = arrayOf( val expectedSavedMessages = arrayOf(
ChatMessage.from( ChatMessage.from(
chatSession, chatSession,
ChatRoles.USER, ChatRoles.USER,
"Hello!", "Hello!",
done = true done = true
), ),
ChatMessage.from( ChatMessage.from(
chatSession, chatSession,
ChatRoles.BOT, ChatRoles.BOT,
"Hello! How can I assist you today?", "Hello! How can I assist you today?",
done = true 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( .exchange()
messageService .expectStatus()
.saveAll(*expectedSavedMessages) .is2xxSuccessful
).willReturn(Flux.just(*expectedSavedMessages).asFlow()) .expectBodyList(ChatMessagePartialReplyDTO::class.java)
.hasSize(expectedReplies.size)
// Ollama llama3.2 generated response for "Hello!" (streaming) .contains(*expectedReplies)
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)
}
} }
@Test @Test
@DisplayName("MessageControllerTest: Ollama API returns error") @DisplayName("MessageControllerTest: Ollama API returns error")
fun sendChatMessageButGot4xx() { fun sendChatMessageButGot4xx(): Unit = runBlocking {
runBlocking { val chatSession = genChatSession()
val chatSession = genChatSession() .apply {
.apply { this.id = Random.nextInt(0, Int.MAX_VALUE)
this.id = Random.nextInt(0, Int.MAX_VALUE) }
}
given( given(
chatService chatService
.findChatByIdForUsername(chatSession.id!!, TESTS_DEFAULT_USERNAME) .findChatByIdForUsername(chatSession.id!!, TESTS_DEFAULT_USERNAME)
).willReturn(chatSession) ).willReturn(chatSession)
// Simulate past messages for the chat // Simulate past messages for the chat
(1..5).map { genChatMessage(chatSession) } (1..5).map { genChatMessage(chatSession) }
.toTypedArray() .toTypedArray()
.also { chatMessages -> .also { chatMessages ->
given( given(
messageService messageService
.findMessagesInChatSessionForUsername(chatSession.id!!, chatSession.username) .findMessagesInChatSessionForUsername(chatSession.id!!, chatSession.username)
).willReturn(Flux.just(*chatMessages).asFlow()) ).willReturn(Flux.just(*chatMessages).asFlow())
} }
val mockResp = MockResponse() val mockResp = MockResponse()
mockResp.setResponseCode(404) mockResp.setResponseCode(404)
mockWebServer!!.enqueue(mockResp) mockWebServer!!.enqueue(mockResp)
getWebTestClient() getWebTestClient()
.post() .post()
.uri("/message/") .uri("/message/")
.bodyValue( .bodyValue(
ChatMessageSendDTO( ChatMessageSendDTO(
chatSessionId = chatSession.id!!, chatSessionId = chatSession.id!!,
message = "Hello!", message = "Hello!",
)
) )
.exchange() )
.expectStatus() .exchange()
.is5xxServerError .expectStatus()
.expectBody(String::class.java) .is5xxServerError
.value { .expectBody(String::class.java)
it.startsWith(RestControllerAdvice.messagePrefixForGenericRestException()) .value {
} it.startsWith(RestControllerAdvice.messagePrefixForGenericRestException())
} }
} }
@Test @Test
@DisplayName("MessageControllerTest: send chat message (streaming reply with error in middle)") @DisplayName("MessageControllerTest: send chat message (streaming reply with error in middle)")
fun sendChatMessageStreamingError() { fun sendChatMessageStreamingError(): Unit = runBlocking {
runBlocking { val chatSession = genChatSession()
val chatSession = genChatSession() .apply {
.apply { this.id = Random.nextInt(0, Int.MAX_VALUE)
this.id = Random.nextInt(0, Int.MAX_VALUE) }
}
given( given(
chatService chatService
.findChatByIdForUsername(chatSession.id!!, TESTS_DEFAULT_USERNAME) .findChatByIdForUsername(chatSession.id!!, TESTS_DEFAULT_USERNAME)
).willReturn(chatSession) ).willReturn(chatSession)
// Simulate past messages for the chat // Simulate past messages for the chat
(1..3).map { genChatMessage(chatSession) } (1..3).map { genChatMessage(chatSession) }
.toTypedArray() .toTypedArray()
.also { chatMessages -> .also { chatMessages ->
given( given(
messageService messageService
.findMessagesInChatSessionForUsername(chatSession.id!!, chatSession.username) .findMessagesInChatSessionForUsername(chatSession.id!!, chatSession.username)
).willReturn(Flux.just(*chatMessages).asFlow()) ).willReturn(Flux.just(*chatMessages).asFlow())
} }
// Ollama llama3.2 generated response for "Hello!" (streaming) // Ollama llama3.2 generated response for "Hello!" (streaming)
val mockResp = MockResponse() val mockResp = MockResponse()
mockResp.setResponseCode(200) mockResp.setResponseCode(200)
mockResp.addHeader("Content-Type", "application/x-ndjson") mockResp.addHeader("Content-Type", "application/x-ndjson")
mockResp.setBody( 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.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.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.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: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.024798921Z\",\"message\":{\"role\":\"assistant\",\"content\":\" I\"},\"done\":false}\n" +
"malformed input in the middle" "malformed input in the middle"
) )
mockWebServer!!.enqueue(mockResp) mockWebServer!!.enqueue(mockResp)
getWebTestClient() getWebTestClient()
.post() .post()
.uri("/message/") .uri("/message/")
.bodyValue( .bodyValue(
ChatMessageSendDTO( ChatMessageSendDTO(
chatSessionId = chatSession.id!!, chatSessionId = chatSession.id!!,
message = "Hello!", message = "Hello!",
)
) )
.exchange() )
.expectStatus() .exchange()
.is5xxServerError .expectStatus()
.expectBody(String::class.java) .is5xxServerError
.value { .expectBody(String::class.java)
it.startsWith(RestControllerAdvice.messagePrefixForGenericRestException()) .value {
} it.startsWith(RestControllerAdvice.messagePrefixForGenericRestException())
} }
} }
} }

View file

@ -10,10 +10,7 @@ import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.assertThrows
import org.junit.jupiter.api.extension.ExtendWith
import org.mockito.Mock 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.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest
import org.springframework.context.annotation.Import 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.test.web.reactive.server.WebTestClient
import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController import org.springframework.web.bind.annotation.RestController
import reactor.core.publisher.Mono
@Import(TestApplicationConfig::class, SecurityConfig::class) @Import(TestApplicationConfig::class, SecurityConfig::class)
@WebFluxTest(RestSecurityTests.Http200Controller::class) @WebFluxTest(RestSecurityTests.Http200Controller::class)
@ -41,14 +37,8 @@ class RestSecurityTests : BaseControllerTests() {
@RestController @RestController
class Http200Controller { class Http200Controller {
val log = logger() val log = logger()
@GetMapping("/")
suspend fun getOK(): Mono<String> {
return Mono.just("OK")
}
@GetMapping("/user") @GetMapping("/user")
suspend fun getUsername(): String = getPrincipal().also { log.debug("Principal: ${it} ") } suspend fun getUsername(): String = getPrincipal().also { log.debug("Principal: ${it} ") }
} }
@ -71,70 +61,61 @@ class RestSecurityTests : BaseControllerTests() {
@Test @Test
@DisplayName("Authentication required") @DisplayName("Authentication required")
fun getModelsList() { fun getModelsList(): Unit = runBlocking {
runBlocking { getWebTestClient()
getWebTestClient() .get()
.get() .uri("/")
.uri("/") .accept(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON) .exchange()
.exchange() .expectStatus()
.expectStatus() .is4xxClientError
.is4xxClientError
}
} }
@Test @Test
@DisplayName("getPrincipal works for mock users") @DisplayName("getPrincipal works for mock users")
@WithMockUser(username = TESTS_DEFAULT_USERNAME) @WithMockUser(username = TESTS_DEFAULT_USERNAME)
fun getPrincipalForMockUser() { fun getPrincipalForMockUser(): Unit = runBlocking {
// This one is just to make sure other tests with @WithMockUser do behave. // This one is just to make sure other tests with @WithMockUser do behave.
runBlocking { getWebTestClient()
getWebTestClient() .get()
.get() .uri("/user")
.uri("/user") .accept(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON) .exchange()
.exchange() .expectStatus()
.expectStatus() .is2xxSuccessful
.is2xxSuccessful
}
} }
@Test @Test
@DisplayName("getPrincipal works for mocked OAuth2") @DisplayName("getPrincipal works for mocked OAuth2")
fun getPrincipalForMockOAuth2() { fun getPrincipalForMockOAuth2(): Unit = runBlocking {
runBlocking { getWebTestClient()
getWebTestClient() .mutateWith(
.mutateWith( SecurityMockServerConfigurers.mockJwt().jwt { jwt ->
SecurityMockServerConfigurers.mockJwt().jwt { jwt -> jwt.claims { claims -> claims["email"] = TESTS_DEFAULT_USERNAME }
jwt.claims { claims -> claims["email"] = TESTS_DEFAULT_USERNAME } }
} )
) .get()
.get() .uri("/user")
.uri("/user") .accept(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON) .exchange()
.exchange() .expectStatus()
.expectStatus() .is2xxSuccessful
.is2xxSuccessful .expectBody(String::class.java)
.expectBody(String::class.java) .isEqualTo(TESTS_DEFAULT_USERNAME)
.isEqualTo(TESTS_DEFAULT_USERNAME)
}
} }
@Mock @Mock
private val auth: Authentication? = null private val auth: Authentication? = null
@Test @Test
@DisplayName("getPrincipal reacts to non-supported authentication context") @DisplayName("getPrincipal reacts to non-supported authentication context")
fun getPrincipalWithUnsupportedContext() { fun getPrincipalWithUnsupportedContext(): Unit = runBlocking {
runBlocking { assertThrows<IllegalStateException> {
assertThrows<IllegalStateException> { SecurityContextHolder
SecurityContextHolder .getContext()
.getContext() .authentication = auth
.authentication = auth
getPrincipal() getPrincipal()
}
} }
} }
} }

View file

@ -45,133 +45,118 @@ class ChatServiceTests : BaseServiceTests() {
@Test @Test
@DisplayName("ChatServiceTest: create chat session") @DisplayName("ChatServiceTest: create chat session")
fun createChatSession() { fun createChatSession(): Unit = runBlocking {
runBlocking { genChatSession()
genChatSession() .let { chatSession ->
.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 ->
chatService chatService
.findChatByIdForUsername(chatSession.id!!, chatSession.username) .save(chatSession)
?.apply { .apply {
assertNotNull(id) assertNotNull(id)
assertNotNull(createdDate)
assertNotNull(modifiedDate)
assertNotNull(version)
assertEquals(name, chatSession.name) assertEquals(name, chatSession.name)
assertEquals(model, chatSession.model) assertEquals(model, chatSession.model)
assertEquals(username, chatSession.username) 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 @Test
@DisplayName("ChatServiceTest: trying to fetch another user chat session") @DisplayName("ChatServiceTest: trying to fetch another user chat session")
fun findOneByIdAndUsernameButDifferentUser() { fun findOneByIdAndUsernameButDifferentUser(): Unit = runBlocking {
runBlocking { chatService
chatService .save(genChatSession())
.save(genChatSession()) .let { chatSession ->
.let { chatSession -> assertNull(
assertNull( chatService
chatService .findChatByIdForUsername(chatSession.id!!, TESTS_DEFAULT_ANOTHER_USERNAME)
.findChatByIdForUsername(chatSession.id!!, TESTS_DEFAULT_ANOTHER_USERNAME) )
) }
}
}
} }
@Test @Test
@DisplayName("ChatServiceTest: trying to fetch non existing chat session") @DisplayName("ChatServiceTest: trying to fetch non existing chat session")
fun findOneByIdAndUsernameButNonExisting() { fun findOneByIdAndUsernameButNonExisting(): Unit = runBlocking {
runBlocking { chatService
chatService .save(genChatSession())
.save(genChatSession()) .let { chatSession ->
.let { chatSession -> assertNull(
assertNull( chatService
chatService .findChatByIdForUsername(chatSession.id!! + 1, chatSession.username)
.findChatByIdForUsername(chatSession.id!! + 1, chatSession.username) )
) }
}
}
} }
@Test @Test
@DisplayName("ChatServiceTest: checking the order of queried chat sessions for username") @DisplayName("ChatServiceTest: checking the order of queried chat sessions for username")
fun findAllByUsernameAndCheckOrder() { fun findAllByUsernameAndCheckOrder(): Unit = runBlocking {
runBlocking { // Order should be reversed from the order of inserting into database. Last first.
// Order should be reversed from the order of inserting into database. Last first. (1..5).map { genChatSession() }
(1..5).map { genChatSession() } .also { csList ->
.also { csList -> chatRepository.saveAll(csList)
chatRepository.saveAll(csList) .last()
.last() }
} .also { csList ->
.also { csList -> chatService
chatService .findChatSessionsForUsername(TESTS_DEFAULT_USERNAME)
.findChatSessionsForUsername(TESTS_DEFAULT_USERNAME) .test {
.test { csList
csList .reversed()
.reversed() .forEach { cs ->
.forEach { cs -> with(this.awaitItem()) {
with(this.awaitItem()) { assertNotNull(id)
assertNotNull(id) assertNotNull(createdDate)
assertNotNull(createdDate) assertNotNull(modifiedDate)
assertNotNull(modifiedDate) assertNotNull(version)
assertNotNull(version) assertEquals(cs.name, name)
assertEquals(cs.name, name) assertEquals(cs.model, model)
assertEquals(cs.model, model) assertEquals(cs.username, username)
assertEquals(cs.username, username)
}
} }
awaitComplete() }
} awaitComplete()
} }
} }
} }
} }

View file

@ -42,91 +42,85 @@ class MessageServiceTests : BaseServiceTests() {
@Test @Test
@DisplayName("MessageServiceTest: create message") @DisplayName("MessageServiceTest: create message")
fun createMessage() { fun createMessage(): Unit = runBlocking {
runBlocking { genChatSession()
genChatSession() .let { chatSession ->
.let { chatSession -> genChatMessage(
genChatMessage( chatService
chatService .save(chatSession)
.save(chatSession) ).let { msg ->
).let { msg -> messageService.save(msg)
messageService.save(msg) .apply {
.apply { assertNotNull(id)
assertNotNull(id) assertEquals(message, msg.message)
assertEquals(message, msg.message) assertEquals(role, msg.role)
assertEquals(role, msg.role) assertEquals(done, msg.done)
assertEquals(done, msg.done) assertEquals(chatSessionId, msg.chatSessionId)
assertEquals(chatSessionId, msg.chatSessionId) }
}
}
} }
} }
} }
@Test @Test
@DisplayName("MessageServiceTest: find all previous messages and verify order") @DisplayName("MessageServiceTest: find all previous messages and verify order")
fun findAllByChatSessionIdAndUsername() { fun findAllByChatSessionIdAndUsername(): Unit = runBlocking {
runBlocking { val chatSession = genChatSession()
val chatSession = genChatSession() .let { chatSession ->
.let { chatSession -> chatService
chatService .save(chatSession)
.save(chatSession) }
}
val messageList = (1..5).map { val messageList = (1..5).map {
genChatMessage( genChatMessage(
chatSession chatSession
) )
}.toTypedArray() }.toTypedArray()
messageService messageService
.saveAll(*messageList) .saveAll(*messageList)
.last() .last()
messageService messageService
.findMessagesInChatSessionForUsername(chatSession.id!!, TESTS_DEFAULT_USERNAME) .findMessagesInChatSessionForUsername(chatSession.id!!, TESTS_DEFAULT_USERNAME)
.test { .test {
messageList messageList
.forEach { .forEach {
val item = awaitItem() val item = awaitItem()
assertNotNull(item.id) assertNotNull(item.id)
assertNotNull(item.createdDate) assertNotNull(item.createdDate)
assertNotNull(item.modifiedDate) assertNotNull(item.modifiedDate)
assertEquals(item.message, it.message) assertEquals(item.message, it.message)
assertEquals(item.role, it.role) assertEquals(item.role, it.role)
assertEquals(item.done, it.done) assertEquals(item.done, it.done)
assertEquals(item.chatSessionId, it.chatSessionId) assertEquals(item.chatSessionId, it.chatSessionId)
} }
awaitComplete() awaitComplete()
} }
}
} }
@Test @Test
@DisplayName("MessageServiceTest: whether we can retrieve messages from another user's chat session") @DisplayName("MessageServiceTest: whether we can retrieve messages from another user's chat session")
fun findAllForAnotherUser() { fun findAllForAnotherUser(): Unit = runBlocking {
runBlocking { val chatSession = genChatSession()
val chatSession = genChatSession() .let { chatSession ->
.let { chatSession -> chatService
chatService .save(chatSession)
.save(chatSession) }
}
val messageList = (1..5).map { val messageList = (1..5).map {
genChatMessage( genChatMessage(
chatSession chatSession
) )
}.toTypedArray() }.toTypedArray()
messageService messageService
.saveAll(*messageList) .saveAll(*messageList)
.last() .last()
messageService messageService
.findMessagesInChatSessionForUsername(chatSession.id!!, TESTS_DEFAULT_ANOTHER_USERNAME) .findMessagesInChatSessionForUsername(chatSession.id!!, TESTS_DEFAULT_ANOTHER_USERNAME)
.test { .test {
awaitComplete() awaitComplete()
} }
}
} }
} }