Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package br.all.application.user.find

import br.all.application.user.find.RetrieveUserProfileService.ResponseModel
import br.all.domain.shared.presenter.GenericPresenter

interface RetrieveUserProfilePresenter : GenericPresenter<ResponseModel>
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package br.all.application.user.find
import java.util.UUID

interface RetrieveUserProfileService {
fun retrieveData(presenter: RetrieveUserProfilePresenter, request: RequestModel)

data class RequestModel(
val userId: UUID
)

data class ResponseModel(
val userId: UUID,
val username: String,
val email: String,
val affiliation: String,
val country: String,
val authorities: Set<String>,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package br.all.application.user.find

import br.all.application.user.find.RetrieveUserProfileService.RequestModel
import br.all.application.user.find.RetrieveUserProfileService.ResponseModel
import br.all.application.user.repository.UserAccountRepository

class RetrieveUserProfileServiceImpl(
private val userAccountRepository: UserAccountRepository
) : RetrieveUserProfileService {
override fun retrieveData(
presenter: RetrieveUserProfilePresenter,
request: RequestModel
) {
val userProfile = userAccountRepository.loadUserProfileById(request.userId)
if (userProfile == null) {
presenter.prepareFailView(NoSuchElementException("User with id ${request.userId} doesn't exist!"))
return
}

val userCredentials = userAccountRepository.loadCredentialsById(request.userId)
if (userCredentials == null) {
presenter.prepareFailView(NoSuchElementException("Account credentials with id ${request.userId} doesn't exist!"))
return
}


val profile = ResponseModel(
userId = userProfile.id,
username = userCredentials.username,
email = userProfile.email,
affiliation = userProfile.affiliation,
country = userProfile.country,
authorities = userCredentials.authorities
)

presenter.prepareSuccessView(profile)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ interface UserAccountRepository {
fun existsByEmail(email: String): Boolean
fun existsByUsername(username: String): Boolean
fun deleteById(id: UUID)
fun loadUserProfileById(id: UUID): UserProfileDto?
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package br.all.application.user.repository

import java.util.UUID

data class UserProfileDto(
val id: UUID,
val email: String,
val country: String,
val affiliation: String,
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package br.all.infrastructure.user

import br.all.application.user.repository.AccountCredentialsDto
import br.all.application.user.repository.UserAccountDto
import br.all.application.user.repository.UserProfileDto

fun UserAccountDto.toUserAccountEntity(): UserAccountEntity {
val credentials = AccountCredentialsEntity(
Expand All @@ -18,4 +19,11 @@ fun UserAccountDto.toUserAccountEntity(): UserAccountEntity {
return UserAccountEntity(id, credentials, email, country, affiliation, createdAt)
}

fun AccountCredentialsEntity.toAccountCredentialsDto() = AccountCredentialsDto(id, username, password, authorities, refreshToken)
fun AccountCredentialsEntity.toAccountCredentialsDto() = AccountCredentialsDto(id, username, password, authorities, refreshToken)

fun UserAccountEntity.toUserProfileDto() = UserProfileDto(
id = this.id,
email = this.email,
country = this.country,
affiliation = this.affiliation
)
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package br.all.infrastructure.user
import br.all.application.user.repository.AccountCredentialsDto
import br.all.application.user.repository.UserAccountDto
import br.all.application.user.repository.UserAccountRepository
import br.all.application.user.repository.UserProfileDto
import org.springframework.stereotype.Repository
import java.util.*

Expand All @@ -22,6 +23,9 @@ class UserAccountRepositoryImpl(
credentialsRepository.deleteById(id)
}

override fun loadUserProfileById(id: UUID): UserProfileDto? =
userAccountRepository.findById(id).orElse(null)?.toUserProfileDto()

override fun loadCredentialsByUsername(username: String) =
credentialsRepository.findByUsername(username)?.toAccountCredentialsDto()

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package br.all.application.user.find

import br.all.application.user.repository.UserAccountRepository
import br.all.application.user.utils.TestDataFactory
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.junit5.MockKExtension
import io.mockk.slot
import io.mockk.verify
import org.junit.jupiter.api.*
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.extension.ExtendWith
import java.util.*
import kotlin.NoSuchElementException

@Tag("UnitTest")
@Tag("ServiceTest")
@ExtendWith(MockKExtension::class)
class RetrieveUserProfileServiceImplTest {

@MockK(relaxUnitFun = true)
private lateinit var userAccountRepository: UserAccountRepository

@MockK(relaxUnitFun = true)
private lateinit var presenter: RetrieveUserProfilePresenter

private lateinit var sut: RetrieveUserProfileServiceImpl
private lateinit var factory: TestDataFactory

@BeforeEach
fun setUp() {
sut = RetrieveUserProfileServiceImpl(userAccountRepository)
factory = TestDataFactory()
}

@Nested
@DisplayName("When retrieving a user profile")
inner class WhenRetrievingUserProfile {

@Test
fun `should retrieve user profile and prepare success view`() {
val userProfile = factory.userProfile()
val userCredentials = factory.accountCredentials().copy(id = userProfile.id)
val request = RetrieveUserProfileService.RequestModel(userId = userProfile.id)

every { userAccountRepository.loadUserProfileById(request.userId) } returns userProfile
every { userAccountRepository.loadCredentialsById(request.userId) } returns userCredentials

val responseSlot = slot<RetrieveUserProfileService.ResponseModel>()
every { presenter.prepareSuccessView(capture(responseSlot)) } returns Unit

sut.retrieveData(presenter, request)

verify(exactly = 1) { presenter.prepareSuccessView(any()) }
verify(exactly = 0) { presenter.prepareFailView(any()) }

val capturedResponse = responseSlot.captured
assertEquals(userProfile.id, capturedResponse.userId)
assertEquals(userCredentials.username, capturedResponse.username)
assertEquals(userProfile.email, capturedResponse.email)
assertEquals(userProfile.affiliation, capturedResponse.affiliation)
assertEquals(userProfile.country, capturedResponse.country)
assertEquals(userCredentials.authorities, capturedResponse.authorities)
}

@Test
fun `should prepare fail view when user profile is not found`() {
val userId = UUID.randomUUID()
val request = RetrieveUserProfileService.RequestModel(userId = userId)

every { userAccountRepository.loadUserProfileById(userId) } returns null

val exceptionSlot = slot<NoSuchElementException>()
every { presenter.prepareFailView(capture(exceptionSlot)) } returns Unit

sut.retrieveData(presenter, request)

verify(exactly = 0) { presenter.prepareSuccessView(any()) }
verify(exactly = 1) { presenter.prepareFailView(any()) }

val capturedException = exceptionSlot.captured
assertEquals("User with id $userId doesn't exist!", capturedException.message)
}

@Test
fun `should prepare fail view when user credentials are not found`() {
val userProfile = factory.userProfile()
val request = RetrieveUserProfileService.RequestModel(userId = userProfile.id)

every { userAccountRepository.loadUserProfileById(request.userId) } returns userProfile
every { userAccountRepository.loadCredentialsById(request.userId) } returns null

val exceptionSlot = slot<NoSuchElementException>()
every { presenter.prepareFailView(capture(exceptionSlot)) } returns Unit

sut.retrieveData(presenter, request)

verify(exactly = 0) { presenter.prepareSuccessView(any()) }
verify(exactly = 1) { presenter.prepareFailView(any()) }

val capturedException = exceptionSlot.captured
assertEquals("Account credentials with id ${request.userId} doesn't exist!", capturedException.message)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package br.all.application.user.utils

import br.all.application.user.create.RegisterUserAccountService
import br.all.application.user.repository.AccountCredentialsDto
import br.all.application.user.repository.UserProfileDto
import io.github.serpro69.kfaker.Faker
import java.util.*

Expand All @@ -25,4 +26,10 @@ class TestDataFactory {
refreshToken = faker.lorem.words()
)

fun userProfile() = UserProfileDto(
id = UUID.randomUUID(),
email = faker.internet.email(),
country = faker.address.countryCode(),
affiliation = faker.leagueOfLegends.rank(),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class JwtAuthenticationFilter(
) : OncePerRequestFilter() {

private val matchersToSkip: List<RequestMatcher> = listOf(
AntPathRequestMatcher("/api/v1/user"),
AntPathRequestMatcher("/api/v1/user", "POST"),
AntPathRequestMatcher("/api/v1/auth/"),
AntPathRequestMatcher("/api/v1/auth/logout/"),
AntPathRequestMatcher("/webjars/**"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class SecurityConfiguration(
"/webjars/**"
).permitAll()
.requestMatchers(HttpMethod.POST, "/api/v1/user").permitAll()
.requestMatchers(HttpMethod.GET, "/api/v1/user").authenticated()
.anyRequest().fullyAuthenticated()

}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package br.all.user.controller

import br.all.application.user.create.RegisterUserAccountServiceImpl
import br.all.application.user.find.RetrieveUserProfileServiceImpl
import br.all.application.user.repository.UserAccountRepository
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
Expand All @@ -10,4 +11,7 @@ class UserAccountConfiguration {

@Bean
fun registerUser(repository: UserAccountRepository) = RegisterUserAccountServiceImpl(repository)

@Bean
fun retrieveUserProfile(repository: UserAccountRepository) = RetrieveUserProfileServiceImpl(repository)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ package br.all.user.controller
import br.all.application.user.CredentialsService
import br.all.application.user.create.RegisterUserAccountService
import br.all.application.user.create.RegisterUserAccountService.RequestModel
import br.all.user.presenter.RestfullRegisterUserAccountPresenter
import br.all.application.user.find.RetrieveUserProfileService
import br.all.security.service.AuthenticationInfoService
import br.all.user.presenter.RestfulRegisterUserAccountPresenter
import br.all.user.presenter.RestfulRetrieveUserProfilePresenter
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.media.Content
import io.swagger.v3.oas.annotations.media.Schema
Expand All @@ -12,6 +15,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
Expand All @@ -21,7 +25,9 @@ import org.springframework.web.bind.annotation.RestController
@RequestMapping("api/v1/user")
class UserAccountController(
private val registerUserAccountService: RegisterUserAccountService,
private val encoder: PasswordEncoder
private val encoder: PasswordEncoder,
private val retrieveUserProfileService: RetrieveUserProfileService,
private val authenticationInfoService: AuthenticationInfoService
) {

@PostMapping
Expand Down Expand Up @@ -49,9 +55,46 @@ class UserAccountController(
]
)
fun registerUser(@RequestBody request: RequestModel): ResponseEntity<*> {
val presenter = RestfullRegisterUserAccountPresenter()
val presenter = RestfulRegisterUserAccountPresenter()
val encodedPasswordRequest = request.copy(password = encoder.encode(request.password))
registerUserAccountService.register(presenter, encodedPasswordRequest)
return presenter.responseEntity ?: ResponseEntity<Void>(HttpStatus.INTERNAL_SERVER_ERROR)
}

@GetMapping("/profile")
@Operation(summary = "Retrieve public information of a user")
@ApiResponses(
value = [
ApiResponse(
responseCode = "200",
description = "Success retrieving user profile",
content = [Content(
mediaType = "application/json",
schema = Schema(implementation = RetrieveUserProfileService.ResponseModel::class)
)]
),
ApiResponse(
responseCode = "401",
description = "Fail retrieving user profile - unauthenticated collaborator",
content = [Content(schema = Schema(hidden = true))]
),
ApiResponse(
responseCode = "403",
description = "Fail retrieving user profile - unauthorized collaborator",
content = [Content(schema = Schema(hidden = true))]
),
ApiResponse(
responseCode = "404",
description = "Fail retrieving user profile - nonexistent user",
content = [Content(schema = Schema(hidden = true))]
),
])
fun retrieveUserPublicData(): ResponseEntity<*> {
val presenter = RestfulRetrieveUserProfilePresenter()
val userId = authenticationInfoService.getAuthenticatedUserId()
val request = RetrieveUserProfileService.RequestModel(userId)

retrieveUserProfileService.retrieveData(presenter, request)
return presenter.responseEntity ?: ResponseEntity<Void>(HttpStatus.INTERNAL_SERVER_ERROR)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import org.springframework.http.ResponseEntity
import org.springframework.http.ResponseEntity.status
import java.util.*

class RestfullRegisterUserAccountPresenter : RegisterUserAccountPresenter {
class RestfulRegisterUserAccountPresenter : RegisterUserAccountPresenter {

var responseEntity: ResponseEntity<*>? = null

Expand Down
Loading
Loading