9 Commits

19 changed files with 244 additions and 61 deletions

View File

@@ -9,7 +9,7 @@ insert_final_newline = true
max_line_length = 120 max_line_length = 120
tab_width = 4 tab_width = 4
[*.{md,sh,sql,yaml,yml}] [*.{md,sh,sql,xml,xsd,yaml,yml}]
max_line_length = 1024 max_line_length = 1024
indent_size = 2 indent_size = 2
tab_width = 2 tab_width = 2

View File

@@ -14,6 +14,7 @@ dependencies {
implementation(hlaeja.kotlinx.coroutines) implementation(hlaeja.kotlinx.coroutines)
implementation(hlaeja.library.common.messages) implementation(hlaeja.library.common.messages)
implementation(hlaeja.library.jwt) implementation(hlaeja.library.jwt)
implementation(hlaeja.springboot.kafka)
implementation(hlaeja.springboot.starter.actuator) implementation(hlaeja.springboot.starter.actuator)
implementation(hlaeja.springboot.starter.r2dbc) implementation(hlaeja.springboot.starter.r2dbc)
implementation(hlaeja.springboot.starter.security) implementation(hlaeja.springboot.starter.security)
@@ -28,6 +29,7 @@ dependencies {
testImplementation(hlaeja.projectreactor.reactor.test) testImplementation(hlaeja.projectreactor.reactor.test)
testImplementation(hlaeja.kotlin.test.junit5) testImplementation(hlaeja.kotlin.test.junit5)
testImplementation(hlaeja.kotlinx.coroutines.test) testImplementation(hlaeja.kotlinx.coroutines.test)
testImplementation(hlaeja.springboot.kafka.test)
testImplementation(hlaeja.springboot.starter.test) testImplementation(hlaeja.springboot.starter.test)
testRuntimeOnly(hlaeja.junit.platform.launcher) testRuntimeOnly(hlaeja.junit.platform.launcher)

View File

@@ -1,4 +1,4 @@
kotlin.code.style=official kotlin.code.style=official
version=0.3.0 version=0.4.0
catalog=0.11.0 catalog=0.12.0
container.port.host=9050 container.port.host=9050

View File

@@ -10,8 +10,8 @@ Content-Type: application/json
"password": "p4ssw0rd", "password": "p4ssw0rd",
"enabled": true, "enabled": true,
"roles": [ "roles": [
"ROLE_ADMIN", "ADMIN",
"ROLE_TEST" "TEST"
] ]
} }
@@ -24,7 +24,7 @@ Content-Type: application/json
"password": "pass", "password": "pass",
"enabled": true, "enabled": true,
"roles": [ "roles": [
"ROLE_TEST" "TEST"
] ]
} }
@@ -36,6 +36,6 @@ Content-Type: application/json
"username": "user", "username": "user",
"enabled": true, "enabled": true,
"roles": [ "roles": [
"ROLE_TEST" "TEST"
] ]
} }

View File

@@ -0,0 +1,6 @@
UPDATE public.accounts
SET
roles = REPLACE(roles, 'ROLE_', ''),
updated_at = CURRENT_TIMESTAMP
WHERE
roles LIKE '%ROLE_%';

View File

@@ -1,4 +1,5 @@
insert into public.accounts (id, created_at, updated_at, enabled, username, password, roles) insert into public.accounts (id, created_at, updated_at, enabled, username, password, roles)
values ('00000000-0000-7000-0000-000000000001'::uuid, '2000-01-01 00:00:00.000001 +00:00', '2000-01-01 00:00:01.000001 +00:00', true, 'admin', '$2a$12$KoXBoLOANMK11J4xeJHPA.Sy0FG.m8KWk7P4XFsMO.ZbFmFI2DckK', 'ROLE_ADMIN'), values ('00000000-0000-7000-0000-000000000001'::uuid, '2000-01-01 00:00:00.000001 +00:00', '2000-01-01 00:00:01.000001 +00:00', true, 'admin', '$2a$12$KoXBoLOANMK11J4xeJHPA.Sy0FG.m8KWk7P4XFsMO.ZbFmFI2DckK', 'ADMIN'),
('00000000-0000-7000-0000-000000000002'::uuid, '2000-01-01 00:00:00.000001 +00:00', '2000-01-01 00:00:01.000001 +00:00', true, 'user', '$2a$12$KoXBoLOANMK11J4xeJHPA.Sy0FG.m8KWk7P4XFsMO.ZbFmFI2DckK', 'ROLE_USER'), ('00000000-0000-7000-0000-000000000002'::uuid, '2000-01-01 00:00:00.000001 +00:00', '2000-01-01 00:00:01.000001 +00:00', true, 'user', '$2a$12$KoXBoLOANMK11J4xeJHPA.Sy0FG.m8KWk7P4XFsMO.ZbFmFI2DckK', 'USER'),
('00000000-0000-7000-0000-000000000003'::uuid, '2000-01-01 00:00:00.000001 +00:00', '2000-01-01 00:00:01.000001 +00:00', false, 'disabled', '$2a$12$KoXBoLOANMK11J4xeJHPA.Sy0FG.m8KWk7P4XFsMO.ZbFmFI2DckK', 'ROLE_USER'); ('00000000-0000-7000-0000-000000000002'::uuid, '2000-01-01 00:00:00.000001 +00:00', '2000-01-01 00:00:01.000001 +00:00', true, 'user', '$2a$12$KoXBoLOANMK11J4xeJHPA.Sy0FG.m8KWk7P4XFsMO.ZbFmFI2DckK', 'USER'),
('00000000-0000-7000-0000-000000000003'::uuid, '2000-01-01 00:00:00.000001 +00:00', '2000-01-01 00:00:01.000001 +00:00', false, 'disabled', '$2a$12$KoXBoLOANMK11J4xeJHPA.Sy0FG.m8KWk7P4XFsMO.ZbFmFI2DckK', 'USER');

View File

@@ -1,13 +1,15 @@
package ltd.hlaeja.controller package ltd.hlaeja.controller
import java.util.UUID import java.util.UUID
import ltd.hlaeja.validator.ValidAccount
import ltd.hlaeja.entity.AccountEntity import ltd.hlaeja.entity.AccountEntity
import ltd.hlaeja.library.accountRegistry.Account import ltd.hlaeja.library.accountRegistry.Account
import ltd.hlaeja.service.AccountService import ltd.hlaeja.service.AccountService
import ltd.hlaeja.service.PublicEventService
import ltd.hlaeja.util.detectChanges
import ltd.hlaeja.util.toAccountEntity import ltd.hlaeja.util.toAccountEntity
import ltd.hlaeja.util.toAccountResponse import ltd.hlaeja.util.toAccountResponse
import ltd.hlaeja.util.updateAccountEntity import ltd.hlaeja.util.updateAccountEntity
import ltd.hlaeja.validator.ValidAccount
import org.springframework.http.HttpStatus.ACCEPTED import org.springframework.http.HttpStatus.ACCEPTED
import org.springframework.http.HttpStatus.CREATED import org.springframework.http.HttpStatus.CREATED
import org.springframework.security.crypto.password.PasswordEncoder import org.springframework.security.crypto.password.PasswordEncoder
@@ -25,6 +27,7 @@ import reactor.core.publisher.Mono
class AccountController( class AccountController(
private val accountService: AccountService, private val accountService: AccountService,
private val passwordEncoder: PasswordEncoder, private val passwordEncoder: PasswordEncoder,
private val publicEventService: PublicEventService,
) { ) {
@GetMapping("/account-{uuid}") @GetMapping("/account-{uuid}")
@@ -40,9 +43,13 @@ class AccountController(
): Mono<Account.Response> = accountService.getUserById(uuid) ): Mono<Account.Response> = accountService.getUserById(uuid)
.map { user -> .map { user ->
user.updateAccountEntity(request, passwordEncoder) user.updateAccountEntity(request, passwordEncoder)
.also { if (hasChange(user, it)) throw ResponseStatusException(ACCEPTED) } .let { it to it.detectChanges(user) }
.also { if (it.second.isEmpty()) throw ResponseStatusException(ACCEPTED) }
}
.flatMap { (updated: AccountEntity, changes: List<String>) ->
accountService.updateAccount(updated)
.flatMap { publicEventService.updateAccount(it, changes) }
} }
.flatMap { accountService.updateAccount(it) }
.map { it.toAccountResponse() } .map { it.toAccountResponse() }
@PostMapping("/account") @PostMapping("/account")
@@ -51,12 +58,4 @@ class AccountController(
@RequestBody @ValidAccount request: Account.Request, @RequestBody @ValidAccount request: Account.Request,
): Mono<Account.Response> = accountService.addAccount(request.toAccountEntity(passwordEncoder)) ): Mono<Account.Response> = accountService.addAccount(request.toAccountEntity(passwordEncoder))
.map { it.toAccountResponse() } .map { it.toAccountResponse() }
private fun hasChange(
user: AccountEntity,
update: AccountEntity,
): Boolean = user.password == update.password &&
user.username == update.username &&
user.enabled == update.enabled &&
user.roles == update.roles
} }

View File

@@ -0,0 +1,25 @@
package ltd.hlaeja.service
import io.github.oshai.kotlinlogging.KotlinLogging
import ltd.hlaeja.entity.AccountEntity
import ltd.hlaeja.library.accountRegistry.event.AccountMessage
import org.springframework.kafka.core.KafkaTemplate
import org.springframework.stereotype.Service
import reactor.core.publisher.Mono
private val log = KotlinLogging.logger {}
@Service
class PublicEventService(
private val kafkaTemplate: KafkaTemplate<String, AccountMessage>,
) {
fun updateAccount(
account: AccountEntity,
changes: List<String>,
): Mono<AccountEntity> = Mono
.fromFuture(kafkaTemplate.send("account", "change", AccountMessage(account.id!!, changes)))
.doOnSuccess { log.trace { "Sent Kafka created event for user ${account.id}" } }
.doOnError { e -> log.error(e) { "Failed to send Kafka event" } }
.thenReturn(account)
}

View File

@@ -0,0 +1,25 @@
package ltd.hlaeja.util
import ltd.hlaeja.entity.AccountEntity
import org.springframework.http.HttpStatus.ACCEPTED
import org.springframework.web.server.ResponseStatusException
fun AccountEntity.detectChanges(original: AccountEntity): List<String> {
val changes: MutableList<String> = mutableListOf()
if (original.password != password) {
changes.add("password")
}
if (original.username != username) {
changes.add("username")
}
if (original.enabled != enabled) {
changes.add("enabled")
}
if (original.roles != roles) {
changes.add("roles")
}
if (changes.isEmpty()) {
throw ResponseStatusException(ACCEPTED)
}
return changes
}

View File

@@ -9,6 +9,11 @@ spring:
os: os:
name: "%APP_BUILD_OS_NAME%" name: "%APP_BUILD_OS_NAME%"
version: "%APP_BUILD_OS_VERSION%" version: "%APP_BUILD_OS_VERSION%"
r2dbc:
username: services
kafka:
producer:
value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
management: management:
endpoints: endpoints:
@@ -37,8 +42,9 @@ spring:
on-profile: development on-profile: development
r2dbc: r2dbc:
url: r2dbc:postgresql://localhost:5432/account_registry url: r2dbc:postgresql://localhost:5432/account_registry
username: services
password: password password: password
kafka:
bootstrap-servers: localhost:9091
--- ---
########################## ##########################
@@ -50,14 +56,17 @@ spring:
on-profile: docker on-profile: docker
r2dbc: r2dbc:
url: r2dbc:postgresql://PostgreSQL:5432/account_registry url: r2dbc:postgresql://PostgreSQL:5432/account_registry
username: services
password: password password: password
kafka:
bootstrap-servers: kafka:9092
--- ---
############################## ##############################
### Production environment ### ### Kubernetes environment ###
############################## ##############################
spring: spring:
config: config:
activate: activate:
on-profile: production on-profile: kubernetes
r2dbc:
url: r2dbc:postgresql://dependency-postgresql:5432/account_registry

View File

@@ -7,7 +7,10 @@
<root level="INFO"> <root level="INFO">
<appender-ref ref="STDOUT"/> <appender-ref ref="STDOUT"/>
</root> </root>
<springProfile name="develop|docker"> <springProfile name="development">
<logger level="TRACE" name="ltd.hlaeja"/>
</springProfile>
<springProfile name="docker">
<logger level="DEBUG" name="ltd.hlaeja"/> <logger level="DEBUG" name="ltd.hlaeja"/>
</springProfile> </springProfile>
</configuration> </configuration>

View File

@@ -2,7 +2,7 @@ package ltd.hlaeja.controller
import java.util.UUID import java.util.UUID
import ltd.hlaeja.library.accountRegistry.Account import ltd.hlaeja.library.accountRegistry.Account
import ltd.hlaeja.test.container.PostgresContainer import ltd.hlaeja.test.container.KafkaPostgresTestContainer
import org.assertj.core.api.SoftAssertions import org.assertj.core.api.SoftAssertions
import org.assertj.core.api.junit.jupiter.InjectSoftAssertions import org.assertj.core.api.junit.jupiter.InjectSoftAssertions
import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension
@@ -20,7 +20,7 @@ import org.springframework.http.HttpStatus.CONFLICT
import org.springframework.test.web.reactive.server.WebTestClient import org.springframework.test.web.reactive.server.WebTestClient
import org.springframework.test.web.reactive.server.expectBody import org.springframework.test.web.reactive.server.expectBody
@PostgresContainer @KafkaPostgresTestContainer
@SpringBootTest(webEnvironment = RANDOM_PORT) @SpringBootTest(webEnvironment = RANDOM_PORT)
@ExtendWith(SoftAssertionsExtension::class) @ExtendWith(SoftAssertionsExtension::class)
class AccountEndpoint { class AccountEndpoint {
@@ -57,7 +57,7 @@ class AccountEndpoint {
softly.assertThat(it.responseBody?.username).isEqualTo("admin") softly.assertThat(it.responseBody?.username).isEqualTo("admin")
softly.assertThat(it.responseBody?.enabled).isTrue softly.assertThat(it.responseBody?.enabled).isTrue
softly.assertThat(it.responseBody?.roles?.size).isEqualTo(1) softly.assertThat(it.responseBody?.roles?.size).isEqualTo(1)
softly.assertThat(it.responseBody?.roles?.get(0)).isEqualTo("ROLE_ADMIN") softly.assertThat(it.responseBody?.roles?.get(0)).isEqualTo("ADMIN")
} }
} }
@@ -97,7 +97,7 @@ class AccountEndpoint {
username = "usernameA", username = "usernameA",
password = "abc123", password = "abc123",
enabled = true, enabled = true,
roles = listOf("ROLE_USER", "ROLE_TEST"), roles = listOf("USER", "TEST"),
) )
// when // when
@@ -111,8 +111,8 @@ class AccountEndpoint {
softly.assertThat(it.responseBody?.username).isEqualTo("usernameA") softly.assertThat(it.responseBody?.username).isEqualTo("usernameA")
softly.assertThat(it.responseBody?.enabled).isTrue softly.assertThat(it.responseBody?.enabled).isTrue
softly.assertThat(it.responseBody?.roles?.size).isEqualTo(2) softly.assertThat(it.responseBody?.roles?.size).isEqualTo(2)
softly.assertThat(it.responseBody?.roles).contains("ROLE_USER") softly.assertThat(it.responseBody?.roles).contains("USER")
softly.assertThat(it.responseBody?.roles).contains("ROLE_TEST") softly.assertThat(it.responseBody?.roles).contains("TEST")
} }
} }
@@ -124,7 +124,7 @@ class AccountEndpoint {
username = "usernameB", username = "usernameB",
password = null, password = null,
enabled = false, enabled = false,
roles = listOf("ROLE_TEST"), roles = listOf("TEST"),
) )
// when // when
@@ -138,7 +138,7 @@ class AccountEndpoint {
softly.assertThat(it.responseBody?.username).isEqualTo("usernameB") softly.assertThat(it.responseBody?.username).isEqualTo("usernameB")
softly.assertThat(it.responseBody?.enabled).isFalse softly.assertThat(it.responseBody?.enabled).isFalse
softly.assertThat(it.responseBody?.roles?.size).isEqualTo(1) softly.assertThat(it.responseBody?.roles?.size).isEqualTo(1)
softly.assertThat(it.responseBody?.roles).contains("ROLE_TEST") softly.assertThat(it.responseBody?.roles).contains("TEST")
} }
} }
@@ -150,7 +150,7 @@ class AccountEndpoint {
username = "user", username = "user",
password = null, password = null,
enabled = true, enabled = true,
roles = listOf("ROLE_USER"), roles = listOf("USER"),
) )
// when // when
@@ -168,7 +168,7 @@ class AccountEndpoint {
username = "admin", username = "admin",
password = null, password = null,
enabled = true, enabled = true,
roles = listOf("ROLE_USER"), roles = listOf("USER"),
) )
// when // when
@@ -186,7 +186,7 @@ class AccountEndpoint {
username = "admin", username = "admin",
password = null, password = null,
enabled = true, enabled = true,
roles = listOf("ROLE_USER"), roles = listOf("USER"),
) )
// when // when
@@ -202,9 +202,9 @@ class AccountEndpoint {
@ParameterizedTest @ParameterizedTest
@CsvSource( @CsvSource(
"new-user, new-pass, true, 2, ROLE_USER;ROLE_TEST", "new-user, new-pass, true, 2, USER;TEST",
"admin-user, admin-pass, false, 1, ROLE_ADMIN", "admin-user, admin-pass, false, 1, ADMIN",
"test-user, test-pass, true, 1, ROLE_USER", "test-user, test-pass, true, 1, USER",
) )
fun `success added account`( fun `success added account`(
username: String, username: String,
@@ -242,8 +242,8 @@ class AccountEndpoint {
@ParameterizedTest @ParameterizedTest
@CsvSource( @CsvSource(
"'', new-pass, ROLE_TEST", "'', new-pass, TEST",
"new-user, '', ROLE_ADMIN", "new-user, '', ADMIN",
"new-user, new-pass, ''", "new-user, new-pass, ''",
) )
fun `validation fail on empty values`( fun `validation fail on empty values`(
@@ -276,7 +276,7 @@ class AccountEndpoint {
username = "user", username = "user",
password = "new-pass", password = "new-pass",
enabled = true, enabled = true,
roles = listOf("ROLE_USER", "ROLE_TEST"), roles = listOf("USER", "TEST"),
) )
// when // when
@@ -293,7 +293,7 @@ class AccountEndpoint {
username = "user", username = "user",
password = null, password = null,
enabled = true, enabled = true,
roles = listOf("ROLE_USER", "ROLE_TEST"), roles = listOf("USER", "TEST"),
) )
// when // when

View File

@@ -1,7 +1,7 @@
package ltd.hlaeja.controller package ltd.hlaeja.controller
import ltd.hlaeja.library.accountRegistry.Account import ltd.hlaeja.library.accountRegistry.Account
import ltd.hlaeja.test.container.PostgresContainer import ltd.hlaeja.test.container.PostgresTestContainer
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
@@ -13,7 +13,7 @@ import org.springframework.boot.test.web.server.LocalServerPort
import org.springframework.test.web.reactive.server.WebTestClient import org.springframework.test.web.reactive.server.WebTestClient
import org.springframework.test.web.reactive.server.expectBody import org.springframework.test.web.reactive.server.expectBody
@PostgresContainer @PostgresTestContainer
@SpringBootTest(webEnvironment = RANDOM_PORT) @SpringBootTest(webEnvironment = RANDOM_PORT)
class AccountsEndpoint { class AccountsEndpoint {

View File

@@ -1,9 +1,9 @@
package ltd.hlaeja.controller package ltd.hlaeja.controller
import org.assertj.core.api.Assertions.assertThat
import ltd.hlaeja.library.accountRegistry.Authentication import ltd.hlaeja.library.accountRegistry.Authentication
import ltd.hlaeja.test.compareToFile import ltd.hlaeja.test.compareToFile
import ltd.hlaeja.test.container.PostgresContainer import ltd.hlaeja.test.container.PostgresTestContainer
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.SoftAssertions import org.assertj.core.api.SoftAssertions
import org.assertj.core.api.junit.jupiter.InjectSoftAssertions import org.assertj.core.api.junit.jupiter.InjectSoftAssertions
import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension
@@ -17,7 +17,7 @@ import org.springframework.http.HttpStatus
import org.springframework.test.web.reactive.server.WebTestClient import org.springframework.test.web.reactive.server.WebTestClient
import org.springframework.test.web.reactive.server.expectBody import org.springframework.test.web.reactive.server.expectBody
@PostgresContainer @PostgresTestContainer
@SpringBootTest(webEnvironment = RANDOM_PORT) @SpringBootTest(webEnvironment = RANDOM_PORT)
@ExtendWith(SoftAssertionsExtension::class) @ExtendWith(SoftAssertionsExtension::class)
class AuthenticationEndpoint { class AuthenticationEndpoint {

View File

@@ -6,10 +6,14 @@ spring:
url: r2dbc:postgresql://placeholder url: r2dbc:postgresql://placeholder
username: placeholder username: placeholder
password: placeholder password: placeholder
kafka:
container: consumer:
postgres: group-id: test-group
version: postgres:17 auto-offset-reset: earliest
init: postgres/schema.sql key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
before: postgres/data.sql value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
after: postgres/reset.sql properties:
spring.json.trusted.packages: "*"
producer:
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.springframework.kafka.support.serializer.JsonSerializer

View File

@@ -1 +1 @@
eyJhbGciOiJSUzI1NiJ9.eyJpZCI6IjAwMDAwMDAwLTAwMDAtNzAwMC0wMDAwLTAwMDAwMDAwMDAwMSIsInVzZXJuYW1lIjoiYWRtaW4iLCJyb2xlIjoiUk9MRV9BRE1JTiJ9.D6pK86XPWcdu1imV_y_4nAM6R4WEZvJpQ7oGaPAYe0_rg3UWdmVMa8Iw7L21bRgFoyIa7FQBwb_0AXojFVdb2mdOVDeGOwxQZAx23dwqeicOGd8yUMnuBaRSnd7z4P65KPMbbf0NOTQtho0Iv5mBAwFMJoF67sw-yntfx3cD_bfrI-Rf4oZaZsVn38Y2HJBe2sO2QI4e5_7s82ikxac416OX7PcIEgaf3IeEK1fSzSjRG_dyBGT_Jq_vAzVURsSu4ep976kI-k5ZXNE9EMxKu1S-n5c5eiaqo96ObnaSl4eWFik5q8vLhNLYIYO-bQi1xlJKnStwZqtUwlR763Gd5w eyJhbGciOiJSUzI1NiJ9.eyJpZCI6IjAwMDAwMDAwLTAwMDAtNzAwMC0wMDAwLTAwMDAwMDAwMDAwMSIsInVzZXJuYW1lIjoiYWRtaW4iLCJyb2xlIjoiQURNSU4ifQ.Z2mc__k9apWjJoZzJ3DWZuDiVN_jpisWtd0ecwrMnk1NrJ5Uw25pgrXPwn2aY0qYFAe0UGbE-4FhjUCxWLkknR0B-2_86IKHmN1A7z8lTqMRkK7qH-71uK0Y3o0kWKn117-FoSKDG-oefQE42NTwsSrzhiaEIzhUd0fsIyKuQCbDRol79rX5cU1HwOI8DHowpNEgvCLW1ogMWJDygq5GDgQI2HmV8vbnO1soFjKzvW3pz0sHWTimhAi76gl5mD_Lv_DdywcQWndwcGEoNj-SgHuKWktaG2_yzkoC9FQqWBgU7tukuycmLkbde_Oagydt2CAfPsBebu4Ac81UHGdUpw

View File

@@ -1 +1 @@
eyJhbGciOiJSUzI1NiJ9.eyJpZCI6IjAwMDAwMDAwLTAwMDAtNzAwMC0wMDAwLTAwMDAwMDAwMDAwMiIsInVzZXJuYW1lIjoidXNlciIsInJvbGUiOiJST0xFX1VTRVIifQ.GvZIq0VF9xB8UY3PUGdnc6JNeUXtv4LzHJ56hWSeqUS6BXH0M_QJ5Lu9ndh9_P85CECp3eKrW4fKymGYe-NUXCtrzhr9-SSZLF6D7GRzAJ4yZjVRCOa_dgqe1RGuIZyZpli36z4NPqeBFqtHJ3Cs5rAI-WdvxGfWPgtM2kzpSJ_0zFihp9mVcZBlWP57HlN7-oKzDJWVpO2E17fWZTy-y4pdrIUsff63c256Cy8NhiAgux9aqZTdzaqp9TsXw59bRsS5d0YH7-gJuBd4xctZwgy_41BOcRk2q-nLyLZgWJs1wmCa_zaW0Fj6fjAsYvpdPNegkpIqrHJcQpGd7nE0KQ eyJhbGciOiJSUzI1NiJ9.eyJpZCI6IjAwMDAwMDAwLTAwMDAtNzAwMC0wMDAwLTAwMDAwMDAwMDAwMiIsInVzZXJuYW1lIjoidXNlciIsInJvbGUiOiJVU0VSIn0.kpmQYxhkyKsIjo9mJaysBXW0xdv8UjlmNnVsYNfBu-Tdro_0nQFVzhCcjaD6_TUhx2-3vSkvTwDtmMHsP0JC5B43K473o2zQjyHYzCNakPcNHiste9llNj12n5qUCOUMgCKb7ZztLffSIsYlSL7hyRwwmTaz73MDMYvLWAa4AgSNm8JPe3HkTkqRJ4YZ-saKO9Q0Vb9LLftB7T3b9P5kHYqzwISBsRm1rYHRRpGYs5goR2Qax1hLJBbQR4bswaeTRfl3fQ66mIr6mZqiY279wCzzueLuGyJPFzeZQYiQ2JiYRq3H2NyXCsWKCt2bK-YNwol1K3fYLPSq9kap-AGasQ

View File

@@ -1,5 +1,5 @@
-- Test data -- Test data
insert into public.accounts (id, created_at, updated_at, enabled, username, password, roles) insert into public.accounts (id, created_at, updated_at, enabled, username, password, roles)
values ('00000000-0000-7000-0000-000000000001'::uuid, '2000-01-01 00:00:00.000001 +00:00', '2000-01-01 00:00:01.000001 +00:00', true, 'admin', '$2a$12$KoXBoLOANMK11J4xeJHPA.Sy0FG.m8KWk7P4XFsMO.ZbFmFI2DckK', 'ROLE_ADMIN'), values ('00000000-0000-7000-0000-000000000001'::uuid, '2000-01-01 00:00:00.000001 +00:00', '2000-01-01 00:00:01.000001 +00:00', true, 'admin', '$2a$12$KoXBoLOANMK11J4xeJHPA.Sy0FG.m8KWk7P4XFsMO.ZbFmFI2DckK', 'ADMIN'),
('00000000-0000-7000-0000-000000000002'::uuid, '2000-01-01 00:00:00.000001 +00:00', '2000-01-01 00:00:01.000001 +00:00', true, 'user', '$2a$12$KoXBoLOANMK11J4xeJHPA.Sy0FG.m8KWk7P4XFsMO.ZbFmFI2DckK', 'ROLE_USER'), ('00000000-0000-7000-0000-000000000002'::uuid, '2000-01-01 00:00:00.000001 +00:00', '2000-01-01 00:00:01.000001 +00:00', true, 'user', '$2a$12$KoXBoLOANMK11J4xeJHPA.Sy0FG.m8KWk7P4XFsMO.ZbFmFI2DckK', 'USER'),
('00000000-0000-7000-0000-000000000003'::uuid, '2000-01-01 00:00:00.000001 +00:00', '2000-01-01 00:00:01.000001 +00:00', false, 'disabled', '$2a$12$KoXBoLOANMK11J4xeJHPA.Sy0FG.m8KWk7P4XFsMO.ZbFmFI2DckK', 'ROLE_USER'); ('00000000-0000-7000-0000-000000000003'::uuid, '2000-01-01 00:00:00.000001 +00:00', '2000-01-01 00:00:01.000001 +00:00', false, 'disabled', '$2a$12$KoXBoLOANMK11J4xeJHPA.Sy0FG.m8KWk7P4XFsMO.ZbFmFI2DckK', 'USER');

View File

@@ -0,0 +1,109 @@
package ltd.hlaeja.util
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.ZonedDateTime
import java.util.UUID
import ltd.hlaeja.entity.AccountEntity
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.Assertions.assertThrows
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.web.server.ResponseStatusException
@ExtendWith(SoftAssertionsExtension::class)
class AccountUtilKtTest {
companion object {
val account = UUID.fromString("00000000-0000-0000-0000-000000000000")
val timestamp: ZonedDateTime = ZonedDateTime.of(LocalDateTime.of(2000, 1, 1, 0, 0, 0, 1), ZoneId.of("UTC"))
val originalUser = AccountEntity(
id = account,
createdAt = timestamp,
updatedAt = timestamp,
enabled = true,
username = "username",
password = "password",
roles = "ROLE_TEST",
)
}
@InjectSoftAssertions
lateinit var softly: SoftAssertions
@Test
fun `no change detected`() {
// given
val account = AccountEntity(
id = account,
createdAt = timestamp,
updatedAt = timestamp,
enabled = true,
username = "username",
password = "password",
roles = "ROLE_TEST",
)
// then exception
assertThrows(ResponseStatusException::class.java) {
account.detectChanges(originalUser)
}
}
@ParameterizedTest
@CsvSource(
"false, username, password, ROLE_TEST, enabled",
"true, change, password, ROLE_TEST, username",
"true, username, change, ROLE_TEST, password",
"true, username, password, ROLE_CHANGE, roles",
)
fun `change one thing`(
enabled: Boolean,
username: String,
password: String,
roles: String,
expected: String,
) {
// given
val account = AccountEntity(
id = account,
createdAt = timestamp,
updatedAt = timestamp,
enabled = enabled,
username = username,
password = password,
roles = roles,
)
// when
val result = account.detectChanges(originalUser)
// then
softly.assertThat(result).hasSize(1)
softly.assertThat(result[0]).isEqualTo(expected)
}
@Test
fun `change everything`() {
// given
val account = AccountEntity(
id = account,
createdAt = timestamp,
updatedAt = timestamp,
enabled = false,
username = "change",
password = "change",
roles = "ROLE_CHANGE",
)
// when
val result = account.detectChanges(originalUser)
// then
softly.assertThat(result).hasSize(4)
softly.assertThat(result).contains("password", "username", "enabled", "roles")
}
}