diff --git a/backend/.editorconfig b/backend/.editorconfig index cae1f2c..1f7a025 100644 --- a/backend/.editorconfig +++ b/backend/.editorconfig @@ -10,5 +10,5 @@ trim_trailing_whitespace = true insert_final_newline = true [*.kt] -max_line_length = 120 +max_line_length = 144 ij_kotlin_allow_trailing_comma = true diff --git a/backend/src/main/kotlin/com/lightswitch/application/service/AuthService.kt b/backend/src/main/kotlin/com/lightswitch/application/service/AuthService.kt index de5ae11..8e99043 100644 --- a/backend/src/main/kotlin/com/lightswitch/application/service/AuthService.kt +++ b/backend/src/main/kotlin/com/lightswitch/application/service/AuthService.kt @@ -1,30 +1,33 @@ package com.lightswitch.application.service -import com.lightswitch.presentation.exception.BusinessException import com.lightswitch.infrastructure.database.entity.RefreshToken import com.lightswitch.infrastructure.database.entity.User import com.lightswitch.infrastructure.database.repository.RefreshTokenRepository import com.lightswitch.infrastructure.database.repository.UserRepository import com.lightswitch.infrastructure.security.JwtToken import com.lightswitch.infrastructure.security.JwtTokenProvider +import com.lightswitch.presentation.exception.BusinessException import org.springframework.security.crypto.password.PasswordEncoder import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import java.time.LocalDateTime -import java.util.* +import java.util.Date @Service class AuthService( private val jwtTokenProvider: JwtTokenProvider, val userRepository: UserRepository, val refreshTokenRepository: RefreshTokenRepository, - val passwordEncoder: PasswordEncoder + val passwordEncoder: PasswordEncoder, ) { - @Transactional - fun login(username: String, password: String): JwtToken { - val user = userRepository.findByUsername(username) - ?: throw BusinessException("User with username $username not found") + fun login( + username: String, + password: String, + ): JwtToken { + val user = + userRepository.findByUsername(username) + ?: throw BusinessException("User with username $username not found") if (!passwordEncoder.matches(password, user.passwordHash)) { throw BusinessException("Password is incorrect") @@ -39,30 +42,39 @@ class AuthService( } @Transactional - fun signup(username: String, password: String): User { + fun signup( + username: String, + password: String, + ): User { if (userRepository.existsByUsername(username)) { throw BusinessException("Username already exists") } val passwordHash = passwordEncoder.encode(password) - val newUser = User( - username = username, - passwordHash = passwordHash - ) + val newUser = + User( + username = username, + passwordHash = passwordHash, + ) return userRepository.save(newUser) } @Transactional - fun reissue(jwtToken: String, userId: Long): JwtToken? { - val refreshToken: RefreshToken = refreshTokenRepository.findById(userId) - .orElseThrow { BusinessException("Log-out user") } + fun reissue( + jwtToken: String, + userId: Long, + ): JwtToken? { + val refreshToken: RefreshToken = + refreshTokenRepository.findById(userId) + .orElseThrow { BusinessException("Log-out user") } if (refreshToken.value != jwtToken) { throw BusinessException("Refresh Token is Not Valid") } - val user = userRepository.findById(userId) - .orElseThrow { BusinessException("User not found") } + val user = + userRepository.findById(userId) + .orElseThrow { BusinessException("User not found") } return when { jwtTokenProvider.isRefreshTokenRenewalRequired(refreshToken.value) -> { @@ -70,6 +82,7 @@ class AuthService( refreshToken.value = it.refreshToken.toString() } } + else -> { jwtTokenProvider.generateJwtAccessToken(userId, user, Date(), refreshToken.value) } @@ -80,4 +93,4 @@ class AuthService( fun logout(userId: Long) { refreshTokenRepository.deleteById(userId) } -} \ No newline at end of file +} diff --git a/backend/src/main/kotlin/com/lightswitch/controller/UserController.kt b/backend/src/main/kotlin/com/lightswitch/controller/UserController.kt index 8dcbf7b..9b7f95d 100644 --- a/backend/src/main/kotlin/com/lightswitch/controller/UserController.kt +++ b/backend/src/main/kotlin/com/lightswitch/controller/UserController.kt @@ -10,45 +10,58 @@ import jakarta.validation.Valid import jakarta.validation.constraints.NotEmpty import org.springframework.security.core.Authentication import org.springframework.security.core.context.SecurityContextHolder -import org.springframework.web.bind.annotation.* +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.RequestHeader +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController @RestController @RequestMapping("/api/v1/users") class UserController(private val authService: AuthService) { @Operation( summary = "Login the user", - description = "Authenticates a user with their username and password, and returns a JWT token." + description = "Authenticates a user with their username and password, and returns a JWT token.", ) @PostMapping("/login") - fun userLogin(@RequestBody @Valid body: UserAccount): PayloadResponse { + fun userLogin( + @RequestBody @Valid body: UserAccount, + ): PayloadResponse { val token = authService.login(body.username, body.password) return PayloadResponse( "Success", message = "User Login Success", - data = token + data = token, ) } @Operation( summary = "Initialize user account", - description = "Registers a new user with the provided username and password. Returns a confirmation message." + description = "Registers a new user with the provided username and password. Returns a confirmation message.", ) @PostMapping("/initialize") - fun userInitialize(@RequestBody @Valid body: UserAccount): PayloadResponse { + fun userInitialize( + @RequestBody @Valid body: UserAccount, + ): PayloadResponse { val user = authService.signup(body.username, body.password) return PayloadResponse( "Success", message = "Signup New User Success", - data = user.username + data = user.username, ) } @Operation( summary = "Refresh authentication token", - description = "Reissues a new JWT token using the provided refresh token and current user's identity." + description = "Reissues a new JWT token using the provided refresh token and current user's identity.", ) @PutMapping("/auth/refresh") - fun refreshUserToken(@RequestHeader("Authorization") @NotEmpty @Parameter(description = "The refresh token prefixed with 'Bearer '.") refreshToken: String): PayloadResponse { + fun refreshUserToken( + @RequestHeader( + "Authorization", + ) @NotEmpty @Parameter(description = "The refresh token prefixed with 'Bearer '.") refreshToken: String, + ): PayloadResponse { val authentication: Authentication = SecurityContextHolder.getContext().authentication val token = authService.reissue(refreshToken.removePrefix("Bearer "), authentication.name.toLong()) return PayloadResponse("Success", "Refresh Token Success", token) @@ -56,11 +69,13 @@ class UserController(private val authService: AuthService) { @Operation( summary = "Logout the user", - description = "Logs out the user by invalidating their access token. Requires the user to provide their token." + description = "Logs out the user by invalidating their access token. Requires the user to provide their token.", ) @PostMapping("/logout") fun userLogout( - @RequestHeader("Authorization") @NotEmpty @Parameter(description = "The access token prefixed with 'Bearer '.") accessToken: String, + @RequestHeader( + "Authorization", + ) @NotEmpty @Parameter(description = "The access token prefixed with 'Bearer '.") accessToken: String, @RequestBody @Valid body: UserAccount, ): PayloadResponse { val authentication: Authentication = SecurityContextHolder.getContext().authentication diff --git a/backend/src/main/kotlin/com/lightswitch/controller/request/UserAccount.kt b/backend/src/main/kotlin/com/lightswitch/controller/request/UserAccount.kt index 60f18fd..af31883 100644 --- a/backend/src/main/kotlin/com/lightswitch/controller/request/UserAccount.kt +++ b/backend/src/main/kotlin/com/lightswitch/controller/request/UserAccount.kt @@ -10,5 +10,5 @@ data class UserAccount( val username: String, @Schema(description = "The password of the user.") @field:NotBlank(message = "Password is required.") - val password: String -) \ No newline at end of file + val password: String, +) diff --git a/backend/src/main/kotlin/com/lightswitch/infrastructure/database/entity/RefreshToken.kt b/backend/src/main/kotlin/com/lightswitch/infrastructure/database/entity/RefreshToken.kt index dfaeb4a..0e61677 100644 --- a/backend/src/main/kotlin/com/lightswitch/infrastructure/database/entity/RefreshToken.kt +++ b/backend/src/main/kotlin/com/lightswitch/infrastructure/database/entity/RefreshToken.kt @@ -5,10 +5,10 @@ import jakarta.persistence.Entity import jakarta.persistence.Id @Entity -class RefreshToken ( +class RefreshToken( @Id @Column(nullable = false) val userId: Long, @Column(nullable = false) - var value: String -): BaseEntity() \ No newline at end of file + var value: String, +) : BaseEntity() diff --git a/backend/src/main/kotlin/com/lightswitch/infrastructure/database/model/Type.kt b/backend/src/main/kotlin/com/lightswitch/infrastructure/database/model/Type.kt index 8fbb05c..240855c 100644 --- a/backend/src/main/kotlin/com/lightswitch/infrastructure/database/model/Type.kt +++ b/backend/src/main/kotlin/com/lightswitch/infrastructure/database/model/Type.kt @@ -3,7 +3,8 @@ package com.lightswitch.infrastructure.database.model enum class Type { NUMBER, BOOLEAN, - STRING; + STRING, + ; companion object { fun from(type: String): Type { diff --git a/backend/src/main/kotlin/com/lightswitch/infrastructure/database/repository/RefreshTokenRepository.kt b/backend/src/main/kotlin/com/lightswitch/infrastructure/database/repository/RefreshTokenRepository.kt index b931696..ee945c8 100644 --- a/backend/src/main/kotlin/com/lightswitch/infrastructure/database/repository/RefreshTokenRepository.kt +++ b/backend/src/main/kotlin/com/lightswitch/infrastructure/database/repository/RefreshTokenRepository.kt @@ -3,4 +3,4 @@ package com.lightswitch.infrastructure.database.repository import com.lightswitch.infrastructure.database.entity.RefreshToken import org.springframework.data.jpa.repository.JpaRepository -interface RefreshTokenRepository: JpaRepository \ No newline at end of file +interface RefreshTokenRepository : JpaRepository diff --git a/backend/src/main/kotlin/com/lightswitch/infrastructure/database/repository/UserRepository.kt b/backend/src/main/kotlin/com/lightswitch/infrastructure/database/repository/UserRepository.kt index bcb73a7..7588151 100644 --- a/backend/src/main/kotlin/com/lightswitch/infrastructure/database/repository/UserRepository.kt +++ b/backend/src/main/kotlin/com/lightswitch/infrastructure/database/repository/UserRepository.kt @@ -3,7 +3,8 @@ package com.lightswitch.infrastructure.database.repository import com.lightswitch.infrastructure.database.entity.User import org.springframework.data.jpa.repository.JpaRepository -interface UserRepository : JpaRepository{ +interface UserRepository : JpaRepository { fun existsByUsername(username: String): Boolean + fun findByUsername(username: String): User? } diff --git a/backend/src/main/kotlin/com/lightswitch/infrastructure/security/JwtToken.kt b/backend/src/main/kotlin/com/lightswitch/infrastructure/security/JwtToken.kt index 22f65af..7261520 100644 --- a/backend/src/main/kotlin/com/lightswitch/infrastructure/security/JwtToken.kt +++ b/backend/src/main/kotlin/com/lightswitch/infrastructure/security/JwtToken.kt @@ -2,12 +2,22 @@ package com.lightswitch.infrastructure.security import io.swagger.v3.oas.annotations.media.Schema -@Schema(description = "Represents the JWT token response containing both access and refresh tokens, as well as the expiration date of the access token.") +@Schema( + description = + "Represents the JWT token response containing both access and refresh tokens, " + + "as well as the expiration date of the access token.", +) data class JwtToken( - @Schema(description = "The access token used to authenticate the user in subsequent requests. This token is valid for 30 minutes.") + @Schema( + description = "The access token used to authenticate the user in subsequent requests. This token is valid for 30 minutes.", + ) val accessToken: String? = null, - @Schema(description = "The refresh token used to obtain a new access token once it expires. This token is valid for 7 days.") + @Schema( + description = "The refresh token used to obtain a new access token once it expires. This token is valid for 7 days.", + ) val refreshToken: String? = null, - @Schema(description = "The expiration date (timestamp in milliseconds) of the access token. The token becomes invalid after this time.") - val accessTokenExpiredDate: Long? = null + @Schema( + description = "The expiration date (timestamp in milliseconds) of the access token. The token becomes invalid after this time.", + ) + val accessTokenExpiredDate: Long? = null, ) diff --git a/backend/src/main/kotlin/com/lightswitch/infrastructure/security/JwtTokenFilter.kt b/backend/src/main/kotlin/com/lightswitch/infrastructure/security/JwtTokenFilter.kt index b79d2d4..ea43fcf 100644 --- a/backend/src/main/kotlin/com/lightswitch/infrastructure/security/JwtTokenFilter.kt +++ b/backend/src/main/kotlin/com/lightswitch/infrastructure/security/JwtTokenFilter.kt @@ -13,7 +13,6 @@ import java.io.IOException class JwtTokenFilter( private val jwtTokenProvider: JwtTokenProvider, ) : OncePerRequestFilter() { - companion object { const val HEADER_STRING = "Authorization" const val TOKEN_PREFIX = "Bearer " @@ -23,7 +22,7 @@ class JwtTokenFilter( public override fun doFilterInternal( request: HttpServletRequest, response: HttpServletResponse, - filterChain: FilterChain + filterChain: FilterChain, ) { getTokenFromRequest(request) ?.takeIf { jwtTokenProvider.validateToken(it) } @@ -38,4 +37,4 @@ class JwtTokenFilter( ?.takeIf { it.startsWith(TOKEN_PREFIX) } ?.removePrefix(TOKEN_PREFIX) } -} \ No newline at end of file +} diff --git a/backend/src/main/kotlin/com/lightswitch/infrastructure/security/JwtTokenProvider.kt b/backend/src/main/kotlin/com/lightswitch/infrastructure/security/JwtTokenProvider.kt index cd09f95..adc10da 100644 --- a/backend/src/main/kotlin/com/lightswitch/infrastructure/security/JwtTokenProvider.kt +++ b/backend/src/main/kotlin/com/lightswitch/infrastructure/security/JwtTokenProvider.kt @@ -1,7 +1,13 @@ package com.lightswitch.infrastructure.security import com.lightswitch.infrastructure.database.entity.User -import io.jsonwebtoken.* +import io.jsonwebtoken.Claims +import io.jsonwebtoken.ExpiredJwtException +import io.jsonwebtoken.JwtException +import io.jsonwebtoken.Jwts +import io.jsonwebtoken.MalformedJwtException +import io.jsonwebtoken.SignatureAlgorithm +import io.jsonwebtoken.UnsupportedJwtException import io.jsonwebtoken.io.Decoders import io.jsonwebtoken.security.Keys import org.slf4j.Logger @@ -10,13 +16,13 @@ import org.springframework.beans.factory.annotation.Value import org.springframework.security.authentication.UsernamePasswordAuthenticationToken import org.springframework.security.core.Authentication import org.springframework.stereotype.Component -import java.util.* +import java.util.Date import javax.crypto.SecretKey - @Component -class JwtTokenProvider(@Value("\${jwt.secret}") private var secretKey: String) { - +class JwtTokenProvider( + @Value("\${jwt.secret}") private var secretKey: String, +) { private val logger: Logger = LoggerFactory.getLogger(JwtTokenProvider::class.java) companion object { @@ -27,47 +33,53 @@ class JwtTokenProvider(@Value("\${jwt.secret}") private var secretKey: String) { const val THREE_DAYS = 3 * 24 * 60 * 60 * 1000L // 3 days } - fun generateJwtToken(userId: Long, user: User): JwtToken { + fun generateJwtToken( + userId: Long, + user: User, + ): JwtToken { val now = Date() val accessToken = createAccessToken(userId, user, now) - val refreshTokenClaims: Claims = Jwts.claims().setSubject(userId.toString()).apply { - this[TYPE] = "refresh" - } - val refreshToken = Jwts.builder() - .setClaims(refreshTokenClaims) - .setIssuedAt(now) - .setExpiration(Date(now.time + REFRESH_VALID_TIME)) - .signWith(getSigningKey(), SignatureAlgorithm.HS256) - .compact() + val refreshTokenClaims: Claims = + Jwts.claims().setSubject(userId.toString()).apply { + this[TYPE] = "refresh" + } + val refreshToken = + Jwts.builder() + .setClaims(refreshTokenClaims) + .setIssuedAt(now) + .setExpiration(Date(now.time + REFRESH_VALID_TIME)) + .signWith(getSigningKey(), SignatureAlgorithm.HS256) + .compact() return JwtToken( accessToken = accessToken, refreshToken = refreshToken, - accessTokenExpiredDate = ACCESS_VALID_TIME + accessTokenExpiredDate = ACCESS_VALID_TIME, ) } private fun createAccessToken( userId: Long, user: User, - now: Date + now: Date, ): String? { - val accessTokenClaims: Claims = Jwts.claims().setSubject(userId.toString()).apply { - this[TYPE] = "access" - this[USER] = userToMap(user) - } - - val accessToken = Jwts.builder() - .setClaims(accessTokenClaims) - .setIssuedAt(now) - .setExpiration(Date(now.time + ACCESS_VALID_TIME)) - .signWith(getSigningKey(), SignatureAlgorithm.HS256) - .compact() + val accessTokenClaims: Claims = + Jwts.claims().setSubject(userId.toString()).apply { + this[TYPE] = "access" + this[USER] = userToMap(user) + } + + val accessToken = + Jwts.builder() + .setClaims(accessTokenClaims) + .setIssuedAt(now) + .setExpiration(Date(now.time + ACCESS_VALID_TIME)) + .signWith(getSigningKey(), SignatureAlgorithm.HS256) + .compact() return accessToken } - private fun userToMap(user: User): Map { val userMap: MutableMap = HashMap() @@ -78,17 +90,21 @@ class JwtTokenProvider(@Value("\${jwt.secret}") private var secretKey: String) { return userMap } - private fun getSigningKey(): SecretKey? { return Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretKey)) } - fun generateJwtAccessToken(userId: Long, user: User, now: Date, refreshToken: String): JwtToken { + fun generateJwtAccessToken( + userId: Long, + user: User, + now: Date, + refreshToken: String, + ): JwtToken { val accessToken = createAccessToken(userId, user, now) return JwtToken( accessToken = accessToken, refreshToken = refreshToken, - accessTokenExpiredDate = ACCESS_VALID_TIME + accessTokenExpiredDate = ACCESS_VALID_TIME, ) } @@ -132,12 +148,12 @@ class JwtTokenProvider(@Value("\${jwt.secret}") private var secretKey: String) { } } - fun getRefreshTokenSubject(token: String): Long { return try { val claims = parseClaims(token) - if (claims[TYPE] != "refresh") + if (claims[TYPE] != "refresh") { throw RuntimeException("Token is not refresh token") + } claims.subject.toLong() } catch (e: Exception) { @@ -153,5 +169,4 @@ class JwtTokenProvider(@Value("\${jwt.secret}") private var secretKey: String) { return refreshExpiredTime - now <= THREE_DAYS } - -} \ No newline at end of file +} diff --git a/backend/src/main/kotlin/com/lightswitch/infrastructure/security/SecurityConfig.kt b/backend/src/main/kotlin/com/lightswitch/infrastructure/security/SecurityConfig.kt index 00dc19d..2f994a2 100644 --- a/backend/src/main/kotlin/com/lightswitch/infrastructure/security/SecurityConfig.kt +++ b/backend/src/main/kotlin/com/lightswitch/infrastructure/security/SecurityConfig.kt @@ -19,9 +19,8 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic @EnableMethodSecurity class SecurityConfig( private val authenticationConfiguration: AuthenticationConfiguration, - private val jwtTokenProvider: JwtTokenProvider + private val jwtTokenProvider: JwtTokenProvider, ) { - @Bean fun passwordEncoder(): PasswordEncoder { return BCryptPasswordEncoder() @@ -29,7 +28,6 @@ class SecurityConfig( @Bean fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { - http .csrf { it.disable() @@ -40,9 +38,7 @@ class SecurityConfig( .permitAll() .requestMatchers("/api/v1/users/**").permitAll() .anyRequest().authenticated() - } - .sessionManagement { session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS) } @@ -51,13 +47,12 @@ class SecurityConfig( } .addFilterBefore( JwtTokenFilter(jwtTokenProvider), - UsernamePasswordAuthenticationFilter::class.java + UsernamePasswordAuthenticationFilter::class.java, ) return http.build() } @Bean - fun authenticationManager(): AuthenticationManager = - authenticationConfiguration.authenticationManager + fun authenticationManager(): AuthenticationManager = authenticationConfiguration.authenticationManager } diff --git a/backend/src/main/kotlin/com/lightswitch/presentation/controller/FeatureFlagController.kt b/backend/src/main/kotlin/com/lightswitch/presentation/controller/FeatureFlagController.kt index 26f7f29..487a302 100644 --- a/backend/src/main/kotlin/com/lightswitch/presentation/controller/FeatureFlagController.kt +++ b/backend/src/main/kotlin/com/lightswitch/presentation/controller/FeatureFlagController.kt @@ -29,7 +29,6 @@ class FeatureFlagController( private val featureFlagService: FeatureFlagService, private val userRepository: UserRepository, ) { - @Operation( summary = "Retrieve all feature flags", ) @@ -54,7 +53,7 @@ class FeatureFlagController( return PayloadResponse.success( message = "Fetched a flag successfully", - data = FeatureFlagResponse.from(flag) + data = FeatureFlagResponse.from(flag), ) } @@ -74,7 +73,7 @@ class FeatureFlagController( return PayloadResponse.success( message = "Created flag successfully", - data = FeatureFlagResponse.from(flag) + data = FeatureFlagResponse.from(flag), ) } @@ -89,7 +88,7 @@ class FeatureFlagController( return PayloadResponse( status = "status", message = "message", - data = null + data = null, ) } @@ -104,7 +103,7 @@ class FeatureFlagController( return PayloadResponse( status = "status", message = "message", - data = null + data = null, ) } diff --git a/backend/src/main/kotlin/com/lightswitch/presentation/controlleradvice/GlobalExceptionHandler.kt b/backend/src/main/kotlin/com/lightswitch/presentation/controlleradvice/GlobalExceptionHandler.kt index 8d7402d..c5c3b53 100644 --- a/backend/src/main/kotlin/com/lightswitch/presentation/controlleradvice/GlobalExceptionHandler.kt +++ b/backend/src/main/kotlin/com/lightswitch/presentation/controlleradvice/GlobalExceptionHandler.kt @@ -26,7 +26,7 @@ class GlobalExceptionHandler { StatusResponse( status = HttpStatus.BAD_REQUEST.name, message = ex.message ?: "Invalid request", - ) + ), ) } @@ -39,7 +39,7 @@ class GlobalExceptionHandler { StatusResponse( status = HttpStatus.BAD_REQUEST.name, message = ex.message, - ) + ), ) } @@ -52,7 +52,7 @@ class GlobalExceptionHandler { StatusResponse( status = HttpStatus.BAD_REQUEST.name, message = ex.message ?: "Malformed JSON request", - ) + ), ) } @@ -65,7 +65,7 @@ class GlobalExceptionHandler { StatusResponse( status = HttpStatus.BAD_REQUEST.name, message = ex.message ?: "Entity not found", - ) + ), ) } @@ -78,7 +78,7 @@ class GlobalExceptionHandler { StatusResponse( status = HttpStatus.UNAUTHORIZED.name, message = "Invalid username or password", - ) + ), ) } @@ -91,7 +91,7 @@ class GlobalExceptionHandler { StatusResponse( status = HttpStatus.UNAUTHORIZED.name, message = ex.message ?: "Authentication failed", - ) + ), ) } @@ -104,7 +104,7 @@ class GlobalExceptionHandler { StatusResponse( status = HttpStatus.FORBIDDEN.name, message = ex.message ?: "Authorization failed", - ) + ), ) } @@ -117,7 +117,7 @@ class GlobalExceptionHandler { StatusResponse( status = HttpStatus.INTERNAL_SERVER_ERROR.name, message = ex.message ?: "Business error occurred", - ) + ), ) } @@ -130,7 +130,7 @@ class GlobalExceptionHandler { StatusResponse( status = HttpStatus.INTERNAL_SERVER_ERROR.name, message = "Unexpected error: ${ex.message}", - ) + ), ) } } diff --git a/backend/src/main/kotlin/com/lightswitch/presentation/exception/BusinessException.kt b/backend/src/main/kotlin/com/lightswitch/presentation/exception/BusinessException.kt index 86e2e39..dec5888 100644 --- a/backend/src/main/kotlin/com/lightswitch/presentation/exception/BusinessException.kt +++ b/backend/src/main/kotlin/com/lightswitch/presentation/exception/BusinessException.kt @@ -1,4 +1,3 @@ package com.lightswitch.presentation.exception -class BusinessException(message: String) : RuntimeException(message) { -} \ No newline at end of file +class BusinessException(message: String) : RuntimeException(message) diff --git a/backend/src/main/kotlin/com/lightswitch/presentation/model/PayloadResponse.kt b/backend/src/main/kotlin/com/lightswitch/presentation/model/PayloadResponse.kt index 4d6317b..310181e 100644 --- a/backend/src/main/kotlin/com/lightswitch/presentation/model/PayloadResponse.kt +++ b/backend/src/main/kotlin/com/lightswitch/presentation/model/PayloadResponse.kt @@ -6,7 +6,10 @@ data class PayloadResponse( val data: T? = null, ) { companion object { - fun success(message: String, data: T): PayloadResponse { + fun success( + message: String, + data: T, + ): PayloadResponse { return PayloadResponse("Success", message, data) } } diff --git a/backend/src/main/kotlin/com/lightswitch/presentation/model/StatusResponse.kt b/backend/src/main/kotlin/com/lightswitch/presentation/model/StatusResponse.kt index ca8692e..bc730eb 100644 --- a/backend/src/main/kotlin/com/lightswitch/presentation/model/StatusResponse.kt +++ b/backend/src/main/kotlin/com/lightswitch/presentation/model/StatusResponse.kt @@ -2,5 +2,5 @@ package com.lightswitch.presentation.model data class StatusResponse( val status: String, - val message: String + val message: String, ) diff --git a/backend/src/main/kotlin/com/lightswitch/presentation/model/flag/CreateFeatureFlagRequest.kt b/backend/src/main/kotlin/com/lightswitch/presentation/model/flag/CreateFeatureFlagRequest.kt index 867b11f..6db9d03 100644 --- a/backend/src/main/kotlin/com/lightswitch/presentation/model/flag/CreateFeatureFlagRequest.kt +++ b/backend/src/main/kotlin/com/lightswitch/presentation/model/flag/CreateFeatureFlagRequest.kt @@ -20,5 +20,6 @@ data class CreateFeatureFlagRequest( val variants: List>? = null, ) { fun defaultValueAsPair(): Pair = defaultValue.entries.first().toPair() + fun variantPairs(): List>? = variants?.map { it.entries.first().toPair() } } diff --git a/backend/src/test/kotlin/com/lightswitch/application/service/AuthServiceTest.kt b/backend/src/test/kotlin/com/lightswitch/application/service/AuthServiceTest.kt index 58cd946..4968ff3 100644 --- a/backend/src/test/kotlin/com/lightswitch/application/service/AuthServiceTest.kt +++ b/backend/src/test/kotlin/com/lightswitch/application/service/AuthServiceTest.kt @@ -1,18 +1,22 @@ package com.lightswitch.application.service -import com.lightswitch.presentation.exception.BusinessException import com.lightswitch.infrastructure.database.entity.RefreshToken import com.lightswitch.infrastructure.database.entity.User import com.lightswitch.infrastructure.database.repository.RefreshTokenRepository import com.lightswitch.infrastructure.database.repository.UserRepository import com.lightswitch.infrastructure.security.JwtToken import com.lightswitch.infrastructure.security.JwtTokenProvider -import org.junit.jupiter.api.Assertions.* +import com.lightswitch.presentation.exception.BusinessException +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertThrows import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.mockito.ArgumentMatchers import org.mockito.Mock -import org.mockito.Mockito.* +import org.mockito.Mockito.mock +import org.mockito.Mockito.times +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations import org.mockito.kotlin.any import org.mockito.kotlin.eq @@ -20,10 +24,9 @@ import org.mockito.kotlin.verify import org.springframework.security.core.Authentication import org.springframework.security.crypto.password.PasswordEncoder import java.time.LocalDateTime -import java.util.* +import java.util.Optional class AuthServiceTest { - @Mock private lateinit var jwtTokenProvider: JwtTokenProvider @@ -38,27 +41,30 @@ class AuthServiceTest { private lateinit var authService: AuthService - private val user = User( - id = 1L, - username = "testUser", - passwordHash = "hashedPassword", - lastLoginAt = LocalDateTime.now() - ) + private val user = + User( + id = 1L, + username = "testUser", + passwordHash = "hashedPassword", + lastLoginAt = LocalDateTime.now(), + ) - private val refreshToken = RefreshToken( - userId = 1L, - value = "refreshToken" - ) + private val refreshToken = + RefreshToken( + userId = 1L, + value = "refreshToken", + ) @BeforeEach fun setUp() { MockitoAnnotations.openMocks(this) - authService = AuthService( - jwtTokenProvider = jwtTokenProvider, - userRepository = userRepository, - refreshTokenRepository = refreshTokenRepository, - passwordEncoder = passwordEncoder - ) + authService = + AuthService( + jwtTokenProvider = jwtTokenProvider, + userRepository = userRepository, + refreshTokenRepository = refreshTokenRepository, + passwordEncoder = passwordEncoder, + ) } @Test @@ -93,9 +99,10 @@ class AuthServiceTest { `when`(userRepository.findByUsername(username)) .thenReturn(null) - val exception = assertThrows(BusinessException::class.java) { - authService.login(username, password) - } + val exception = + assertThrows(BusinessException::class.java) { + authService.login(username, password) + } assertEquals("User with username invalidUser not found", exception.message) } @@ -110,9 +117,10 @@ class AuthServiceTest { `when`(passwordEncoder.matches(password, user.passwordHash)) .thenReturn(false) - val exception = assertThrows(BusinessException::class.java) { - authService.login(username, password) - } + val exception = + assertThrows(BusinessException::class.java) { + authService.login(username, password) + } assertEquals("Password is incorrect", exception.message) } @@ -121,10 +129,11 @@ class AuthServiceTest { val username = "newUser" val password = "newPassword" val passwordHash = "hashedPassword" - val newUser = User( - username = username, - passwordHash = passwordHash - ) + val newUser = + User( + username = username, + passwordHash = passwordHash, + ) `when`(userRepository.existsByUsername(username)) .thenReturn(false) @@ -139,7 +148,7 @@ class AuthServiceTest { assertNotNull(result) assertEquals(username, result.username) - verify(userRepository).save(any(User::class.java)) + verify(userRepository).save(ArgumentMatchers.any(User::class.java)) } @Test @@ -150,9 +159,10 @@ class AuthServiceTest { `when`(userRepository.existsByUsername(username)) .thenReturn(true) - val exception = assertThrows(BusinessException::class.java) { - authService.signup(username, password) - } + val exception = + assertThrows(BusinessException::class.java) { + authService.signup(username, password) + } assertEquals("Username already exists", exception.message) } @@ -181,7 +191,6 @@ class AuthServiceTest { assertEquals(newToken.refreshToken, result?.refreshToken) } - @Test fun `reissue should return new JwtToken if refresh token is valid but no renewal is required`() { val userId = 1L @@ -209,9 +218,9 @@ class AuthServiceTest { eq(user), any(), eq( - token.refreshToken.toString() - ) - ) + token.refreshToken.toString(), + ), + ), ).thenReturn(newToken) val result = authService.reissue(token.refreshToken.toString(), userId) @@ -231,7 +240,7 @@ class AuthServiceTest { verify( refreshTokenRepository, - times(1) + times(1), ).deleteById(1L) } } diff --git a/backend/src/test/kotlin/com/lightswitch/application/service/FeatureFlagServiceTest.kt b/backend/src/test/kotlin/com/lightswitch/application/service/FeatureFlagServiceTest.kt index 6e89ff2..f699bc7 100644 --- a/backend/src/test/kotlin/com/lightswitch/application/service/FeatureFlagServiceTest.kt +++ b/backend/src/test/kotlin/com/lightswitch/application/service/FeatureFlagServiceTest.kt @@ -41,43 +41,47 @@ class FeatureFlagServiceTest : BaseRepositoryTest() { @Test fun `getFlags should return all feature flags`() { - val user = userRepository.save( - User( - username = "test-user", - passwordHash = "passwordHash", - lastLoginAt = LocalDate.of(2025, 1, 1).atStartOfDay(), + val user = + userRepository.save( + User( + username = "test-user", + passwordHash = "passwordHash", + lastLoginAt = LocalDate.of(2025, 1, 1).atStartOfDay(), + ), ) - ) - val flag1 = featureFlagRepository.save( - FeatureFlag( - name = "feature-1", - description = "Feature Flag 1", - type = Type.BOOLEAN, - enabled = true, - createdBy = user, - updatedBy = user + val flag1 = + featureFlagRepository.save( + FeatureFlag( + name = "feature-1", + description = "Feature Flag 1", + type = Type.BOOLEAN, + enabled = true, + createdBy = user, + updatedBy = user, + ), ) - ) - val flag2 = featureFlagRepository.save( - FeatureFlag( - name = "feature-2", - description = "Feature Flag 2", - type = Type.NUMBER, - enabled = false, - createdBy = user, - updatedBy = user + val flag2 = + featureFlagRepository.save( + FeatureFlag( + name = "feature-2", + description = "Feature Flag 2", + type = Type.NUMBER, + enabled = false, + createdBy = user, + updatedBy = user, + ), ) - ) - val flag3 = featureFlagRepository.save( - FeatureFlag( - name = "feature-3", - description = "Feature Flag 3", - type = Type.STRING, - enabled = true, - createdBy = user, - updatedBy = user + val flag3 = + featureFlagRepository.save( + FeatureFlag( + name = "feature-3", + description = "Feature Flag 3", + type = Type.STRING, + enabled = true, + createdBy = user, + updatedBy = user, + ), ) - ) flag1.defaultCondition = Condition(flag = flag1, key = "boolean", value = true) flag2.defaultCondition = Condition(flag = flag2, key = "number", value = 10) flag3.defaultCondition = Condition(flag = flag3, key = "string", value = "value") @@ -108,23 +112,25 @@ class FeatureFlagServiceTest : BaseRepositoryTest() { @Test fun `getFlags should return empty list when all feature flags are deleted`() { - val user = userRepository.save( - User( - username = "test-user", - passwordHash = "passwordHash", - lastLoginAt = LocalDate.of(2025, 1, 1).atStartOfDay(), - ), - ) - val flag = FeatureFlag( - name = "user-limit", - description = "User Limit Flag", - type = Type.NUMBER, - enabled = true, - createdBy = user, - updatedBy = user, - ).apply { - this.deletedAt = Instant.now() - } + val user = + userRepository.save( + User( + username = "test-user", + passwordHash = "passwordHash", + lastLoginAt = LocalDate.of(2025, 1, 1).atStartOfDay(), + ), + ) + val flag = + FeatureFlag( + name = "user-limit", + description = "User Limit Flag", + type = Type.NUMBER, + enabled = true, + createdBy = user, + updatedBy = user, + ).apply { + this.deletedAt = Instant.now() + } featureFlagRepository.save(flag) assertThat(featureFlagService.getFlags()).isEmpty() @@ -132,34 +138,39 @@ class FeatureFlagServiceTest : BaseRepositoryTest() { @Test fun `getFlagOrThrow should return feature flag when key exists`() { - val user = userRepository.save( - User( - username = "test-user", - passwordHash = "passwordHash", - lastLoginAt = LocalDate.of(2025, 1, 1).atStartOfDay(), - ), - ) - val savedFlag = featureFlagRepository.save( - FeatureFlag( - name = "user-limit", - description = "User Limit Flag", - type = Type.NUMBER, - enabled = true, - createdBy = user, - updatedBy = user + val user = + userRepository.save( + User( + username = "test-user", + passwordHash = "passwordHash", + lastLoginAt = LocalDate.of(2025, 1, 1).atStartOfDay(), + ), + ) + val savedFlag = + featureFlagRepository.save( + FeatureFlag( + name = "user-limit", + description = "User Limit Flag", + type = Type.NUMBER, + enabled = true, + createdBy = user, + updatedBy = user, + ), ) - ) val defaultCondition = Condition(flag = savedFlag, key = "number", value = 10) - val conditions = listOf( - Condition(flag = savedFlag, key = "free", value = 10), - Condition(flag = savedFlag, key = "pro", value = 100), - Condition(flag = savedFlag, key = "enterprise", value = 1000), + val conditions = + listOf( + Condition(flag = savedFlag, key = "free", value = 10), + Condition(flag = savedFlag, key = "pro", value = 100), + Condition(flag = savedFlag, key = "enterprise", value = 1000), + ) + featureFlagRepository.save( + savedFlag.apply { + this.defaultCondition = defaultCondition + this.conditions.addAll(conditions) + this.conditions.add(defaultCondition) + }, ) - featureFlagRepository.save(savedFlag.apply { - this.defaultCondition = defaultCondition - this.conditions.addAll(conditions) - this.conditions.add(defaultCondition) - }) val flag = featureFlagService.getFlagOrThrow("user-limit") @@ -193,26 +204,27 @@ class FeatureFlagServiceTest : BaseRepositoryTest() { .hasMessageContaining("Feature flag $nonExistentKey does not exist") } - @Test fun `getFlagOrThrow should not return when feature flag is deleted`() { - val user = userRepository.save( - User( - username = "test-user", - passwordHash = "passwordHash", - lastLoginAt = LocalDate.of(2025, 1, 1).atStartOfDay(), - ), - ) - val flag = FeatureFlag( - name = "user-limit", - description = "User Limit Flag", - type = Type.NUMBER, - enabled = true, - createdBy = user, - updatedBy = user, - ).apply { - this.deletedAt = Instant.now() - } + val user = + userRepository.save( + User( + username = "test-user", + passwordHash = "passwordHash", + lastLoginAt = LocalDate.of(2025, 1, 1).atStartOfDay(), + ), + ) + val flag = + FeatureFlag( + name = "user-limit", + description = "User Limit Flag", + type = Type.NUMBER, + enabled = true, + createdBy = user, + updatedBy = user, + ).apply { + this.deletedAt = Instant.now() + } featureFlagRepository.save(flag) assertThatThrownBy { featureFlagService.getFlagOrThrow("user-limit") } diff --git a/backend/src/test/kotlin/com/lightswitch/controller/UserControllerTest.kt b/backend/src/test/kotlin/com/lightswitch/controller/UserControllerTest.kt index 880ab87..11e57f0 100644 --- a/backend/src/test/kotlin/com/lightswitch/controller/UserControllerTest.kt +++ b/backend/src/test/kotlin/com/lightswitch/controller/UserControllerTest.kt @@ -22,7 +22,6 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders @ExtendWith(MockitoExtension::class) class UserControllerTest { - @Autowired lateinit var mockMvc: MockMvc @@ -50,14 +49,13 @@ class UserControllerTest { mockMvc.perform( MockMvcRequestBuilders.post("/api/v1/users/login") .contentType(MediaType.APPLICATION_JSON) - .content("""{"username": "testUser", "password": "password"}""") + .content("""{"username": "testUser", "password": "password"}"""), ) .andExpect(MockMvcResultMatchers.status().isOk) .andExpect(MockMvcResultMatchers.jsonPath("$.data.accessToken").value("accessToken")) .andExpect(MockMvcResultMatchers.jsonPath("$.data.refreshToken").value("refreshToken")) } - @Test fun `should refresh user token successfully`() { val refreshToken = "Bearer refreshToken" @@ -68,7 +66,7 @@ class UserControllerTest { mockMvc.perform( MockMvcRequestBuilders.put("/api/v1/users/auth/refresh") .header("Authorization", refreshToken) - .contentType(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON), ) .andExpect(MockMvcResultMatchers.status().isOk) .andExpect(MockMvcResultMatchers.jsonPath("$.data.accessToken").value("newAccessToken")) @@ -81,10 +79,9 @@ class UserControllerTest { MockMvcRequestBuilders.post("/api/v1/users/logout") .header("Authorization", "Bearer someAccessToken") .contentType(MediaType.APPLICATION_JSON) - .content("""{"username": "testUser", "password": "password"}""") + .content("""{"username": "testUser", "password": "password"}"""), ) .andExpect(MockMvcResultMatchers.status().isOk) .andExpect(MockMvcResultMatchers.jsonPath("$.data").value("testUser")) - } } diff --git a/backend/src/test/kotlin/com/lightswitch/infrastructue/security/JwtTokenFilterTest.kt b/backend/src/test/kotlin/com/lightswitch/infrastructue/security/JwtTokenFilterTest.kt index ea1c4e8..a8a9b20 100644 --- a/backend/src/test/kotlin/com/lightswitch/infrastructue/security/JwtTokenFilterTest.kt +++ b/backend/src/test/kotlin/com/lightswitch/infrastructue/security/JwtTokenFilterTest.kt @@ -16,7 +16,6 @@ import org.springframework.security.core.context.SecurityContext import org.springframework.security.core.context.SecurityContextHolder class JwtTokenFilterTest { - @Mock private lateinit var jwtTokenProvider: JwtTokenProvider diff --git a/backend/src/test/kotlin/com/lightswitch/infrastructue/security/JwtTokenProviderTest.kt b/backend/src/test/kotlin/com/lightswitch/infrastructue/security/JwtTokenProviderTest.kt index 58ecd52..fb523dd 100644 --- a/backend/src/test/kotlin/com/lightswitch/infrastructue/security/JwtTokenProviderTest.kt +++ b/backend/src/test/kotlin/com/lightswitch/infrastructue/security/JwtTokenProviderTest.kt @@ -6,23 +6,26 @@ import io.jsonwebtoken.Jwts import io.jsonwebtoken.SignatureAlgorithm import io.jsonwebtoken.io.Decoders import io.jsonwebtoken.security.Keys -import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.mockito.MockitoAnnotations -import java.util.* +import java.util.Date class JwtTokenProviderTest { - private lateinit var jwtTokenProvider: JwtTokenProvider private val secretKey = "64461f01e1af406da538b9c48d801ce59142452199ff112fb5404c8e7e98e3ff" - val user = User( - id = 1L, - username = "testUser", - passwordHash = "passwordHash", - lastLoginAt = null - ) + val user = + User( + id = 1L, + username = "testUser", + passwordHash = "passwordHash", + lastLoginAt = null, + ) @BeforeEach fun setUp() { @@ -45,7 +48,7 @@ class JwtTokenProviderTest { val jwtToken = jwtTokenProvider.generateJwtAccessToken(user.id!!, user, now, "testRefreshToken") assertNotNull(jwtToken.accessToken) - assertEquals("testRefreshToken",jwtToken.refreshToken) + assertEquals("testRefreshToken", jwtToken.refreshToken) assertTrue(jwtToken.accessTokenExpiredDate!! > 0) } @@ -92,12 +95,13 @@ class JwtTokenProviderTest { val issuedTime = Date(now.time - 5 * 24 * 60 * 60 * 1000L) val expiredTime = Date(now.time + 4 * 24 * 60 * 60 * 1000L) - val refreshToken = Jwts.builder() - .setSubject("1") - .setIssuedAt(issuedTime) - .setExpiration(expiredTime) - .signWith(Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretKey)), SignatureAlgorithm.HS256) - .compact() + val refreshToken = + Jwts.builder() + .setSubject("1") + .setIssuedAt(issuedTime) + .setExpiration(expiredTime) + .signWith(Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretKey)), SignatureAlgorithm.HS256) + .compact() val result = jwtTokenProvider.isRefreshTokenRenewalRequired(refreshToken) @@ -110,16 +114,16 @@ class JwtTokenProviderTest { val issuedTime = Date(now.time - 5 * 24 * 60 * 60 * 1000L) val expiredTime = Date(now.time + 2 * 24 * 60 * 60 * 1000L) - val refreshToken = Jwts.builder() - .setSubject("1") - .setIssuedAt(issuedTime) - .setExpiration(expiredTime) - .signWith(Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretKey)), SignatureAlgorithm.HS256) - .compact() + val refreshToken = + Jwts.builder() + .setSubject("1") + .setIssuedAt(issuedTime) + .setExpiration(expiredTime) + .signWith(Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretKey)), SignatureAlgorithm.HS256) + .compact() val result = jwtTokenProvider.isRefreshTokenRenewalRequired(refreshToken) assertTrue(result) } - } diff --git a/backend/src/test/kotlin/com/lightswitch/presentation/controller/FeatureFlagControllerTest.kt b/backend/src/test/kotlin/com/lightswitch/presentation/controller/FeatureFlagControllerTest.kt index 7bb8f9a..b641eaf 100644 --- a/backend/src/test/kotlin/com/lightswitch/presentation/controller/FeatureFlagControllerTest.kt +++ b/backend/src/test/kotlin/com/lightswitch/presentation/controller/FeatureFlagControllerTest.kt @@ -23,13 +23,12 @@ import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post import org.springframework.test.web.servlet.result.MockMvcResultHandlers.print import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status -import java.util.* +import java.util.Optional import kotlin.test.Test @WebMvcTest(controllers = [FeatureFlagController::class]) @ExtendWith(MockitoExtension::class) class FeatureFlagControllerTest { - @Autowired private lateinit var mockMvc: MockMvc @@ -49,22 +48,24 @@ class FeatureFlagControllerTest { @WithMockUser(username = "1") fun `should create feature flag successfully`() { val user = User(id = 1L, username = "username", passwordHash = "passwordHash") - val request = CreateFeatureFlagRequest( - key = "new-feature", - status = true, - type = "BOOLEAN", - defaultValue = mapOf("default" to true), - description = "Test feature flag", - ) - val flag = FeatureFlag( - id = 1L, - name = "new-feature", - description = "Test feature flag", - type = Type.BOOLEAN, - enabled = true, - createdBy = user, - updatedBy = user - ) + val request = + CreateFeatureFlagRequest( + key = "new-feature", + status = true, + type = "BOOLEAN", + defaultValue = mapOf("default" to true), + description = "Test feature flag", + ) + val flag = + FeatureFlag( + id = 1L, + name = "new-feature", + description = "Test feature flag", + type = Type.BOOLEAN, + enabled = true, + createdBy = user, + updatedBy = user, + ) val condition = Condition(id = 1L, key = "default", value = true, flag = flag) flag.defaultCondition = condition @@ -75,7 +76,7 @@ class FeatureFlagControllerTest { post("/api/v1/flags") .with(csrf()) .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(request)) + .content(objectMapper.writeValueAsString(request)), ) .andExpect(status().isOk) .andExpect(jsonPath("$.status").value("Success")) @@ -95,19 +96,20 @@ class FeatureFlagControllerTest { @Test @WithMockUser(username = "1") fun `should return 400 when request validation fails due to empty key`() { - val request = CreateFeatureFlagRequest( - key = "", - status = true, - type = "boolean", - defaultValue = mapOf("default" to true), - description = "Test feature flag", - ) + val request = + CreateFeatureFlagRequest( + key = "", + status = true, + type = "boolean", + defaultValue = mapOf("default" to true), + description = "Test feature flag", + ) mockMvc.perform( post("/api/v1/flags") .with(csrf()) .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(request)) + .content(objectMapper.writeValueAsString(request)), ) .andExpect(status().isBadRequest) } @@ -115,19 +117,20 @@ class FeatureFlagControllerTest { @Test @WithMockUser(username = "1") fun `should return 400 when request validation fails due to empty type`() { - val request = CreateFeatureFlagRequest( - key = "new-feature", - status = true, - type = "", - defaultValue = mapOf("default" to true), - description = "Test feature flag", - ) + val request = + CreateFeatureFlagRequest( + key = "new-feature", + status = true, + type = "", + defaultValue = mapOf("default" to true), + description = "Test feature flag", + ) mockMvc.perform( post("/api/v1/flags") .with(csrf()) .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(request)) + .content(objectMapper.writeValueAsString(request)), ) .andExpect(status().isBadRequest) } @@ -135,19 +138,20 @@ class FeatureFlagControllerTest { @Test @WithMockUser(username = "1") fun `should return 400 when request validation fails due to empty defaultValue`() { - val request = CreateFeatureFlagRequest( - key = "new-feature", - status = true, - type = "boolean", - defaultValue = emptyMap(), - description = "Test feature flag", - ) + val request = + CreateFeatureFlagRequest( + key = "new-feature", + status = true, + type = "boolean", + defaultValue = emptyMap(), + description = "Test feature flag", + ) mockMvc.perform( post("/api/v1/flags") .with(csrf()) .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(request)) + .content(objectMapper.writeValueAsString(request)), ) .andExpect(status().isBadRequest) } @@ -155,19 +159,20 @@ class FeatureFlagControllerTest { @Test @WithMockUser(username = "1") fun `should return 400 when request validation fails due to empty description`() { - val request = CreateFeatureFlagRequest( - key = "new-feature", - status = true, - type = "boolean", - defaultValue = mapOf("default" to true), - description = "", - ) + val request = + CreateFeatureFlagRequest( + key = "new-feature", + status = true, + type = "boolean", + defaultValue = mapOf("default" to true), + description = "", + ) mockMvc.perform( post("/api/v1/flags") .with(csrf()) .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(request)) + .content(objectMapper.writeValueAsString(request)), ) .andExpect(status().isBadRequest) } @@ -175,19 +180,20 @@ class FeatureFlagControllerTest { @Test @WithMockUser(username = "1") fun `should return 400 when request validation fails due to invalid type`() { - val request = CreateFeatureFlagRequest( - key = "new-feature", - status = true, - type = "invalid-type", - defaultValue = mapOf("default" to true), - description = "", - ) + val request = + CreateFeatureFlagRequest( + key = "new-feature", + status = true, + type = "invalid-type", + defaultValue = mapOf("default" to true), + description = "", + ) mockMvc.perform( post("/api/v1/flags") .with(csrf()) .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(request)) + .content(objectMapper.writeValueAsString(request)), ) .andExpect(status().isBadRequest) }