diff --git a/src/integration-test/kotlin/ltd/hlaeja/controller/AccountEndpoint.kt b/src/integration-test/kotlin/ltd/hlaeja/controller/AccountEndpoint.kt new file mode 100644 index 0000000..c6f77cc --- /dev/null +++ b/src/integration-test/kotlin/ltd/hlaeja/controller/AccountEndpoint.kt @@ -0,0 +1,307 @@ +package ltd.hlaeja.controller + +import java.util.UUID +import ltd.hlaeja.library.accountRegistry.Account +import ltd.hlaeja.test.container.PostgresContainer +import org.assertj.core.api.SoftAssertions +import org.assertj.core.api.junit.jupiter.InjectSoftAssertions +import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.CsvSource +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT +import org.springframework.boot.test.web.server.LocalServerPort +import org.springframework.http.HttpStatus.ACCEPTED +import org.springframework.http.HttpStatus.CONFLICT +import org.springframework.test.web.reactive.server.WebTestClient +import org.springframework.test.web.reactive.server.expectBody + +@PostgresContainer +@SpringBootTest(webEnvironment = RANDOM_PORT) +@ExtendWith(SoftAssertionsExtension::class) +class AccountEndpoint { + + @InjectSoftAssertions + lateinit var softly: SoftAssertions + + @LocalServerPort + var port: Int = 0 + + lateinit var webClient: WebTestClient + + @BeforeEach + fun setup() { + webClient = WebTestClient.bindToServer().baseUrl("http://localhost:$port").build() + } + + @Nested + inner class GetAccount { + + @Test + fun `get account with valid uuid`() { + // given + val uuid = UUID.fromString("00000000-0000-7000-0000-000000000001") + + // when + val result = webClient.get().uri("/account-$uuid").exchange() + + // then + result.expectStatus().isOk() + .expectBody() + .consumeWith { + softly.assertThat(it.responseBody?.id).isEqualTo(uuid) + softly.assertThat(it.responseBody?.username).isEqualTo("admin") + softly.assertThat(it.responseBody?.enabled).isTrue + softly.assertThat(it.responseBody?.roles?.size).isEqualTo(1) + softly.assertThat(it.responseBody?.roles?.get(0)).isEqualTo("ROLE_ADMIN") + } + } + + @Test + fun `get account with invalid uuid`() { + // given + val uuid = UUID.fromString("00000000-0000-7000-0000-000000000000") + + // when + val result = webClient.get().uri("/account-$uuid").exchange() + + // then + result.expectStatus().isNotFound + } + + @Test + fun `get account with bad uuid`() { + // given + val uuidInvalid = "000000000001" + + // when + val result = webClient.get().uri("/account-$uuidInvalid").exchange() + + // then + result.expectStatus().isBadRequest + } + } + + @Nested + inner class PutAccount { + + @Test + fun `success account with all changes`() { + // given + val uuid = UUID.fromString("00000000-0000-7000-0000-000000000003") + val request = Account.Request( + username = "usernameA", + password = "abc123", + enabled = true, + roles = listOf("ROLE_USER", "ROLE_TEST"), + ) + + // when + val result = webClient.put().uri("/account-$uuid").bodyValue(request).exchange() + + // then + result.expectStatus().isOk() + .expectBody() + .consumeWith { + softly.assertThat(it.responseBody?.id).isEqualTo(uuid) + softly.assertThat(it.responseBody?.username).isEqualTo("usernameA") + softly.assertThat(it.responseBody?.enabled).isTrue + softly.assertThat(it.responseBody?.roles?.size).isEqualTo(2) + softly.assertThat(it.responseBody?.roles).contains("ROLE_USER") + softly.assertThat(it.responseBody?.roles).contains("ROLE_TEST") + } + } + + @Test + fun `success account with null password changes`() { + // given + val uuid = UUID.fromString("00000000-0000-7000-0000-000000000003") + val request = Account.Request( + username = "usernameB", + password = null, + enabled = false, + roles = listOf("ROLE_TEST"), + ) + + // when + val result = webClient.put().uri("/account-$uuid").bodyValue(request).exchange() + + // then + result.expectStatus().isOk() + .expectBody() + .consumeWith { + softly.assertThat(it.responseBody?.id).isEqualTo(uuid) + softly.assertThat(it.responseBody?.username).isEqualTo("usernameB") + softly.assertThat(it.responseBody?.enabled).isFalse + softly.assertThat(it.responseBody?.roles?.size).isEqualTo(1) + softly.assertThat(it.responseBody?.roles).contains("ROLE_TEST") + } + } + + @Test + fun `success account with no changes`() { + // given + val uuid = UUID.fromString("00000000-0000-7000-0000-000000000002") + val request = Account.Request( + username = "user", + password = null, + enabled = true, + roles = listOf("ROLE_USER"), + ) + + // when + val result = webClient.put().uri("/account-$uuid").bodyValue(request).exchange() + + // then + result.expectStatus().isEqualTo(ACCEPTED) + } + + @Test + fun `failed username duplicate`() { + // given + val uuid = UUID.fromString("00000000-0000-7000-0000-000000000002") + val request = Account.Request( + username = "admin", + password = null, + enabled = true, + roles = listOf("ROLE_USER"), + ) + + // when + val result = webClient.put().uri("/account-$uuid").bodyValue(request).exchange() + + // then + result.expectStatus().isEqualTo(CONFLICT) + } + + @Test + fun `failed account not found`() { + // given + val uuid = UUID.fromString("00000000-0000-7000-0000-000000000000") + val request = Account.Request( + username = "admin", + password = null, + enabled = true, + roles = listOf("ROLE_USER"), + ) + + // when + val result = webClient.put().uri("/account-$uuid").bodyValue(request).exchange() + + // then + result.expectStatus().isNotFound + } + + } + + @Nested + inner class PostAccount { + + @ParameterizedTest + @CsvSource( + "new-user, new-pass, true, 2, ROLE_USER;ROLE_TEST", + "admin-user, admin-pass, false, 1, ROLE_ADMIN", + "test-user, test-pass, true, 1, ROLE_USER", + ) + fun `success added account`( + username: String, + password: String, + enabled: Boolean, + size: Int, + roleList: String, + ) { + // given + val roles: List = roleList.split(";") + + val request = Account.Request( + username = username, + password = password, + enabled = enabled, + roles = roles, + ) + + // when + val result = webClient.post().uri("/account").bodyValue(request).exchange() + + // then + result.expectStatus().isCreated + .expectBody() + .consumeWith { + softly.assertThat(it.responseBody?.id?.version()).isEqualTo(7) + softly.assertThat(it.responseBody?.username).isEqualTo(username) + softly.assertThat(it.responseBody?.enabled).isEqualTo(enabled) + softly.assertThat(it.responseBody?.roles?.size).isEqualTo(size) + for (role in roles) { + softly.assertThat(it.responseBody?.roles).contains(role) + } + } + } + + @ParameterizedTest + @CsvSource( + "'', new-pass, ROLE_TEST", + "new-user, '', ROLE_ADMIN", + "new-user, new-pass, ''", + ) + fun `validation fail on empty values`( + username: String, + password: String, + roleList: String, + ) { + // given + val request = Account.Request( + username = username, + password = password, + enabled = true, + roles = when { + roleList.isEmpty() -> emptyList() + else -> listOf(roleList) + }, + ) + + // when + val result = webClient.post().uri("/account").bodyValue(request).exchange() + + // then + result.expectStatus().isBadRequest + } + + @Test + fun `fail username take`() { + // given + val request = Account.Request( + username = "user", + password = "new-pass", + enabled = true, + roles = listOf("ROLE_USER", "ROLE_TEST"), + ) + + // when + val result = webClient.post().uri("/account").bodyValue(request).exchange() + + // then + result.expectStatus().isEqualTo(CONFLICT) + } + + @Test + fun `fail password null`() { + // given + val request = Account.Request( + username = "user", + password = null, + enabled = true, + roles = listOf("ROLE_USER", "ROLE_TEST"), + ) + + // when + val result = webClient.post().uri("/account").bodyValue(request).exchange() + + // then + result.expectStatus().isBadRequest + } + } +} diff --git a/src/main/kotlin/ltd/hlaeja/controller/AccountController.kt b/src/main/kotlin/ltd/hlaeja/controller/AccountController.kt index 9b1bbd0..985481b 100644 --- a/src/main/kotlin/ltd/hlaeja/controller/AccountController.kt +++ b/src/main/kotlin/ltd/hlaeja/controller/AccountController.kt @@ -1,6 +1,7 @@ package ltd.hlaeja.controller import java.util.UUID +import ltd.hlaeja.validator.ValidAccount import ltd.hlaeja.entity.AccountEntity import ltd.hlaeja.library.accountRegistry.Account import ltd.hlaeja.service.AccountService @@ -8,12 +9,14 @@ import ltd.hlaeja.util.toAccountEntity import ltd.hlaeja.util.toAccountResponse import ltd.hlaeja.util.updateAccountEntity import org.springframework.http.HttpStatus.ACCEPTED +import org.springframework.http.HttpStatus.CREATED import org.springframework.security.crypto.password.PasswordEncoder import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.PutMapping import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.ResponseStatus import org.springframework.web.bind.annotation.RestController import org.springframework.web.server.ResponseStatusException import reactor.core.publisher.Mono @@ -33,7 +36,7 @@ class AccountController( @PutMapping("/account-{uuid}") fun updateAccount( @PathVariable uuid: UUID, - @RequestBody request: Account.Request, + @RequestBody @ValidAccount request: Account.Request, ): Mono = accountService.getUserById(uuid) .map { user -> user.updateAccountEntity(request, passwordEncoder) @@ -43,8 +46,9 @@ class AccountController( .map { it.toAccountResponse() } @PostMapping("/account") + @ResponseStatus(CREATED) fun addAccount( - @RequestBody request: Account.Request, + @RequestBody @ValidAccount request: Account.Request, ): Mono = accountService.addAccount(request.toAccountEntity(passwordEncoder)) .map { it.toAccountResponse() }