feat(backend): implement step 10 — JWT authentication (JwtService, AuthService, AuthController, JwtAuthFilter, SecurityConfig)
This commit is contained in:
@@ -0,0 +1,40 @@
|
|||||||
|
package com.condado.newsletter.config
|
||||||
|
|
||||||
|
import com.condado.newsletter.service.JwtService
|
||||||
|
import jakarta.servlet.FilterChain
|
||||||
|
import jakarta.servlet.http.HttpServletRequest
|
||||||
|
import jakarta.servlet.http.HttpServletResponse
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder
|
||||||
|
import org.springframework.stereotype.Component
|
||||||
|
import org.springframework.web.filter.OncePerRequestFilter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the JWT from the `jwt` cookie on each request.
|
||||||
|
* If the token is valid, sets an authenticated [SecurityContext].
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
class JwtAuthFilter(private val jwtService: JwtService) : OncePerRequestFilter() {
|
||||||
|
|
||||||
|
override fun doFilterInternal(
|
||||||
|
request: HttpServletRequest,
|
||||||
|
response: HttpServletResponse,
|
||||||
|
filterChain: FilterChain
|
||||||
|
) {
|
||||||
|
val token = request.cookies
|
||||||
|
?.firstOrNull { it.name == "jwt" }
|
||||||
|
?.value
|
||||||
|
|
||||||
|
if (token != null && jwtService.validateToken(token)) {
|
||||||
|
val auth = UsernamePasswordAuthenticationToken(
|
||||||
|
"admin",
|
||||||
|
null,
|
||||||
|
listOf(SimpleGrantedAuthority("ROLE_ADMIN"))
|
||||||
|
)
|
||||||
|
SecurityContextHolder.getContext().authentication = auth
|
||||||
|
}
|
||||||
|
|
||||||
|
filterChain.doFilter(request, response)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,24 +1,34 @@
|
|||||||
package com.condado.newsletter.config
|
package com.condado.newsletter.config
|
||||||
|
|
||||||
import org.springframework.context.annotation.Bean
|
import org.springframework.context.annotation.Bean
|
||||||
import org.springframework.context.annotation.Configuration
|
import org.springframework.context.annotation.Configuration
|
||||||
|
import org.springframework.http.HttpMethod
|
||||||
|
import org.springframework.http.HttpStatus
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||||
|
import org.springframework.security.config.http.SessionCreationPolicy
|
||||||
import org.springframework.security.web.SecurityFilterChain
|
import org.springframework.security.web.SecurityFilterChain
|
||||||
|
import org.springframework.security.web.authentication.HttpStatusEntryPoint
|
||||||
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
|
||||||
|
|
||||||
/**
|
|
||||||
* Security configuration — Step 9: permits all requests.
|
|
||||||
* Will be updated in Step 10 with JWT authentication.
|
|
||||||
*/
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
class SecurityConfig {
|
class SecurityConfig(private val jwtAuthFilter: JwtAuthFilter) {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||||
http
|
http
|
||||||
.csrf { it.disable() }
|
.csrf { it.disable() }
|
||||||
.authorizeHttpRequests { it.anyRequest().permitAll() }
|
.sessionManagement { it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) }
|
||||||
|
.exceptionHandling { it.authenticationEntryPoint(HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)) }
|
||||||
|
.authorizeHttpRequests { auth ->
|
||||||
|
auth
|
||||||
|
.requestMatchers(HttpMethod.POST, "/api/auth/login").permitAll()
|
||||||
|
.requestMatchers(HttpMethod.POST, "/api/auth/logout").permitAll()
|
||||||
|
.requestMatchers("/swagger-ui/**", "/v3/api-docs/**", "/swagger-ui.html").permitAll()
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
}
|
||||||
|
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter::class.java)
|
||||||
return http.build()
|
return http.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package com.condado.newsletter.controller
|
||||||
|
|
||||||
|
import com.condado.newsletter.dto.AuthResponse
|
||||||
|
import com.condado.newsletter.dto.LoginRequest
|
||||||
|
import com.condado.newsletter.service.AuthService
|
||||||
|
import jakarta.servlet.http.Cookie
|
||||||
|
import jakarta.servlet.http.HttpServletResponse
|
||||||
|
import org.springframework.http.ResponseEntity
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping
|
||||||
|
import org.springframework.web.bind.annotation.RestController
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles authentication — login, logout, and session check.
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/auth")
|
||||||
|
class AuthController(private val authService: AuthService) {
|
||||||
|
|
||||||
|
/** Validates the password and sets a JWT cookie on success. */
|
||||||
|
@PostMapping("/login")
|
||||||
|
fun login(
|
||||||
|
@RequestBody request: LoginRequest,
|
||||||
|
response: HttpServletResponse
|
||||||
|
): ResponseEntity<AuthResponse> {
|
||||||
|
val token = authService.login(request.password)
|
||||||
|
val cookie = Cookie("jwt", token).apply {
|
||||||
|
isHttpOnly = true
|
||||||
|
path = "/"
|
||||||
|
maxAge = 86400 // 24 hours
|
||||||
|
}
|
||||||
|
response.addCookie(cookie)
|
||||||
|
return ResponseEntity.ok(AuthResponse("Login successful"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns 200 if the JWT cookie is valid (checked by JwtAuthFilter). */
|
||||||
|
@GetMapping("/me")
|
||||||
|
fun me(): ResponseEntity<AuthResponse> =
|
||||||
|
ResponseEntity.ok(AuthResponse("Authenticated"))
|
||||||
|
|
||||||
|
/** Clears the JWT cookie. */
|
||||||
|
@PostMapping("/logout")
|
||||||
|
fun logout(response: HttpServletResponse): ResponseEntity<AuthResponse> {
|
||||||
|
val cookie = Cookie("jwt", "").apply {
|
||||||
|
isHttpOnly = true
|
||||||
|
path = "/"
|
||||||
|
maxAge = 0
|
||||||
|
}
|
||||||
|
response.addCookie(cookie)
|
||||||
|
return ResponseEntity.ok(AuthResponse("Logged out"))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.condado.newsletter.dto
|
||||||
|
|
||||||
|
/** Request body for POST /api/auth/login. */
|
||||||
|
data class LoginRequest(val password: String)
|
||||||
|
|
||||||
|
/** Generic response body for auth operations. */
|
||||||
|
data class AuthResponse(val message: String)
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.condado.newsletter.service
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Value
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the single-admin authentication flow.
|
||||||
|
* There is no user table — the password lives only in the [appPassword] environment variable.
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
class AuthService(
|
||||||
|
private val jwtService: JwtService,
|
||||||
|
@Value("\${app.password}") private val appPassword: String
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* Validates the given [password] against [appPassword].
|
||||||
|
* @return A signed JWT token string.
|
||||||
|
* @throws [UnauthorizedException] if the password is incorrect.
|
||||||
|
*/
|
||||||
|
fun login(password: String): String {
|
||||||
|
if (password != appPassword) {
|
||||||
|
throw UnauthorizedException("Invalid password")
|
||||||
|
}
|
||||||
|
return jwtService.generateToken()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package com.condado.newsletter.service
|
||||||
|
|
||||||
|
import io.jsonwebtoken.ExpiredJwtException
|
||||||
|
import io.jsonwebtoken.Jwts
|
||||||
|
import io.jsonwebtoken.security.Keys
|
||||||
|
import org.springframework.beans.factory.annotation.Value
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles JWT token creation and validation using JJWT 0.12.x.
|
||||||
|
* The secret and expiration are read from environment variables.
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
class JwtService(
|
||||||
|
@Value("\${app.jwt.secret}") val secret: String,
|
||||||
|
@Value("\${app.jwt.expiration-ms}") val expirationMs: Long
|
||||||
|
) {
|
||||||
|
private val signingKey by lazy {
|
||||||
|
Keys.hmacShaKeyFor(secret.toByteArray(Charsets.UTF_8))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Generates a new signed JWT token valid for [expirationMs] milliseconds. */
|
||||||
|
fun generateToken(): String {
|
||||||
|
val now = Date()
|
||||||
|
return Jwts.builder()
|
||||||
|
.subject("admin")
|
||||||
|
.issuedAt(now)
|
||||||
|
.expiration(Date(now.time + expirationMs))
|
||||||
|
.signWith(signingKey)
|
||||||
|
.compact()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates a JWT token.
|
||||||
|
* @return `true` if the token is valid and not expired; `false` otherwise.
|
||||||
|
*/
|
||||||
|
fun validateToken(token: String): Boolean = try {
|
||||||
|
Jwts.parser().verifyWith(signingKey).build().parseSignedClaims(token)
|
||||||
|
true
|
||||||
|
} catch (e: ExpiredJwtException) {
|
||||||
|
false
|
||||||
|
} catch (e: Exception) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.condado.newsletter.service
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus
|
||||||
|
import org.springframework.web.bind.annotation.ResponseStatus
|
||||||
|
|
||||||
|
/** Thrown when an authentication attempt fails (wrong password or missing token). */
|
||||||
|
@ResponseStatus(HttpStatus.UNAUTHORIZED)
|
||||||
|
class UnauthorizedException(message: String) : RuntimeException(message)
|
||||||
@@ -6,7 +6,9 @@ import com.condado.newsletter.model.VirtualEntity
|
|||||||
import com.condado.newsletter.repository.DispatchLogRepository
|
import com.condado.newsletter.repository.DispatchLogRepository
|
||||||
import com.condado.newsletter.repository.VirtualEntityRepository
|
import com.condado.newsletter.repository.VirtualEntityRepository
|
||||||
import com.condado.newsletter.scheduler.EntityScheduler
|
import com.condado.newsletter.scheduler.EntityScheduler
|
||||||
|
import com.condado.newsletter.service.JwtService
|
||||||
import com.ninjasquad.springmockk.MockkBean
|
import com.ninjasquad.springmockk.MockkBean
|
||||||
|
import jakarta.servlet.http.Cookie
|
||||||
import org.junit.jupiter.api.AfterEach
|
import org.junit.jupiter.api.AfterEach
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
@@ -21,17 +23,13 @@ import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
|
|||||||
@AutoConfigureMockMvc
|
@AutoConfigureMockMvc
|
||||||
class DispatchLogControllerTest {
|
class DispatchLogControllerTest {
|
||||||
|
|
||||||
@Autowired
|
@Autowired lateinit var mockMvc: MockMvc
|
||||||
lateinit var mockMvc: MockMvc
|
@Autowired lateinit var virtualEntityRepository: VirtualEntityRepository
|
||||||
|
@Autowired lateinit var dispatchLogRepository: DispatchLogRepository
|
||||||
|
@Autowired lateinit var jwtService: JwtService
|
||||||
|
@MockkBean lateinit var entityScheduler: EntityScheduler
|
||||||
|
|
||||||
@Autowired
|
private fun authCookie() = Cookie("jwt", jwtService.generateToken())
|
||||||
lateinit var virtualEntityRepository: VirtualEntityRepository
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
lateinit var dispatchLogRepository: DispatchLogRepository
|
|
||||||
|
|
||||||
@MockkBean
|
|
||||||
lateinit var entityScheduler: EntityScheduler
|
|
||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
fun cleanUp() {
|
fun cleanUp() {
|
||||||
@@ -41,42 +39,19 @@ class DispatchLogControllerTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun should_return200AndAllLogs_when_getAllLogs() {
|
fun should_return200AndAllLogs_when_getAllLogs() {
|
||||||
val entity = virtualEntityRepository.save(VirtualEntity(
|
val entity = virtualEntityRepository.save(VirtualEntity(name = "Log Entity", email = "log@condado.com", jobTitle = "Logger"))
|
||||||
name = "Log Entity",
|
dispatchLogRepository.save(DispatchLog(virtualEntity = entity, emailSubject = "Test Subject", status = DispatchStatus.SENT))
|
||||||
email = "log@condado.com",
|
mockMvc.perform(get("/api/v1/dispatch-logs").cookie(authCookie()))
|
||||||
jobTitle = "Logger"
|
.andExpect(status().isOk).andExpect(jsonPath("$").isArray).andExpect(jsonPath("$[0].emailSubject").value("Test Subject"))
|
||||||
))
|
|
||||||
dispatchLogRepository.save(DispatchLog(
|
|
||||||
virtualEntity = entity,
|
|
||||||
emailSubject = "Test Subject",
|
|
||||||
status = DispatchStatus.SENT
|
|
||||||
))
|
|
||||||
|
|
||||||
mockMvc.perform(get("/api/v1/dispatch-logs"))
|
|
||||||
.andExpect(status().isOk)
|
|
||||||
.andExpect(jsonPath("$").isArray)
|
|
||||||
.andExpect(jsonPath("$[0].emailSubject").value("Test Subject"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun should_return200AndFilteredLogs_when_getByEntityId() {
|
fun should_return200AndFilteredLogs_when_getByEntityId() {
|
||||||
val entity1 = virtualEntityRepository.save(VirtualEntity(
|
val entity1 = virtualEntityRepository.save(VirtualEntity(name = "Entity One", email = "one@condado.com", jobTitle = "Job One"))
|
||||||
name = "Entity One",
|
val entity2 = virtualEntityRepository.save(VirtualEntity(name = "Entity Two", email = "two@condado.com", jobTitle = "Job Two"))
|
||||||
email = "one@condado.com",
|
|
||||||
jobTitle = "Job One"
|
|
||||||
))
|
|
||||||
val entity2 = virtualEntityRepository.save(VirtualEntity(
|
|
||||||
name = "Entity Two",
|
|
||||||
email = "two@condado.com",
|
|
||||||
jobTitle = "Job Two"
|
|
||||||
))
|
|
||||||
dispatchLogRepository.save(DispatchLog(virtualEntity = entity1, emailSubject = "Log One", status = DispatchStatus.SENT))
|
dispatchLogRepository.save(DispatchLog(virtualEntity = entity1, emailSubject = "Log One", status = DispatchStatus.SENT))
|
||||||
dispatchLogRepository.save(DispatchLog(virtualEntity = entity2, emailSubject = "Log Two", status = DispatchStatus.FAILED))
|
dispatchLogRepository.save(DispatchLog(virtualEntity = entity2, emailSubject = "Log Two", status = DispatchStatus.FAILED))
|
||||||
|
mockMvc.perform(get("/api/v1/dispatch-logs/entity/${entity1.id}").cookie(authCookie()))
|
||||||
mockMvc.perform(get("/api/v1/dispatch-logs/entity/${entity1.id}"))
|
.andExpect(status().isOk).andExpect(jsonPath("$.length()").value(1)).andExpect(jsonPath("$[0].emailSubject").value("Log One"))
|
||||||
.andExpect(status().isOk)
|
|
||||||
.andExpect(jsonPath("$").isArray)
|
|
||||||
.andExpect(jsonPath("$.length()").value(1))
|
|
||||||
.andExpect(jsonPath("$[0].emailSubject").value("Log One"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ package com.condado.newsletter.controller
|
|||||||
import com.condado.newsletter.model.VirtualEntity
|
import com.condado.newsletter.model.VirtualEntity
|
||||||
import com.condado.newsletter.repository.VirtualEntityRepository
|
import com.condado.newsletter.repository.VirtualEntityRepository
|
||||||
import com.condado.newsletter.scheduler.EntityScheduler
|
import com.condado.newsletter.scheduler.EntityScheduler
|
||||||
|
import com.condado.newsletter.service.JwtService
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
import com.ninjasquad.springmockk.MockkBean
|
import com.ninjasquad.springmockk.MockkBean
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.just
|
import io.mockk.just
|
||||||
import io.mockk.runs
|
import io.mockk.runs
|
||||||
import io.mockk.verify
|
import io.mockk.verify
|
||||||
|
import jakarta.servlet.http.Cookie
|
||||||
import org.junit.jupiter.api.AfterEach
|
import org.junit.jupiter.api.AfterEach
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
@@ -27,135 +29,72 @@ import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
|
|||||||
@AutoConfigureMockMvc
|
@AutoConfigureMockMvc
|
||||||
class VirtualEntityControllerTest {
|
class VirtualEntityControllerTest {
|
||||||
|
|
||||||
@Autowired
|
@Autowired lateinit var mockMvc: MockMvc
|
||||||
lateinit var mockMvc: MockMvc
|
@Autowired lateinit var virtualEntityRepository: VirtualEntityRepository
|
||||||
|
@Autowired lateinit var jwtService: JwtService
|
||||||
@Autowired
|
@MockkBean lateinit var entityScheduler: EntityScheduler
|
||||||
lateinit var virtualEntityRepository: VirtualEntityRepository
|
|
||||||
|
|
||||||
@MockkBean
|
|
||||||
lateinit var entityScheduler: EntityScheduler
|
|
||||||
|
|
||||||
private val objectMapper = ObjectMapper()
|
private val objectMapper = ObjectMapper()
|
||||||
|
|
||||||
|
private fun authCookie() = Cookie("jwt", jwtService.generateToken())
|
||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
fun cleanUp() {
|
fun cleanUp() { virtualEntityRepository.deleteAll() }
|
||||||
virtualEntityRepository.deleteAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun should_return201AndBody_when_postWithValidPayload() {
|
fun should_return201AndBody_when_postWithValidPayload() {
|
||||||
val payload = mapOf(
|
val payload = mapOf("name" to "Fulano da Silva", "email" to "fulano@condado.com", "jobTitle" to "Diretor de Nada")
|
||||||
"name" to "Fulano da Silva",
|
mockMvc.perform(post("/api/v1/virtual-entities").cookie(authCookie()).contentType(MediaType.APPLICATION_JSON).content(objectMapper.writeValueAsString(payload)))
|
||||||
"email" to "fulano@condado.com",
|
.andExpect(status().isCreated).andExpect(jsonPath("$.name").value("Fulano da Silva")).andExpect(jsonPath("$.id").isNotEmpty)
|
||||||
"jobTitle" to "Diretor de Nada"
|
|
||||||
)
|
|
||||||
|
|
||||||
mockMvc.perform(
|
|
||||||
post("/api/v1/virtual-entities")
|
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
|
||||||
.content(objectMapper.writeValueAsString(payload))
|
|
||||||
)
|
|
||||||
.andExpect(status().isCreated)
|
|
||||||
.andExpect(jsonPath("$.name").value("Fulano da Silva"))
|
|
||||||
.andExpect(jsonPath("$.email").value("fulano@condado.com"))
|
|
||||||
.andExpect(jsonPath("$.jobTitle").value("Diretor de Nada"))
|
|
||||||
.andExpect(jsonPath("$.id").isNotEmpty)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun should_return400_when_postWithMissingRequiredField() {
|
fun should_return400_when_postWithMissingRequiredField() {
|
||||||
val payload = mapOf("name" to "Fulano") // missing email and jobTitle
|
val payload = mapOf("name" to "Fulano")
|
||||||
|
mockMvc.perform(post("/api/v1/virtual-entities").cookie(authCookie()).contentType(MediaType.APPLICATION_JSON).content(objectMapper.writeValueAsString(payload)))
|
||||||
mockMvc.perform(
|
|
||||||
post("/api/v1/virtual-entities")
|
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
|
||||||
.content(objectMapper.writeValueAsString(payload))
|
|
||||||
)
|
|
||||||
.andExpect(status().isBadRequest)
|
.andExpect(status().isBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun should_return200AndList_when_getAllEntities() {
|
fun should_return200AndList_when_getAllEntities() {
|
||||||
virtualEntityRepository.save(VirtualEntity(
|
virtualEntityRepository.save(VirtualEntity(name = "Test Entity", email = "test@condado.com", jobTitle = "Tester"))
|
||||||
name = "Test Entity",
|
mockMvc.perform(get("/api/v1/virtual-entities").cookie(authCookie()))
|
||||||
email = "test@condado.com",
|
.andExpect(status().isOk).andExpect(jsonPath("$").isArray).andExpect(jsonPath("$[0].name").value("Test Entity"))
|
||||||
jobTitle = "Tester"
|
|
||||||
))
|
|
||||||
|
|
||||||
mockMvc.perform(get("/api/v1/virtual-entities"))
|
|
||||||
.andExpect(status().isOk)
|
|
||||||
.andExpect(jsonPath("$").isArray)
|
|
||||||
.andExpect(jsonPath("$[0].name").value("Test Entity"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun should_return200AndEntity_when_getById() {
|
fun should_return200AndEntity_when_getById() {
|
||||||
val entity = virtualEntityRepository.save(VirtualEntity(
|
val entity = virtualEntityRepository.save(VirtualEntity(name = "Test Entity", email = "entity@condado.com", jobTitle = "Test Job"))
|
||||||
name = "Test Entity",
|
mockMvc.perform(get("/api/v1/virtual-entities/${entity.id}").cookie(authCookie()))
|
||||||
email = "entity@condado.com",
|
.andExpect(status().isOk).andExpect(jsonPath("$.name").value("Test Entity"))
|
||||||
jobTitle = "Test Job"
|
|
||||||
))
|
|
||||||
|
|
||||||
mockMvc.perform(get("/api/v1/virtual-entities/${entity.id}"))
|
|
||||||
.andExpect(status().isOk)
|
|
||||||
.andExpect(jsonPath("$.name").value("Test Entity"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun should_return404_when_getByIdNotFound() {
|
fun should_return404_when_getByIdNotFound() {
|
||||||
val randomId = java.util.UUID.randomUUID()
|
mockMvc.perform(get("/api/v1/virtual-entities/${java.util.UUID.randomUUID()}").cookie(authCookie()))
|
||||||
|
|
||||||
mockMvc.perform(get("/api/v1/virtual-entities/$randomId"))
|
|
||||||
.andExpect(status().isNotFound)
|
.andExpect(status().isNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun should_return200_when_putWithValidPayload() {
|
fun should_return200_when_putWithValidPayload() {
|
||||||
val entity = virtualEntityRepository.save(VirtualEntity(
|
val entity = virtualEntityRepository.save(VirtualEntity(name = "Old Name", email = "old@condado.com", jobTitle = "Old Job"))
|
||||||
name = "Old Name",
|
mockMvc.perform(put("/api/v1/virtual-entities/${entity.id}").cookie(authCookie()).contentType(MediaType.APPLICATION_JSON).content("""{"name":"New Name"}"""))
|
||||||
email = "old@condado.com",
|
.andExpect(status().isOk).andExpect(jsonPath("$.name").value("New Name")).andExpect(jsonPath("$.email").value("old@condado.com"))
|
||||||
jobTitle = "Old Job"
|
|
||||||
))
|
|
||||||
|
|
||||||
val payload = mapOf("name" to "New Name")
|
|
||||||
|
|
||||||
mockMvc.perform(
|
|
||||||
put("/api/v1/virtual-entities/${entity.id}")
|
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
|
||||||
.content(objectMapper.writeValueAsString(payload))
|
|
||||||
)
|
|
||||||
.andExpect(status().isOk)
|
|
||||||
.andExpect(jsonPath("$.name").value("New Name"))
|
|
||||||
.andExpect(jsonPath("$.email").value("old@condado.com"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun should_return200AndDeactivated_when_delete() {
|
fun should_return200AndDeactivated_when_delete() {
|
||||||
val entity = virtualEntityRepository.save(VirtualEntity(
|
val entity = virtualEntityRepository.save(VirtualEntity(name = "Active Entity", email = "active@condado.com", jobTitle = "Active Job"))
|
||||||
name = "Active Entity",
|
mockMvc.perform(delete("/api/v1/virtual-entities/${entity.id}").cookie(authCookie()))
|
||||||
email = "active@condado.com",
|
.andExpect(status().isOk).andExpect(jsonPath("$.active").value(false))
|
||||||
jobTitle = "Active Job"
|
|
||||||
))
|
|
||||||
|
|
||||||
mockMvc.perform(delete("/api/v1/virtual-entities/${entity.id}"))
|
|
||||||
.andExpect(status().isOk)
|
|
||||||
.andExpect(jsonPath("$.active").value(false))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun should_return200_when_triggerEndpointCalled() {
|
fun should_return200_when_triggerEndpointCalled() {
|
||||||
val entity = virtualEntityRepository.save(VirtualEntity(
|
val entity = virtualEntityRepository.save(VirtualEntity(name = "Trigger Entity", email = "trigger@condado.com", jobTitle = "Trigger Job"))
|
||||||
name = "Trigger Entity",
|
|
||||||
email = "trigger@condado.com",
|
|
||||||
jobTitle = "Trigger Job"
|
|
||||||
))
|
|
||||||
|
|
||||||
every { entityScheduler.runPipeline(any()) } just runs
|
every { entityScheduler.runPipeline(any()) } just runs
|
||||||
|
mockMvc.perform(post("/api/v1/virtual-entities/${entity.id}/trigger").cookie(authCookie()))
|
||||||
mockMvc.perform(post("/api/v1/virtual-entities/${entity.id}/trigger"))
|
|
||||||
.andExpect(status().isOk)
|
.andExpect(status().isOk)
|
||||||
|
|
||||||
verify(exactly = 1) { entityScheduler.runPipeline(any()) }
|
verify(exactly = 1) { entityScheduler.runPipeline(any()) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user