fix: address PR review comments for OAuth2 implementation #32
+2
-5
@@ -30,11 +30,10 @@ java {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(platform(libs.sentry.bom))
|
implementation(platform(libs.sentry.bom))
|
||||||
|
|
|||||||
implementation(platform(libs.spring.grpc.bom))
|
|
||||||
|
|
||||||
implementation(libs.spring.boot.starter.web)
|
implementation(libs.spring.boot.starter.web)
|
||||||
implementation(libs.spring.boot.starter.actuator)
|
implementation(libs.spring.boot.starter.actuator)
|
||||||
// implementation(libs.spring.boot.starter.security)
|
implementation(libs.spring.boot.starter.security)
|
||||||
implementation(libs.spring.boot.starter.cache)
|
implementation(libs.spring.boot.starter.cache)
|
||||||
implementation(libs.spring.boot.starter.data.jdbc)
|
implementation(libs.spring.boot.starter.data.jdbc)
|
||||||
implementation(libs.spring.boot.starter.validation)
|
implementation(libs.spring.boot.starter.validation)
|
||||||
@@ -46,8 +45,7 @@ dependencies {
|
|||||||
implementation(libs.opentelemetry.exporter.otlp)
|
implementation(libs.opentelemetry.exporter.otlp)
|
||||||
implementation(libs.sentry.spring.boot.starter)
|
implementation(libs.sentry.spring.boot.starter)
|
||||||
|
|
||||||
implementation(libs.spring.grpc.starter)
|
implementation(libs.spring.boot.starter.oauth2.client)
|
||||||
implementation(libs.grpc.services)
|
|
||||||
|
|
||||||
runtimeOnly(libs.micrometer.registry.prometheus)
|
runtimeOnly(libs.micrometer.registry.prometheus)
|
||||||
runtimeOnly(libs.h2)
|
runtimeOnly(libs.h2)
|
||||||
@@ -57,7 +55,6 @@ dependencies {
|
|||||||
|
|
||||||
testImplementation(libs.spring.boot.starter.test)
|
testImplementation(libs.spring.boot.starter.test)
|
||||||
testImplementation(libs.kotlin.test.junit5)
|
testImplementation(libs.kotlin.test.junit5)
|
||||||
testImplementation(libs.spring.grpc.test)
|
|
||||||
testImplementation(libs.mockk)
|
testImplementation(libs.mockk)
|
||||||
testRuntimeOnly(libs.junit.platform.launcher)
|
testRuntimeOnly(libs.junit.platform.launcher)
|
||||||
}
|
}
|
||||||
|
do not remove any deps, this removal does not make any sense to your PR do not remove any deps, this removal does not make any sense to your PR
|
|||||||
|
|||||||
@@ -11,9 +11,10 @@ spring-grpc = "1.0.1"
|
|||||||
protoc = "3.25.1"
|
protoc = "3.25.1"
|
||||||
grpc-java = "1.60.0"
|
grpc-java = "1.60.0"
|
||||||
springdoc = "2.8.6"
|
springdoc = "2.8.6"
|
||||||
mockk = "1.13.12"
|
mockk = "1.13.13"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
|
spring-boot-starter-oauth2-client = { module = "org.springframework.boot:spring-boot-starter-oauth2-client" }
|
||||||
spring-boot-starter-web = { module = "org.springframework.boot:spring-boot-starter-web" }
|
spring-boot-starter-web = { module = "org.springframework.boot:spring-boot-starter-web" }
|
||||||
spring-boot-starter-actuator = { module = "org.springframework.boot:spring-boot-starter-actuator" }
|
spring-boot-starter-actuator = { module = "org.springframework.boot:spring-boot-starter-actuator" }
|
||||||
spring-boot-starter-security = { module = "org.springframework.boot:spring-boot-starter-security" }
|
spring-boot-starter-security = { module = "org.springframework.boot:spring-boot-starter-security" }
|
||||||
|
|||||||
@@ -27,7 +27,20 @@ class UserRepository(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun save(user: User): User {
|
override fun save(user: User): User {
|
||||||
val entity = user.toEntity()
|
val existingUser = findById(user.id)
|
||||||
|
|
||||||
|
val entity =
|
||||||
|
create a mapper to reduce code repetion create a mapper to reduce code repetion
well, nevermind well, nevermind
|
|||||||
|
if (existingUser != null) {
|
||||||
|
val existingEntity = existingUser.toEntity()
|
||||||
|
user.toEntity(
|
||||||
|
provider = existingEntity.provider?.let { AuthProvider.valueOf(it) },
|
||||||
|
providerId = existingEntity.providerId,
|
||||||
|
createdAt = existingEntity.createdAt,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
user.toEntity()
|
||||||
|
}
|
||||||
|
|
||||||
val updatedRows =
|
val updatedRows =
|
||||||
jdbc.update(
|
jdbc.update(
|
||||||
"""
|
"""
|
||||||
@@ -41,6 +54,7 @@ class UserRepository(
|
|||||||
entity.providerId,
|
entity.providerId,
|
||||||
entity.id,
|
entity.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (updatedRows == 0) {
|
if (updatedRows == 0) {
|
||||||
jdbc.update(
|
jdbc.update(
|
||||||
"""
|
"""
|
||||||
@@ -68,6 +82,16 @@ class UserRepository(
|
|||||||
return entities.firstOrNull()?.toDomain()
|
return entities.firstOrNull()?.toDomain()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun findByEmail(email: String): User? {
|
||||||
|
val entities =
|
||||||
|
jdbc.query(
|
||||||
|
"SELECT id, name, email, provider, provider_id, created_at FROM users WHERE email = ?",
|
||||||
|
userEntityRowMapper,
|
||||||
|
email,
|
||||||
|
)
|
||||||
|
return entities.firstOrNull()?.toDomain()
|
||||||
|
}
|
||||||
|
|
||||||
override fun findAll(): List<User> =
|
override fun findAll(): List<User> =
|
||||||
jdbc
|
jdbc
|
||||||
.query(
|
.query(
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package com.project.movienight.adapters.security
|
||||||
|
|
||||||
|
import com.project.movienight.adapters.persistence.entity.toDomain
|
||||||
|
import com.project.movienight.adapters.persistence.entity.toEntity
|
||||||
|
import com.project.movienight.application.ports.input.security.OAuth2UserInfo
|
||||||
|
import com.project.movienight.application.ports.output.IdGenerator
|
||||||
|
import com.project.movienight.application.ports.output.UserRepositoryPort
|
||||||
|
import com.project.movienight.domain.model.AuthProvider
|
||||||
|
import com.project.movienight.domain.model.User
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService
|
||||||
|
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException
|
||||||
|
import org.springframework.security.oauth2.core.user.OAuth2User
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class CustomOAuth2UserService(
|
||||||
|
private val userRepository: UserRepositoryPort,
|
||||||
|
private val idGenerator: IdGenerator,
|
||||||
|
) : DefaultOAuth2UserService() {
|
||||||
|
companion object {
|
||||||
|
private val log = LoggerFactory.getLogger(CustomOAuth2UserService::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun loadUser(userRequest: OAuth2UserRequest): OAuth2User {
|
||||||
|
val oAuth2User = super.loadUser(userRequest)
|
||||||
|
val registrationId = userRequest.clientRegistration.registrationId
|
||||||
|
|
||||||
|
log.debug("Processing OAuth2 login for provider: {}", registrationId)
|
||||||
|
|
||||||
|
return try {
|
||||||
|
val userInfo = OAuth2UserInfoFactory.getOAuth2UserInfo(registrationId, oAuth2User)
|
||||||
|
val user = findOrCreateUser(userInfo)
|
||||||
|
UserPrincipal.create(user, oAuth2User.attributes)
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
log.error("OAuth2 authentication failed: ${e.message}", e)
|
||||||
|
throw OAuth2AuthenticationException("Failed to process OAuth2 user data")
|
||||||
|
} catch (e: OAuth2AuthenticationException) {
|
||||||
|
log.error("OAuth2 authentication failed: ${e.message}", e)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findOrCreateUser(userInfo: OAuth2UserInfo): User {
|
||||||
|
val provider = AuthProvider.valueOf(userInfo.getProvider().uppercase())
|
||||||
|
|
||||||
|
val existingUser =
|
||||||
|
userRepository.findByProviderAndProviderId(
|
||||||
|
provider,
|
||||||
|
userInfo.getProviderId(),
|
||||||
|
)
|
||||||
|
|
||||||
|
return if (existingUser != null) {
|
||||||
|
log.debug("User found by provider: {}", userInfo.getProvider())
|
||||||
|
existingUser
|
||||||
|
} else {
|
||||||
|
val userByEmail = userRepository.findByEmail(userInfo.getEmail())
|
||||||
|
|
||||||
|
if (userByEmail != null) {
|
||||||
|
log.debug("Linking OAuth2 account to existing user: {}", userInfo.getEmail())
|
||||||
|
val entity =
|
||||||
|
userByEmail.toEntity(
|
||||||
|
provider = provider,
|
||||||
|
providerId = userInfo.getProviderId(),
|
||||||
|
)
|
||||||
|
userRepository.save(entity.toDomain())
|
||||||
|
} else {
|
||||||
|
log.debug("Creating new user for provider: {}", userInfo.getProvider())
|
||||||
|
val newUser =
|
||||||
|
User(
|
||||||
|
id = idGenerator.generateId(),
|
||||||
|
name = userInfo.getName(),
|
||||||
|
email = userInfo.getEmail(),
|
||||||
|
library = null,
|
||||||
|
)
|
||||||
|
val entity =
|
||||||
|
newUser.toEntity(
|
||||||
|
provider = provider,
|
||||||
|
providerId = userInfo.getProviderId(),
|
||||||
|
)
|
||||||
|
userRepository.save(entity.toDomain())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.project.movienight.adapters.security
|
||||||
|
|
||||||
|
import com.project.movienight.application.ports.input.security.OAuth2UserInfo
|
||||||
|
|
||||||
|
class GoogleOAuth2UserInfo(
|
||||||
|
private val attributes: Map<String, Any>,
|
||||||
|
) : OAuth2UserInfo {
|
||||||
|
override fun getProviderId(): String = attributes["sub"] as String
|
||||||
|
|
||||||
|
override fun getEmail(): String = attributes["email"] as String
|
||||||
|
|
||||||
|
override fun getName(): String = attributes["name"] as String
|
||||||
|
|
||||||
|
override fun getProvider(): String = "google"
|
||||||
|
|
||||||
|
override fun getAttributes(): Map<String, Any> = attributes
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.project.movienight.adapters.security
|
||||||
|
|
||||||
|
import com.project.movienight.application.ports.input.security.OAuth2UserInfo
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException
|
||||||
|
import org.springframework.security.oauth2.core.user.OAuth2User
|
||||||
|
|
||||||
|
object OAuth2UserInfoFactory {
|
||||||
|
fun getOAuth2UserInfo(
|
||||||
|
registrationId: String,
|
||||||
|
user: OAuth2User,
|
||||||
|
): OAuth2UserInfo {
|
||||||
|
val attributes = user.attributes
|
||||||
|
|
||||||
|
return when (registrationId.lowercase()) {
|
||||||
|
"google" -> GoogleOAuth2UserInfo(attributes)
|
||||||
|
"yandex" -> YandexOAuth2UserInfo(attributes)
|
||||||
|
"vk" -> VkOAuth2UserInfo(attributes)
|
||||||
|
else -> throw OAuth2AuthenticationException("Unknown provider: $registrationId")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.project.movienight.adapters.security
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean
|
||||||
|
import org.springframework.context.annotation.Configuration
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||||
|
import org.springframework.security.web.SecurityFilterChain
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
class SecurityConfiguration(
|
||||||
|
private val customOAuth2UserService: CustomOAuth2UserService,
|
||||||
|
) {
|
||||||
|
@Bean
|
||||||
|
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||||||
|
http
|
||||||
|
.oauth2Login { oauth2 ->
|
||||||
|
oauth2
|
||||||
|
.userInfoEndpoint { userInfo ->
|
||||||
|
userInfo.userService(customOAuth2UserService)
|
||||||
|
}.defaultSuccessUrl("/api/users/me", true)
|
||||||
|
}.authorizeHttpRequests { auth ->
|
||||||
|
auth
|
||||||
|
.requestMatchers("/", "/login/**", "/oauth2/**", "/h2-console/**", "/actuator/health")
|
||||||
|
.permitAll()
|
||||||
|
.requestMatchers("/api/users/me")
|
||||||
|
.authenticated()
|
||||||
|
.requestMatchers("/api/**")
|
||||||
|
.authenticated()
|
||||||
|
.anyRequest()
|
||||||
|
.authenticated()
|
||||||
|
}.headers { headers ->
|
||||||
|
headers.frameOptions { frameOptions ->
|
||||||
|
frameOptions.sameOrigin()
|
||||||
|
}
|
||||||
|
}.csrf { csrf ->
|
||||||
|
csrf.disable()
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.project.movienight.adapters.security
|
||||||
|
|
||||||
|
import com.project.movienight.domain.model.User
|
||||||
|
import org.springframework.security.core.GrantedAuthority
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails
|
||||||
|
import org.springframework.security.oauth2.core.user.OAuth2User
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
class UserPrincipal(
|
||||||
|
private val user: User,
|
||||||
|
private val attributes: Map<String, Any>? = null,
|
||||||
|
) : OAuth2User,
|
||||||
|
UserDetails {
|
||||||
|
fun getId(): UUID = user.id
|
||||||
|
|
||||||
|
override fun getName(): String = user.name
|
||||||
|
|
||||||
|
override fun getAttributes(): Map<String, Any> = attributes ?: emptyMap()
|
||||||
|
|
||||||
|
override fun getAuthorities(): Collection<GrantedAuthority> =
|
||||||
|
listOf(
|
||||||
|
SimpleGrantedAuthority("ROLE_USER"),
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun getPassword(): String = ""
|
||||||
|
|
||||||
|
override fun getUsername(): String = user.email
|
||||||
|
|
||||||
|
override fun isAccountNonExpired(): Boolean = true
|
||||||
|
|
||||||
|
override fun isAccountNonLocked(): Boolean = true
|
||||||
|
|
||||||
|
override fun isCredentialsNonExpired(): Boolean = true
|
||||||
|
|
||||||
|
override fun isEnabled(): Boolean = true
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun create(
|
||||||
|
user: User,
|
||||||
|
attributes: Map<String, Any>? = null,
|
||||||
|
): UserPrincipal = UserPrincipal(user, attributes)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.project.movienight.adapters.security
|
||||||
|
|
||||||
|
import com.project.movienight.application.ports.input.security.OAuth2UserInfo
|
||||||
|
|
||||||
|
class VkOAuth2UserInfo(
|
||||||
|
private val attributes: Map<String, Any>,
|
||||||
|
) : OAuth2UserInfo {
|
||||||
|
override fun getProviderId(): String =
|
||||||
|
(attributes["response"] as? List<*>)
|
||||||
|
?.firstOrNull()
|
||||||
|
?.let { it as? Map<*, *> }
|
||||||
|
?.get("id")
|
||||||
|
?.toString() ?: ""
|
||||||
|
|
||||||
|
override fun getEmail(): String = attributes["email"]?.toString() ?: ""
|
||||||
|
|
||||||
|
override fun getName(): String {
|
||||||
|
val response = attributes["response"] as? List<*>
|
||||||
|
val first = response?.firstOrNull() as? Map<*, *>
|
||||||
|
val firstName = first?.get("first_name")?.toString() ?: ""
|
||||||
|
val lastName = first?.get("last_name")?.toString() ?: ""
|
||||||
|
return "$firstName $lastName".trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getProvider(): String = "vk"
|
||||||
|
|
||||||
|
override fun getAttributes(): Map<String, Any> = attributes
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.project.movienight.adapters.security
|
||||||
|
|
||||||
|
import com.project.movienight.application.ports.input.security.OAuth2UserInfo
|
||||||
|
|
||||||
|
class YandexOAuth2UserInfo(
|
||||||
|
private val attributes: Map<String, Any>,
|
||||||
|
) : OAuth2UserInfo {
|
||||||
|
override fun getProviderId(): String = attributes["id"]?.toString() ?: ""
|
||||||
|
|
||||||
|
override fun getEmail(): String =
|
||||||
|
(attributes["emails"] as? List<*>)
|
||||||
|
?.firstOrNull()
|
||||||
|
?.let { it as? Map<*, *> }
|
||||||
|
?.get("value")
|
||||||
|
?.toString() ?: ""
|
||||||
|
|
||||||
|
override fun getName(): String = attributes["display_name"]?.toString() ?: ""
|
||||||
|
|
||||||
|
override fun getProvider(): String = "yandex"
|
||||||
|
|
||||||
|
override fun getAttributes(): Map<String, Any> = attributes
|
||||||
|
}
|
||||||
@@ -68,6 +68,7 @@ class FilmLibraryController(
|
|||||||
getFilmLibraryUseCase.getLibrary(
|
getFilmLibraryUseCase.getLibrary(
|
||||||
GetFilmLibraryQuery(userId = userId),
|
GetFilmLibraryQuery(userId = userId),
|
||||||
)
|
)
|
||||||
|
|
||||||
val film = getFilmByIdUseCase.getById(library.filmId)
|
val film = getFilmByIdUseCase.getById(library.filmId)
|
||||||
return listOf(FilmResponse.fromDomain(film))
|
return listOf(FilmResponse.fromDomain(film))
|
||||||
}
|
}
|
||||||
|
|||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
package com.project.movienight.application.ports.input.security
|
||||||
|
|
||||||
|
interface OAuth2UserInfo {
|
||||||
|
fun getProviderId(): String
|
||||||
|
|
||||||
|
fun getEmail(): String
|
||||||
|
|
||||||
|
fun getName(): String
|
||||||
|
|
||||||
|
fun getProvider(): String
|
||||||
|
|
||||||
|
fun getAttributes(): Map<String, Any>
|
||||||
|
}
|
||||||
@@ -9,6 +9,8 @@ interface UserRepositoryPort {
|
|||||||
|
|
||||||
fun findById(id: UUID): User?
|
fun findById(id: UUID): User?
|
||||||
|
|
||||||
|
fun findByEmail(email: String): User?
|
||||||
|
|
||||||
fun findAll(): List<User>
|
fun findAll(): List<User>
|
||||||
|
|
||||||
fun deleteById(id: UUID)
|
fun deleteById(id: UUID)
|
||||||
|
|||||||
@@ -25,6 +25,38 @@ spring:
|
|||||||
console:
|
console:
|
||||||
enabled: ${SPRING_H2_CONSOLE_ENABLED:true}
|
enabled: ${SPRING_H2_CONSOLE_ENABLED:true}
|
||||||
path: /h2-console
|
path: /h2-console
|
||||||
|
security:
|
||||||
|
oauth2:
|
||||||
|
client:
|
||||||
|
registration:
|
||||||
|
google:
|
||||||
|
client-id: ${OAUTH2_GOOGLE_CLIENT_ID:test-client-id}
|
||||||
|
client-secret: ${OAUTH2_GOOGLE_CLIENT_SECRET:test-secret}
|
||||||
|
scope: email,profile
|
||||||
|
yandex:
|
||||||
|
client-id: ${OAUTH2_YANDEX_CLIENT_ID:test-client-id}
|
||||||
|
client-secret: ${OAUTH2_YANDEX_CLIENT_SECRET:test-secret}
|
||||||
|
authorization-grant-type: authorization_code
|
||||||
|
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
|
||||||
|
scope: login:email,login:avatar
|
||||||
|
vk:
|
||||||
|
client-id: ${OAUTH2_VK_CLIENT_ID:test-client-id}
|
||||||
|
client-secret: ${OAUTH2_VK_CLIENT_SECRET:test-secret}
|
||||||
|
authorization-grant-type: authorization_code
|
||||||
|
client-authentication-method: client_secret_post
|
||||||
|
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
|
||||||
|
scope: email
|
||||||
|
provider:
|
||||||
|
yandex:
|
||||||
|
authorization-uri: https://oauth.yandex.ru/authorize
|
||||||
|
token-uri: https://oauth.yandex.ru/token
|
||||||
|
user-info-uri: https://login.yandex.ru/info
|
||||||
|
user-name-attribute: id
|
||||||
|
vk:
|
||||||
|
authorization-uri: https://oauth.vk.com/authorize
|
||||||
|
token-uri: https://oauth.vk.com/access_token
|
||||||
|
user-info-uri: https://api.vk.com/method/users.get?v=5.131&fields=photo_200
|
||||||
|
user-name-attribute: response
|
||||||
|
|
||||||
server:
|
server:
|
||||||
shutdown: graceful
|
shutdown: graceful
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ CREATE TABLE IF NOT EXISTS public.users (
|
|||||||
id UUID PRIMARY KEY,
|
id UUID PRIMARY KEY,
|
||||||
name VARCHAR(255) NOT NULL,
|
name VARCHAR(255) NOT NULL,
|
||||||
email VARCHAR(320) NOT NULL UNIQUE,
|
email VARCHAR(320) NOT NULL UNIQUE,
|
||||||
|
password VARCHAR(255),
|
||||||
provider VARCHAR(64),
|
provider VARCHAR(64),
|
||||||
provider_id VARCHAR(255),
|
provider_id VARCHAR(255),
|
||||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_users_provider_provider_id
|
||||||
|
ON users(provider, provider_id);
|
||||||
@@ -17,7 +17,7 @@ import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPat
|
|||||||
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
|
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
|
||||||
|
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
@AutoConfigureMockMvc
|
@AutoConfigureMockMvc(addFilters = false)
|
||||||
class FilmControllerTest {
|
class FilmControllerTest {
|
||||||
@Autowired
|
@Autowired
|
||||||
private lateinit var mockMvc: MockMvc
|
private lateinit var mockMvc: MockMvc
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
|
|||||||
import org.springframework.transaction.annotation.Transactional
|
import org.springframework.transaction.annotation.Transactional
|
||||||
|
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
@AutoConfigureMockMvc
|
@AutoConfigureMockMvc(addFilters = false)
|
||||||
@Transactional
|
@Transactional
|
||||||
class FilmLibraryControllerTest {
|
class FilmLibraryControllerTest {
|
||||||
@Autowired
|
@Autowired
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
|
|||||||
import org.springframework.transaction.annotation.Transactional
|
import org.springframework.transaction.annotation.Transactional
|
||||||
|
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
@AutoConfigureMockMvc
|
@AutoConfigureMockMvc(addFilters = false)
|
||||||
@Transactional
|
@Transactional
|
||||||
class UserControllerTest {
|
class UserControllerTest {
|
||||||
@Autowired
|
@Autowired
|
||||||
|
|||||||
Reference in New Issue
Block a user
do not remove any deps, this removal does not make any sense to your PR