Skip to content

Commit

Permalink
#60 Optional getById
Browse files Browse the repository at this point in the history
  • Loading branch information
vityaman committed Apr 19, 2024
1 parent 63d6b23 commit 7e95fd0
Show file tree
Hide file tree
Showing 12 changed files with 52 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import ru.vityaman.lms.botalka.api.http.message.toModel
import ru.vityaman.lms.botalka.api.http.server.UserDraftMessage
import ru.vityaman.lms.botalka.api.http.server.UserMessage
import ru.vityaman.lms.botalka.api.http.server.apis.UserApi
import ru.vityaman.lms.botalka.domain.exception.orNotFound
import ru.vityaman.lms.botalka.domain.model.User
import ru.vityaman.lms.botalka.logic.UserService

Expand All @@ -18,6 +19,7 @@ class UserHttpApi(
override suspend fun userIdGet(id: Int): ResponseEntity<UserMessage> {
val userId = User.Id(id)
val user = userService.getById(userId)
.orNotFound("User with id ${userId.number} not found")
return ResponseEntity.ok(user.toMessage())
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,5 @@ fun DomainException.toResponseEntity() =
code = this.httpCode.value(),
status = this.httpCode.reasonPhrase,
message = this.message!!,
)
),
)
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ package ru.vityaman.lms.botalka.api.http.error
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.RestControllerAdvice
import ru.vityaman.lms.botalka.domain.exception.DomainException
import ru.vityaman.lms.botalka.domain.exception.InvalidValueException

@RestControllerAdvice
class DomainExceptionMapping {
@ExceptionHandler(DomainException::class)
fun handle(exception: InvalidValueException) =
fun handle(exception: DomainException) =
exception.toResponseEntity()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package ru.vityaman.lms.botalka.domain.exception

fun <T> T?.orNotFound(message: String): T =
this ?: throw NotFoundException(message)
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package ru.vityaman.lms.botalka.logic
import ru.vityaman.lms.botalka.domain.model.User

interface UserService {
suspend fun getById(id: User.Id): User
suspend fun getById(id: User.Id): User?
suspend fun create(user: User.Draft): User
suspend fun promote(id: User.Id, role: User.Role)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package ru.vityaman.lms.botalka.logic.basic
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import ru.vityaman.lms.botalka.domain.exception.PromotionRequestResolvedException
import ru.vityaman.lms.botalka.domain.exception.orNotFound
import ru.vityaman.lms.botalka.domain.model.PromotionRequest
import ru.vityaman.lms.botalka.logic.PromotionService
import ru.vityaman.lms.botalka.logic.UserService
Expand All @@ -19,18 +20,22 @@ class BasicPromotionService(
storage.create(promotion)

override suspend fun approve(id: PromotionRequest.Id) {
val request = storage.getById(id)
if (request.isResolved) {
throw PromotionRequestResolvedException(request.id)
}
val request = getUnresolvedRequestById(id)
storage.updateStatus(request.id, PromotionRequest.Status.APPROVED)
userService.promote(request.user, request.role)
}

override suspend fun reject(id: PromotionRequest.Id) {
if (storage.getById(id).isResolved) {
throw PromotionRequestResolvedException(id)
}
getUnresolvedRequestById(id)
storage.updateStatus(id, PromotionRequest.Status.REJECTED)
}

private suspend fun getUnresolvedRequestById(id: PromotionRequest.Id) =
storage.getById(id)
.orNotFound("Promotion request with id ${id.number} not found")
.also {
if (it.isResolved) {
throw PromotionRequestResolvedException(it.id)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import ru.vityaman.lms.botalka.storage.UserStorage
class BasicUserService(
@Autowired private val storage: UserStorage,
) : UserService {
override suspend fun getById(id: User.Id): User =
override suspend fun getById(id: User.Id): User? =
storage.getById(id)

override suspend fun create(user: User.Draft): User =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ interface PromotionStorage {
status: PromotionRequest.Status,
)

suspend fun getById(id: PromotionRequest.Id): PromotionRequest
suspend fun getById(id: PromotionRequest.Id): PromotionRequest?
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import ru.vityaman.lms.botalka.domain.model.Teacher
import ru.vityaman.lms.botalka.domain.model.User

interface UserStorage {
suspend fun getById(id: User.Id): User
suspend fun getById(id: User.Id): User?
suspend fun create(user: User.Draft): User
suspend fun create(teacher: Teacher)
suspend fun create(student: Student)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package ru.vityaman.lms.botalka.storage.jooq

import kotlinx.coroutines.reactor.awaitSingle
import kotlinx.coroutines.reactor.awaitSingleOrNull
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Repository
import reactor.kotlin.core.publisher.toMono
Expand Down Expand Up @@ -42,11 +43,11 @@ class JooqPromotionStorage(
.map { }
.awaitSingle()

override suspend fun getById(id: PromotionRequest.Id): PromotionRequest =
override suspend fun getById(id: PromotionRequest.Id): PromotionRequest? =
database.execute
.selectFrom(PROMOTION_REQUEST)
.where(PROMOTION_REQUEST.ID.equal(id.number))
.toMono()
.map { it.toModel() }
.awaitSingle()
.awaitSingleOrNull()
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ import ru.vityaman.lms.botalka.storage.jooq.tables.references.USER
class JooqUserStorage(
@Autowired private val database: JooqDatabase,
) : UserStorage {
override suspend fun getById(id: User.Id): User =
override suspend fun getById(id: User.Id): User? =
database.execute
.selectFrom(USER)
.where(USER.ID.equal(id.number))
.toMono()
.map { it.toModel() }
.awaitSingle()
.copy(
.awaitSingleOrNull()
?.copy(
roles = setOfNotNull(
teacher(id)?.let { User.Role.TEACHER },
student(id)?.let { User.Role.STUDENT },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,15 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.web.reactive.function.client.WebClientResponseException
import ru.vityaman.lms.botalka.BotalkaTestSuite
import ru.vityaman.lms.botalka.api.http.message.toMessage
import ru.vityaman.lms.botalka.api.http.server.apis.UserApi
import ru.vityaman.lms.botalka.api.http.client.UserApiClient
import ru.vityaman.lms.botalka.domain.model.User

@SpringBootTest
class UserApiTest(
@Autowired private val api: UserApi,
@Autowired private val api: UserApiClient,
) : BotalkaTestSuite() {
private val drafts = listOf(
User.Draft(User.Alias("admin")),
Expand All @@ -27,19 +26,17 @@ class UserApiTest(
)

@Test
fun createAndGetUser() {
val draftToResultList = runBlocking {
fun createAndGetUser() = runBlocking {
val draftToResultList =
drafts.map { draft ->
val message = draft.toMessage()
val result = async { api.userPost(message).body }
val result = async { api.get(api.register(draft.alias)) }
Pair(draft, result)
}.map { (draft, result) ->
Pair(draft, result.await() ?: throw NullPointerException())
Pair(draft, result.await())
}
}

draftToResultList.forEach { (l, r) ->
l.alias.text shouldBe r.alias
l.alias.text shouldBe r.alias.text
r.roles shouldHaveSize 0
}

Expand All @@ -48,15 +45,20 @@ class UserApiTest(
val ids = results.map { it.id }
ids.shouldBeUnique()

runBlocking {
results.forEach { expected ->
launch {
val actual = api.userIdGet(expected.id).body!!
expected.id shouldBe actual.id
expected.alias shouldBe actual.alias
expected.roles shouldContainExactly actual.roles
}
results.forEach { expected ->
launch {
val actual = api.get(expected.id)
expected.id shouldBe actual.id
expected.alias shouldBe actual.alias
expected.roles shouldContainExactly actual.roles
}
}
}

@Test
fun userNotFound(): Unit = runBlocking {
assertThrows<WebClientResponseException.NotFound> {
api.get(User.Id(666))
}
}
}

0 comments on commit 7e95fd0

Please sign in to comment.