From a6061c4bab9210ff7c14790f3101026754f44d6a Mon Sep 17 00:00:00 2001
From: Bruno Pereira <bruno.henriquepj@gmail.com>
Date: Thu, 30 Jun 2022 00:15:50 -0300
Subject: [PATCH] update spring boot and java

---
 .github/workflows/ci.yml                      | 22 +++---
 .sdkmanrc                                     |  3 +-
 build.gradle.kts                              | 40 +++++-----
 docker-compose.yml                            | 12 +++
 gradle/wrapper/gradle-wrapper.properties      |  2 +-
 .../configuration/JsonConfiguration.kt        |  6 +-
 .../configuration/SecurityConfiguration.kt    | 75 ++++++++++---------
 .../configuration/SwaggerConfiguration.kt     | 39 +++++-----
 .../realworld/filter/AuthenticationFilter.kt  |  5 +-
 .../AuthenticationEntryPointHandler.kt        |  1 +
 .../resources/application-local.properties    | 12 ++-
 .../controller/UserControllerTest.kt          |  5 +-
 ...t.kt => UserControllerUnauthorizedTest.kt} | 11 +--
 .../controller/UsersControllerTest.kt         | 11 +--
 .../com/example/realworld/util/FakerPtBr.kt   |  4 +-
 .../resources/application-test.properties     |  4 +-
 16 files changed, 138 insertions(+), 114 deletions(-)
 create mode 100644 docker-compose.yml
 rename src/test/kotlin/com/example/realworld/integration/controller/{UserControllerForbiddenTest.kt => UserControllerUnauthorizedTest.kt} (84%)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 6ae9d95..ce1490b 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -19,15 +19,15 @@ jobs:
 
       - name: Validate Gradle Wrapper
         uses: gradle/wrapper-validation-action@v1.0.4
-  
+
   thundra:
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v3.0.2
-      - name: Set up JDK 11
+      - name: Set up JDK 17
         uses: actions/setup-java@v3
         with:
-          java-version: '11'
+          java-version: '17'
           distribution: 'adopt'
       - name: Thundra Gradle Test Instrumentation
         uses: thundra-io/thundra-gradle-test-action@v1.0.4
@@ -60,10 +60,10 @@ jobs:
           key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}
           restore-keys: ${{ runner.os }}-gradle-wrapper-
 
-      - name: Set up JDK 11
+      - name: Set up JDK 17
         uses: actions/setup-java@v3
         with:
-          java-version: '11'
+          java-version: '17'
           distribution: 'adopt'
 
       - name: Build with Gradle
@@ -100,10 +100,10 @@ jobs:
           key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}
           restore-keys: ${{ runner.os }}-gradle-wrapper-
 
-      - name: Set up JDK 11
+      - name: Set up JDK 17
         uses: actions/setup-java@v3
         with:
-          java-version: '11'
+          java-version: '17'
           distribution: 'adopt'
 
       - name: Test
@@ -145,10 +145,10 @@ jobs:
           key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}
           restore-keys: ${{ runner.os }}-gradle-wrapper-
 
-      - name: Set up JDK 11
+      - name: Set up JDK 17
         uses: actions/setup-java@v3
         with:
-          java-version: '11'
+          java-version: '17'
           distribution: 'adopt'
 
       - name: Test
@@ -224,10 +224,10 @@ jobs:
           key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}
           restore-keys: ${{ runner.os }}-gradle-wrapper-
 
-      - name: Set up JDK 11
+      - name: Set up JDK 17
         uses: actions/setup-java@v3
         with:
-          java-version: '11'
+          java-version: '17'
           distribution: 'adopt'
 
       - name: Download artifact
diff --git a/.sdkmanrc b/.sdkmanrc
index bf93182..e033b50 100644
--- a/.sdkmanrc
+++ b/.sdkmanrc
@@ -1 +1,2 @@
-java=11.0.11.hs-adpt
+java=17.0.2-open
+kotlin=1.6.21
diff --git a/build.gradle.kts b/build.gradle.kts
index 23a3958..aff01ca 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,19 +1,19 @@
 import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 plugins {
-    id("org.springframework.boot") version "2.6.4"
+    id("org.springframework.boot") version "2.7.1"
     id("io.spring.dependency-management") version "1.0.11.RELEASE"
-    kotlin("jvm") version "1.6.10"
-    kotlin("plugin.spring") version "1.6.10"
-    kotlin("plugin.jpa") version "1.6.10"
+    kotlin("jvm") version "1.6.21"
+    kotlin("plugin.spring") version "1.6.21"
+    kotlin("plugin.jpa") version "1.6.21"
     jacoco
-    id("com.adarshr.test-logger") version "3.1.0"
+    id("com.adarshr.test-logger") version "3.2.0"
 }
 
 
 group = "com.example"
 version = "0.0.0"
-java.sourceCompatibility = JavaVersion.VERSION_11
+java.sourceCompatibility = JavaVersion.VERSION_17
 
 repositories {
     mavenCentral()
@@ -27,26 +27,26 @@ dependencies {
     implementation("org.springframework.boot:spring-boot-starter-actuator")
     implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
     implementation("org.jetbrains.kotlin:kotlin-reflect")
-    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
-    implementation("io.springfox:springfox-boot-starter:3.0.0")
+    implementation("org.jetbrains.kotlin:kotlin-stdlib")
+    implementation("org.springdoc:springdoc-openapi-ui:1.6.9")
+    implementation("org.springdoc:springdoc-openapi-kotlin:1.6.9")
     implementation("io.jsonwebtoken:jjwt:0.9.1")
+    runtimeOnly("mysql:mysql-connector-java")
     developmentOnly("org.springframework.boot:spring-boot-devtools")
-    runtimeOnly("com.h2database:h2")
     testImplementation("org.springframework.boot:spring-boot-starter-test")
-    testImplementation("io.mockk:mockk:1.12.2")
-    testImplementation("com.github.javafaker:javafaker:1.0.2")
-    testImplementation("io.kotest:kotest-assertions-core:4.6.3")
-    testImplementation("org.testcontainers:testcontainers:1.16.2")
-    testImplementation(platform("org.testcontainers:testcontainers-bom:1.16.2"))
-    testImplementation("org.testcontainers:junit-jupiter:1.16.2")
+    testImplementation("io.mockk:mockk:1.12.4")
+    testImplementation("net.datafaker:datafaker:1.4.0")
+    testImplementation("io.kotest:kotest-assertions-core:5.3.1")
+    testImplementation(platform("org.testcontainers:testcontainers-bom:1.17.2"))
+    testImplementation("org.testcontainers:testcontainers")
+    testImplementation("org.testcontainers:junit-jupiter")
     testImplementation("org.testcontainers:mysql")
-    testRuntimeOnly("mysql:mysql-connector-java")
 }
 
 tasks.withType<KotlinCompile> {
     kotlinOptions {
         freeCompilerArgs = listOf("-Xjsr305=strict")
-        jvmTarget = "11"
+        jvmTarget = "17"
     }
 }
 
@@ -71,9 +71,9 @@ tasks.jacocoTestReport {
 
 tasks.jacocoTestReport {
     reports {
-        csv.isEnabled = false
-        xml.isEnabled = true
-        html.isEnabled = true
+        csv.required.set(false)
+        xml.required.set(true)
+        html.required.set(true)
     }
 }
 
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..0da9df6
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,12 @@
+version: "3.7"
+
+services:
+  mysql:
+    image: mysql
+    command: --default-authentication-plugin=mysql_native_password
+    restart: always
+    environment:
+      MYSQL_ROOT_PASSWORD: root
+      MYSQL_DATABASE: spring_boot_kotlin_realworld
+    ports:
+      - "3307:3306"
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 0f80bbf..ffed3a2 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
diff --git a/src/main/kotlin/com/example/realworld/configuration/JsonConfiguration.kt b/src/main/kotlin/com/example/realworld/configuration/JsonConfiguration.kt
index 34663be..3dcb698 100644
--- a/src/main/kotlin/com/example/realworld/configuration/JsonConfiguration.kt
+++ b/src/main/kotlin/com/example/realworld/configuration/JsonConfiguration.kt
@@ -1,7 +1,7 @@
 package com.example.realworld.configuration
 
 import com.fasterxml.jackson.databind.ObjectMapper
-import com.fasterxml.jackson.databind.PropertyNamingStrategy
+import com.fasterxml.jackson.databind.PropertyNamingStrategies
 import org.springframework.context.annotation.Bean
 import org.springframework.context.annotation.Configuration
 
@@ -10,7 +10,7 @@ class JsonConfiguration {
     @Bean
     fun objectMapper(): ObjectMapper {
         return ObjectMapper().apply {
-            propertyNamingStrategy = PropertyNamingStrategy.LOWER_CASE
+            propertyNamingStrategy =  PropertyNamingStrategies.LOWER_CASE
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/main/kotlin/com/example/realworld/configuration/SecurityConfiguration.kt b/src/main/kotlin/com/example/realworld/configuration/SecurityConfiguration.kt
index 718d5f5..b204d3c 100644
--- a/src/main/kotlin/com/example/realworld/configuration/SecurityConfiguration.kt
+++ b/src/main/kotlin/com/example/realworld/configuration/SecurityConfiguration.kt
@@ -1,69 +1,76 @@
 package com.example.realworld.configuration
 
-import com.example.realworld.filter.AuthorizationFilter
+import com.example.realworld.filter.AuthenticationFilter
 import com.example.realworld.handler.AuthenticationEntryPointHandler
 import com.example.realworld.repository.UserRepository
 import com.example.realworld.service.SecurityContextService
-import com.example.realworld.service.UserDetailsService
 import com.example.realworld.util.TokenUtil
 import org.springframework.context.annotation.Bean
 import org.springframework.context.annotation.Configuration
 import org.springframework.http.HttpMethod
 import org.springframework.security.authentication.AuthenticationManager
-import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
+import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration
 import org.springframework.security.config.annotation.web.builders.HttpSecurity
 import org.springframework.security.config.annotation.web.builders.WebSecurity
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
-import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer
 import org.springframework.security.config.http.SessionCreationPolicy
 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
 import org.springframework.security.crypto.password.PasswordEncoder
+import org.springframework.security.web.SecurityFilterChain
+
 
 @EnableWebSecurity
 @Configuration
 class SecurityConfiguration(
-    private val userDetailsService: UserDetailsService,
     private val userRepository: UserRepository,
     private val tokenUtil: TokenUtil,
     private val securityContextService: SecurityContextService
-) : WebSecurityConfigurerAdapter() {
+) {
     @Bean
     fun passwordEncoder(): PasswordEncoder {
         return BCryptPasswordEncoder()
     }
 
     @Bean
-    override fun authenticationManager(): AuthenticationManager {
-        return super.authenticationManager()
-    }
-
-    override fun configure(authenticationManager: AuthenticationManagerBuilder) {
-        authenticationManager.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder())
+    fun authenticationManager(authenticationConfiguration: AuthenticationConfiguration): AuthenticationManager {
+        return authenticationConfiguration.authenticationManager
     }
 
-    override fun configure(http: HttpSecurity) {
-        http.authorizeRequests()
-            .antMatchers(HttpMethod.GET, "/").permitAll()
-            .antMatchers(HttpMethod.GET, "/actuator/**").permitAll()
-            .antMatchers(HttpMethod.POST, "/api/users").permitAll()
-            .antMatchers(HttpMethod.POST, "/api/users/login").permitAll()
-            .anyRequest().authenticated()
-            .and()
-            .csrf().disable()
-            .exceptionHandling().authenticationEntryPoint(AuthenticationEntryPointHandler())
-            .and()
-            .addFilter(AuthorizationFilter(authenticationManager(), tokenUtil, userRepository, securityContextService))
-            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
-            .and()
-            .cors()
+    @Bean
+    fun filterChain(http: HttpSecurity, authenticationManager: AuthenticationManager): SecurityFilterChain {
+        return http
+            .authorizeHttpRequests {
+                it.antMatchers(HttpMethod.GET, "/").permitAll()
+                    .antMatchers(HttpMethod.GET, "/actuator/**").permitAll()
+                    .antMatchers(HttpMethod.POST, "/api/users").permitAll()
+                    .antMatchers(HttpMethod.POST, "/api/users/login").permitAll()
+                    .anyRequest().authenticated()
+            }
+            .csrf { it.disable() }
+            .exceptionHandling { it.authenticationEntryPoint(AuthenticationEntryPointHandler()) }
+            .addFilter(
+                AuthenticationFilter(
+                    authenticationManager,
+                    tokenUtil,
+                    userRepository,
+                    securityContextService
+                )
+            )
+            .sessionManagement { it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) }
+            .cors {}
+            .build()
     }
 
-    override fun configure(web: WebSecurity) {
-        web.ignoring().antMatchers(
-            "/v2/api-docs",
-            "/swagger-resources/**",
-            "/swagger-ui/**",
-            "/h2-console/**"
-        )
+    @Bean
+    fun webSecurityCustomizer(): WebSecurityCustomizer {
+        return WebSecurityCustomizer { web: WebSecurity ->
+            web.ignoring().antMatchers(
+                "/v3/api-docs/**",
+                "/swagger-ui/**",
+                "/swagger-ui.html",
+                "/h2-console/**"
+            )
+        }
     }
 }
diff --git a/src/main/kotlin/com/example/realworld/configuration/SwaggerConfiguration.kt b/src/main/kotlin/com/example/realworld/configuration/SwaggerConfiguration.kt
index d960ea6..f18c281 100644
--- a/src/main/kotlin/com/example/realworld/configuration/SwaggerConfiguration.kt
+++ b/src/main/kotlin/com/example/realworld/configuration/SwaggerConfiguration.kt
@@ -1,31 +1,36 @@
 package com.example.realworld.configuration
 
+import io.swagger.v3.oas.models.ExternalDocumentation
+import io.swagger.v3.oas.models.OpenAPI
+import io.swagger.v3.oas.models.info.Info
+import io.swagger.v3.oas.models.info.License
 import org.springframework.context.annotation.Bean
 import org.springframework.context.annotation.Configuration
-import springfox.documentation.builders.ApiInfoBuilder
-import springfox.documentation.builders.PathSelectors
-import springfox.documentation.builders.RequestHandlerSelectors
-import springfox.documentation.service.ApiInfo
-import springfox.documentation.spi.DocumentationType
-import springfox.documentation.spring.web.plugins.Docket
 
 @Configuration
 class SwaggerConfiguration {
     @Bean
-    fun api(): Docket {
-        return Docket(DocumentationType.SWAGGER_2)
-            .apiInfo(getApiInfo())
-            .select()
-            .apis(RequestHandlerSelectors.any())
-            .paths(PathSelectors.any())
-            .build()
+    fun api(): OpenAPI {
+        return OpenAPI()
+            .info(getApiInfo())
+            .externalDocs(getExternalDocumentation())
     }
 
-    private fun getApiInfo(): ApiInfo {
-        return ApiInfoBuilder()
+    private fun getExternalDocumentation(): ExternalDocumentation {
+        return ExternalDocumentation()
+            .description("RealWorld implementation using Spring Boot with Kotlin")
+            .url("https://github.com/brunohenriquepj/spring-boot-kotlin-realworld")
+    }
+
+    private fun getApiInfo(): Info {
+        return Info()
             .title("RealWorld API Doc")
             .description("Real World API")
+            .license(
+                License()
+                    .name("MIT License")
+                    .url("https://github.com/brunohenriquepj/spring-boot-kotlin-realworld/blob/main/LICENSE")
+            )
             .version("0.0.0")
-            .build()
     }
-}
\ No newline at end of file
+}
diff --git a/src/main/kotlin/com/example/realworld/filter/AuthenticationFilter.kt b/src/main/kotlin/com/example/realworld/filter/AuthenticationFilter.kt
index 7e333fe..42ee627 100644
--- a/src/main/kotlin/com/example/realworld/filter/AuthenticationFilter.kt
+++ b/src/main/kotlin/com/example/realworld/filter/AuthenticationFilter.kt
@@ -11,7 +11,8 @@ import javax.servlet.FilterChain
 import javax.servlet.http.HttpServletRequest
 import javax.servlet.http.HttpServletResponse
 
-class AuthorizationFilter(
+
+class AuthenticationFilter(
     authenticationManager: AuthenticationManager,
     private val tokenUtil: TokenUtil,
     private val userRepository: UserRepository,
@@ -60,4 +61,4 @@ class AuthorizationFilter(
         }
         return header.substring(tokenPrefix.length)
     }
-}
\ No newline at end of file
+}
diff --git a/src/main/kotlin/com/example/realworld/handler/AuthenticationEntryPointHandler.kt b/src/main/kotlin/com/example/realworld/handler/AuthenticationEntryPointHandler.kt
index 0eb56fc..5bd1ed6 100644
--- a/src/main/kotlin/com/example/realworld/handler/AuthenticationEntryPointHandler.kt
+++ b/src/main/kotlin/com/example/realworld/handler/AuthenticationEntryPointHandler.kt
@@ -9,6 +9,7 @@ import org.springframework.security.web.AuthenticationEntryPoint
 import javax.servlet.http.HttpServletRequest
 import javax.servlet.http.HttpServletResponse
 
+
 class AuthenticationEntryPointHandler : AuthenticationEntryPoint {
     override fun commence(
         request: HttpServletRequest,
diff --git a/src/main/resources/application-local.properties b/src/main/resources/application-local.properties
index d002268..f7fd573 100644
--- a/src/main/resources/application-local.properties
+++ b/src/main/resources/application-local.properties
@@ -17,10 +17,11 @@ spring.jpa.properties.hibernate.show_sql=true
 spring.jpa.properties.hibernate.format_sql=true
 
 # data source
-spring.datasource.driverClassName=org.h2.Driver
-spring.datasource.url=jdbc:h2:mem:realworld-api
-spring.datasource.username=sa
-spring.datasource.password=
+# https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-api-changes.html
+spring.datasource.driver-class-namee=com.mysql.cj.jdbc.Driver
+spring.datasource.url=jdbc:mysql://${MYSQL_HOST:localhost}:3307/spring_boot_kotlin_realworld
+spring.datasource.username=root
+spring.datasource.password=root
 
 # h2
 spring.h2.console.enabled=true
@@ -29,3 +30,6 @@ spring.h2.console.path=/h2-console
 # JWT
 jwt.secret=qXvQkqHzXBe^D0Sz#BTJhY7YHZiAIOSCGJRFWSCxG$hZE!Y!kX@fBIuzYc4FDGN%4^PPHzzgMXoW4sdg&$9JOjF*pgH%S*S8Yo*@
 jwt.expiration-milliseconds=600000
+
+# open api
+springdoc.show-actuator=true
diff --git a/src/test/kotlin/com/example/realworld/integration/controller/UserControllerTest.kt b/src/test/kotlin/com/example/realworld/integration/controller/UserControllerTest.kt
index e1260e3..eb4c256 100644
--- a/src/test/kotlin/com/example/realworld/integration/controller/UserControllerTest.kt
+++ b/src/test/kotlin/com/example/realworld/integration/controller/UserControllerTest.kt
@@ -13,8 +13,8 @@ import com.example.realworld.util.builder.user.UserBuilder
 import com.example.realworld.util.extension.getForEntity
 import com.example.realworld.util.extension.putForEntity
 import io.kotest.assertions.asClue
+import io.kotest.matchers.equality.FieldsEqualityCheckConfig
 import io.kotest.matchers.equality.shouldBeEqualToComparingFields
-import io.kotest.matchers.equality.shouldBeEqualToComparingFieldsExcept
 import io.kotest.matchers.shouldBe
 import io.kotest.matchers.shouldNot
 import io.kotest.matchers.shouldNotBe
@@ -85,7 +85,8 @@ class UserControllerTest {
 
         // assert
         response.statusCode shouldBe HttpStatus.OK
-        actualUser.shouldBeEqualToComparingFieldsExcept(expected, UpdateUserResponseData::token)
+        actualUser.shouldBeEqualToComparingFields(expected,
+            FieldsEqualityCheckConfig(propertiesToExclude = listOf(UpdateUserResponseData::token)))
         actualUser.token.asClue {
             it shouldNot beEmpty()
             it shouldNotBe token
diff --git a/src/test/kotlin/com/example/realworld/integration/controller/UserControllerForbiddenTest.kt b/src/test/kotlin/com/example/realworld/integration/controller/UserControllerUnauthorizedTest.kt
similarity index 84%
rename from src/test/kotlin/com/example/realworld/integration/controller/UserControllerForbiddenTest.kt
rename to src/test/kotlin/com/example/realworld/integration/controller/UserControllerUnauthorizedTest.kt
index de62264..90caa19 100644
--- a/src/test/kotlin/com/example/realworld/integration/controller/UserControllerForbiddenTest.kt
+++ b/src/test/kotlin/com/example/realworld/integration/controller/UserControllerUnauthorizedTest.kt
@@ -5,7 +5,6 @@ import com.example.realworld.repository.UserRepository
 import com.example.realworld.service.UserService
 import com.example.realworld.util.annotation.WebMvcIntegrationTest
 import com.example.realworld.util.extension.authorizationHeader
-import com.example.realworld.util.extension.shouldBeEqualJson
 import io.kotest.matchers.shouldBe
 import org.junit.jupiter.api.Test
 import org.junit.jupiter.params.ParameterizedTest
@@ -18,7 +17,7 @@ import org.springframework.test.web.servlet.MockMvc
 import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
 
 @WebMvcIntegrationTest(controllers = [UserControllerTest::class])
-class UserControllerForbiddenTest {
+class UserControllerUnauthorizedTest {
     @Autowired
     lateinit var mockMvc: MockMvc
 
@@ -49,9 +48,7 @@ class UserControllerForbiddenTest {
         val response = mockMvc.perform(request).andReturn().response
 
         // assert
-        response.status shouldBe HttpStatus.FORBIDDEN.value()
-        response.contentType shouldBe MediaType.APPLICATION_JSON_VALUE
-        response.contentAsString shouldBeEqualJson expected
+        response.status shouldBe HttpStatus.UNAUTHORIZED.value()
     }
 
     @Test
@@ -67,8 +64,6 @@ class UserControllerForbiddenTest {
         val response = mockMvc.perform(request).andReturn().response
 
         // assert
-        response.status shouldBe HttpStatus.FORBIDDEN.value()
-        response.contentType shouldBe MediaType.APPLICATION_JSON_VALUE
-        response.contentAsString shouldBeEqualJson expected
+        response.status shouldBe HttpStatus.UNAUTHORIZED.value()
     }
 }
diff --git a/src/test/kotlin/com/example/realworld/integration/controller/UsersControllerTest.kt b/src/test/kotlin/com/example/realworld/integration/controller/UsersControllerTest.kt
index c2af15b..901d73a 100644
--- a/src/test/kotlin/com/example/realworld/integration/controller/UsersControllerTest.kt
+++ b/src/test/kotlin/com/example/realworld/integration/controller/UsersControllerTest.kt
@@ -5,21 +5,16 @@ import com.example.realworld.dto.user.response.CreateUserResponse
 import com.example.realworld.dto.user.response.CreateUserResponseData
 import com.example.realworld.util.annotation.SpringBootIntegrationTest
 import com.example.realworld.util.builder.user.CreateUserRequestDataBuilder
-import io.kotest.matchers.equality.shouldBeEqualToComparingFieldsExcept
+import io.kotest.matchers.equality.FieldsEqualityCheckConfig
+import io.kotest.matchers.equality.shouldBeEqualToComparingFields
 import io.kotest.matchers.shouldBe
 import io.kotest.matchers.shouldNot
 import io.kotest.matchers.string.beEmpty
 import org.junit.jupiter.api.Test
 import org.springframework.beans.factory.annotation.Autowired
-import org.springframework.boot.test.context.SpringBootTest
-import org.springframework.boot.test.context.TestConfiguration
 import org.springframework.boot.test.web.client.TestRestTemplate
 import org.springframework.boot.test.web.client.postForEntity
-import org.springframework.context.annotation.Bean
-import org.springframework.context.annotation.Import
-import org.springframework.core.annotation.Order
 import org.springframework.http.HttpStatus
-import org.springframework.web.context.request.RequestContextListener
 
 
 @SpringBootIntegrationTest
@@ -48,7 +43,7 @@ class UsersControllerTest {
 
         // assert
         response.statusCode shouldBe HttpStatus.CREATED
-        actualUser.shouldBeEqualToComparingFieldsExcept(expected, CreateUserResponseData::token)
+        actualUser.shouldBeEqualToComparingFields(expected, FieldsEqualityCheckConfig(propertiesToExclude = listOf(CreateUserResponseData::token)))
         actualUser.token.trim() shouldNot beEmpty()
     }
 }
diff --git a/src/test/kotlin/com/example/realworld/util/FakerPtBr.kt b/src/test/kotlin/com/example/realworld/util/FakerPtBr.kt
index c139a4f..3b343f7 100644
--- a/src/test/kotlin/com/example/realworld/util/FakerPtBr.kt
+++ b/src/test/kotlin/com/example/realworld/util/FakerPtBr.kt
@@ -1,7 +1,7 @@
 package com.example.realworld.util
 
-import com.github.javafaker.Faker
-import java.util.*
+import net.datafaker.Faker
+import java.util.Locale
 
 class FakerPtBr {
     companion object {
diff --git a/src/test/resources/application-test.properties b/src/test/resources/application-test.properties
index 8a216d8..2d12b56 100644
--- a/src/test/resources/application-test.properties
+++ b/src/test/resources/application-test.properties
@@ -5,12 +5,14 @@ spring.jpa.properties.hibernate.show_sql=true
 spring.jpa.properties.hibernate.format_sql=true
 
 # data source
-# https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-api-changes.html
 spring.datasource.driver-class-name=org.testcontainers.jdbc.ContainerDatabaseDriver
 spring.datasource.url=jdbc:tc:mysql:8:///testDb?TC_REUSABLE=true&TC_DAEMON=true
 spring.datasource.username=test
 spring.datasource.password=test
 
+# Security
+spring.security.strategy=MODE_INHERITABLETHREADLOCAL
+
 # JWT
 jwt.secret=6GOvb%A6NgXeSHW8niU82psGTTdB3GGkOKVe%5jMwwwB!seNUC
 jwt.expiration-milliseconds=60000