Skip to content

Commit

Permalink
Add API for fetching /combine target vehicle schedule frame
Browse files Browse the repository at this point in the history
Similar to the /to-replace API (from which this is largely copypasted)
that already existed for /replace.
  • Loading branch information
Leitsi committed Dec 4, 2023
1 parent 99e9cba commit 570735f
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 0 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,18 @@ When the submodule is updated, to get the newest version of inserter you need to
]
}
```
- `GET /timetables/to-combine`: Fetch the vehicle schedule frame ID slated to be the target for combine,
considering the combine action with the staging vehicle schedule frame ID and target priority.
- Request params:
- `stagingVehicleScheduleFrameId` The ID of the staging vehicle schedule frame. Example: `"50f939b0-aac3-453a-b2f5-24c0cdf8ad21"`
- `targetPriority` The priority to which the staging timetables will be promoted. Example: `10`

Example response body:
```JSON
{
"toCombineTargetVehicleScheduleFrameId": "d3d0aea6-db3f-4421-b4eb-39cffe8835a8"
}
```
## Technical Documentation

jore4-timetables-api is a Spring Boot application written in Kotlin, which implements a REST API for accessing the timetables database and creating more complicated updates in one transaction than is possible with the graphQL interface.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ class TimetablesController(
val toReplaceVehicleScheduleFrameIds: List<UUID>
)

data class ToCombineTimetablesResponseBody(
val toCombineTargetVehicleScheduleFrameId: UUID
)

class TargetPriorityParsingException(message: String, val targetPriority: Int) : RuntimeException(message)

@GetMapping("to-replace")
Expand All @@ -117,6 +121,34 @@ class TimetablesController(
.body(ToReplaceTimetablesResponseBody(toReplaceVehicleScheduleFrameIds = vehicleScheduleFrameIds))
}

@GetMapping("to-combine")
fun getTargetFrameIdsForCombine(
@RequestParam
targetPriority: Int,
@RequestParam
stagingVehicleScheduleFrameId: UUID
): ResponseEntity<ToCombineTimetablesResponseBody> {
LOGGER.info { "ToCombine api, stagingVehicleScheduleFrameId: $stagingVehicleScheduleFrameId, targetPriority: $targetPriority" }

val targetPriorityEnumResult = runCatching { TimetablesPriority.fromInt(targetPriority) }
if (targetPriorityEnumResult.isFailure) {
throw TargetPriorityParsingException("Failed to parse target priority", targetPriority)
}

val targetVehicleScheduleFrame = combineTimetablesService.fetchTargetVehicleScheduleFrame(
stagingVehicleScheduleFrameId,
targetPriorityEnumResult.getOrThrow()
)

return ResponseEntity.status(HttpStatus.OK)
.body(
ToCombineTimetablesResponseBody(
// ID of an existing row, can never be null.
toCombineTargetVehicleScheduleFrameId = targetVehicleScheduleFrame.vehicleScheduleFrameId!!
)
)
}

@ExceptionHandler(RuntimeException::class)
fun handleRuntimeException(ex: RuntimeException): ResponseEntity<HasuraErrorResponse> {
val hasuraExtensions: HasuraErrorExtensions = when (ex) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class WebSecurityConfig {
HttpMethod.GET,
"/actuator/health",
"/error",
"/timetables/to-combine",
"/timetables/to-replace"
)
.permitAll()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@ class CombineTimetablesService(
return targetVehicleScheduleFrame.vehicleScheduleFrameId!! // ID of an existing row, can never be null.
}

@Transactional(readOnly = true)
fun fetchTargetVehicleScheduleFrame(
stagingVehicleScheduleFrameId: UUID,
targetPriority: TimetablesPriority
): VehicleScheduleFrame {
val stagingVehicleScheduleFrame = fetchStagingVehicleScheduleFrame(stagingVehicleScheduleFrameId)
return fetchTargetVehicleScheduleFrame(stagingVehicleScheduleFrame, targetPriority)
}

private fun fetchTargetVehicleScheduleFrame(
stagingVehicleScheduleFrame: VehicleScheduleFrame,
targetPriority: TimetablesPriority
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package fi.hsl.jore4.timetables.api

import com.ninjasquad.springmockk.MockkBean
import fi.hsl.jore.jore4.jooq.vehicle_schedule.tables.pojos.VehicleScheduleFrame
import fi.hsl.jore4.timetables.enumerated.TimetablesPriority
import fi.hsl.jore4.timetables.service.CombineTimetablesService
import io.mockk.every
import io.mockk.junit5.MockKExtension
import io.mockk.verify
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.http.MediaType
import org.springframework.test.context.ActiveProfiles
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.ResultActions
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.content
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import java.time.LocalDate
import java.util.UUID

@ExtendWith(MockKExtension::class)
@AutoConfigureMockMvc
@SpringBootTest
@ActiveProfiles("test")
class TimetablesToCombineApiTest(@Autowired val mockMvc: MockMvc) {
@MockkBean
private lateinit var combineTimetablesService: CombineTimetablesService

private val defaultTargetFrame = VehicleScheduleFrame(
vehicleScheduleFrameId = UUID.fromString("379076ee-d595-47e3-8050-2610d594b57c"),
validityStart = LocalDate.now(),
validityEnd = LocalDate.now(),
priority = 20,
label = "label"
)
private val defaultToCombineTargetId = defaultTargetFrame.vehicleScheduleFrameId
private fun executeToCombineTimetablesRequest(
stagingFrameId: UUID,
targetPriority: Int
): ResultActions {
return mockMvc.perform(
MockMvcRequestBuilders.get("/timetables/to-combine")
.contentType(MediaType.APPLICATION_JSON)
.param("stagingVehicleScheduleFrameId", stagingFrameId.toString())
.param("targetPriority", targetPriority.toString())
)
}

@Test
fun `returns 200 and correct response when called successfully`() {
val stagingVehicleScheduleFrameId = UUID.fromString("81f109d1-dbe2-412a-996e-aa510416b2e4")
val targetPriority = 10

every {
combineTimetablesService.fetchTargetVehicleScheduleFrame(
stagingVehicleScheduleFrameId,
TimetablesPriority.fromInt(targetPriority)
)
} answers { defaultTargetFrame }

executeToCombineTimetablesRequest(stagingVehicleScheduleFrameId, targetPriority)
.andExpect(status().isOk)
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(
content().json(
"""
{
"toCombineTargetVehicleScheduleFrameId": $defaultToCombineTargetId
}
""".trimIndent(),
true
)
)

verify(exactly = 1) {
combineTimetablesService.fetchTargetVehicleScheduleFrame(
stagingVehicleScheduleFrameId,
TimetablesPriority.fromInt(targetPriority)
)
}
}

@Test
fun `throws a 400 error when parsing target priority fails`() {
val errorMessage = "Failed to parse target priority"
val stagingVehicleScheduleFrameId = UUID.fromString("023281cd-51e9-4544-a2af-7b7e268e3a3a")
val invalidTargetPriorityInput = 9999

executeToCombineTimetablesRequest(stagingVehicleScheduleFrameId, invalidTargetPriorityInput)
.andExpect(status().isBadRequest)
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(
content().json(
"""
{
"message": "$errorMessage",
"extensions": {
"code": 400,
"type": "TargetPriorityParsingError",
"targetPriority": $invalidTargetPriorityInput
}
}
""".trimIndent(),
true
)
)
verify(exactly = 0) {
combineTimetablesService.fetchTargetVehicleScheduleFrame(
stagingVehicleScheduleFrameId,
any()
)
}
}
}

0 comments on commit 570735f

Please sign in to comment.