pcp: Ф2 — черновик-клон миссии (replace + fork-forward)
mission-planing: POST /api/missions/{id}/clone — полный клон (replace)
или по перекрытию с окном (fork-forward, [start,stop) открытый правый конец);
пустые drop-mode (все съёмки вне окна) не клонируются
tgu-service: PlannedPlanStatus.DRAFT; POST /api/plans/{planId}/draft
(CreateDraftUseCase: guard replace/fork-forward, clone mission → planId=newMissionId,
компенсация при сбое); DRAFT отфильтрован из PlanQueryService read-пути;
MissionPlaningClient + MissionPlaningProperties + mission-planing.base-url в yaml
тесты: MissionCloneTest (6 граничных кейсов предиката перекрытия),
PlanQueryServiceTest (DRAFT не видна в ответе)
This commit is contained in:
+15
@@ -0,0 +1,15 @@
|
||||
package space.nstart.pcp.pcp_mission_planing_service.controller
|
||||
|
||||
import java.time.LocalDateTime
|
||||
import java.util.UUID
|
||||
|
||||
data class MissionCloneRequest(
|
||||
/** "replace" — полный клон; "fork-forward" — только включения, перекрывающиеся с окном. */
|
||||
val mode: String,
|
||||
/** Начало окна нового плана (UTC naive). Обязательно для fork-forward. */
|
||||
val windowStart: LocalDateTime? = null,
|
||||
/** Конец окна нового плана (UTC naive). Обязательно для fork-forward. */
|
||||
val windowStop: LocalDateTime? = null
|
||||
)
|
||||
|
||||
data class MissionCloneResponse(val newMissionId: UUID)
|
||||
+12
@@ -228,4 +228,16 @@ class MissionController(
|
||||
missionPlaningService.delete(id)
|
||||
return ResponseEntity.noContent().build()
|
||||
}
|
||||
|
||||
@Operation(
|
||||
summary = "Клонировать миссию",
|
||||
description = "Создаёт полный (replace) или частичный (fork-forward) клон миссии с новым missionId. " +
|
||||
"Для fork-forward клонируются только включения, перекрывающиеся с окном [windowStart, windowStop). " +
|
||||
"Возвращает newMissionId, который tgu-service использует как planId черновика."
|
||||
)
|
||||
@PostMapping("/{mission_id}/clone")
|
||||
fun cloneMission(
|
||||
@PathVariable("mission_id") id: UUID,
|
||||
@RequestBody body: MissionCloneRequest
|
||||
): MissionCloneResponse = missionPlaningService.cloneMission(id, body)
|
||||
}
|
||||
|
||||
+113
-1
@@ -29,6 +29,8 @@ import space.nstart.pcp.pcp_types_lib.dto.satellite.mission.SurveyModeResponseDT
|
||||
import space.nstart.pcp.pcp_types_lib.dto.satellite.mission.SurveyRouteAnchor
|
||||
import space.nstart.pcp.pcp_types_lib.dto.satellite.mission.SurveyRouteBuildRequestDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.satellite.mission.SurveyRouteBuildResponseDTO
|
||||
import space.nstart.pcp.pcp_mission_planing_service.controller.MissionCloneRequest
|
||||
import space.nstart.pcp.pcp_mission_planing_service.controller.MissionCloneResponse
|
||||
import space.nstart.pcp.pcp_mission_planing_service.entity.DropModeEntity
|
||||
import space.nstart.pcp.pcp_mission_planing_service.entity.DropModeSurveyEntity
|
||||
import space.nstart.pcp.pcp_mission_planing_service.entity.MissionEntity
|
||||
@@ -125,6 +127,112 @@ class MissionPlaningService(
|
||||
missionRepository.deleteById(id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Клонирует миссию и все её включения в новый `missionId`.
|
||||
*
|
||||
* Режим `replace` — полный глубокий клон: Mission + Plan + все Mode (SurveyMode/DropMode).
|
||||
* Режим `fork-forward` — клонирует только включения, чей интервал `[timeStart, timeStart+duration)`
|
||||
* пересекается с окном `[windowStart, windowStop)`. DropMode клонируется только если хотя бы одна
|
||||
* из его связанных съёмок тоже попала в клон (иначе сброс становится пустым).
|
||||
*
|
||||
* Новый `missionId` передаётся в ответе; он используется как `planId` черновика в pcp-tgu-service
|
||||
* (инвариант planId == missionId сохраняется).
|
||||
*/
|
||||
@Transactional
|
||||
fun cloneMission(sourceMissionId: UUID, req: MissionCloneRequest): MissionCloneResponse {
|
||||
val isForkForward = req.mode == "fork-forward"
|
||||
if (isForkForward && (req.windowStart == null || req.windowStop == null)) {
|
||||
throw ResponseStatusException(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"windowStart and windowStop are required for fork-forward clone"
|
||||
)
|
||||
}
|
||||
|
||||
val source = missionRepository.findById(sourceMissionId).orElseThrow { notFound(sourceMissionId) }
|
||||
val allModes = modeRepository.findAllByPlan_Mission_MissionIdOrderByTimeStartAsc(sourceMissionId)
|
||||
|
||||
val modesToClone: List<ModeEntity> = if (isForkForward) {
|
||||
allModes.filter { mode -> mode.intersects(req.windowStart!!, req.windowStop!!) }
|
||||
} else {
|
||||
allModes
|
||||
}
|
||||
|
||||
val (newMissionStart, newMissionStop) = if (isForkForward) {
|
||||
req.windowStart!! to req.windowStop!!
|
||||
} else {
|
||||
source.missionStart to source.missionStop
|
||||
}
|
||||
|
||||
val newMission = missionRepository.save(
|
||||
MissionEntity(
|
||||
satelliteId = source.satelliteId,
|
||||
station = source.station,
|
||||
missionStart = newMissionStart,
|
||||
missionStop = newMissionStop,
|
||||
status = "NEW"
|
||||
)
|
||||
)
|
||||
|
||||
val newPlan = planRepository.save(
|
||||
PlanEntity(
|
||||
mission = newMission,
|
||||
planStart = newMissionStart,
|
||||
planStop = newMissionStop,
|
||||
planNumber = 1
|
||||
)
|
||||
)
|
||||
|
||||
// Клонируем съёмки и строим маппинг старый id → новая сущность для перевязки сбросов.
|
||||
val surveyIdMapping = mutableMapOf<Long, SurveyModeEntity>()
|
||||
modesToClone.filterIsInstance<SurveyModeEntity>().forEach { src ->
|
||||
val cloned = surveyModeRepository.save(
|
||||
SurveyModeEntity(
|
||||
plan = newPlan,
|
||||
timeStart = src.timeStart,
|
||||
revolution = src.revolution,
|
||||
lat = src.lat,
|
||||
long = src.long,
|
||||
duration = src.duration,
|
||||
contourWkt = src.contourWkt,
|
||||
roll = src.roll,
|
||||
status = SurveyModeStatus.PLANNED,
|
||||
source = src.source
|
||||
)
|
||||
)
|
||||
src.id?.let { surveyIdMapping[it] = cloned }
|
||||
}
|
||||
|
||||
// Клонируем сбросы; пропускаем если все их связанные съёмки вышли за окно (пустой сброс).
|
||||
modesToClone.filterIsInstance<DropModeEntity>().forEach { src ->
|
||||
val srcId = src.id ?: return@forEach
|
||||
val links = dropModeSurveyRepository.findAllByDropMode_IdOrderBySurveyMode_TimeStartAsc(srcId)
|
||||
val remappedSurveys = links.mapNotNull { link -> link.surveyMode?.id?.let { surveyIdMapping[it] } }
|
||||
if (remappedSurveys.isEmpty()) {
|
||||
return@forEach
|
||||
}
|
||||
val newDrop = dropModeRepository.save(
|
||||
DropModeEntity(
|
||||
plan = newPlan,
|
||||
timeStart = src.timeStart,
|
||||
revolution = src.revolution,
|
||||
station = src.station,
|
||||
duration = src.duration
|
||||
)
|
||||
)
|
||||
remappedSurveys.forEach { survey ->
|
||||
dropModeSurveyRepository.save(DropModeSurveyEntity(dropMode = newDrop, surveyMode = survey))
|
||||
}
|
||||
}
|
||||
|
||||
val clonedDropCount = modesToClone.count { it is DropModeEntity }
|
||||
logger.info(
|
||||
"Клонирована миссия {}: mode={}, новый id={}, съёмок={}, сбросов={}",
|
||||
sourceMissionId, req.mode, newMission.missionId, surveyIdMapping.size, clonedDropCount
|
||||
)
|
||||
|
||||
return MissionCloneResponse(newMissionId = newMission.missionId)
|
||||
}
|
||||
|
||||
fun modesByMission(id: UUID): List<ModeResponseDTO> {
|
||||
if (!missionRepository.existsById(id)) {
|
||||
throw notFound(id)
|
||||
@@ -199,10 +307,14 @@ class MissionPlaningService(
|
||||
private fun OffsetDateTime.toUtcLocalDateTime(): LocalDateTime =
|
||||
withOffsetSameInstant(ZoneOffset.UTC).toLocalDateTime()
|
||||
|
||||
/**
|
||||
* Пересечение интервала включения с окном `[intervalStart, intervalStop)` (правый конец открыт).
|
||||
* Применяется при fork-forward-клоне: включения строго вне окна не клонируются.
|
||||
*/
|
||||
private fun ModeEntity.intersects(intervalStart: LocalDateTime, intervalStop: LocalDateTime): Boolean {
|
||||
val modeStart = timeStart?.toUtcLocalDateTime() ?: return false
|
||||
val modeStop = modeStop() ?: return false
|
||||
return !modeStop.isBefore(intervalStart) && !modeStart.isAfter(intervalStop)
|
||||
return modeStop.isAfter(intervalStart) && modeStart.isBefore(intervalStop)
|
||||
}
|
||||
|
||||
private fun ModeEntity.modeStop(): LocalDateTime? {
|
||||
|
||||
+280
@@ -0,0 +1,280 @@
|
||||
package space.nstart.pcp.pcp_mission_planing_service.service
|
||||
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
import org.mockito.Mockito.mock
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.springframework.web.server.ResponseStatusException
|
||||
import space.nstart.pcp.pcp_mission_planing_service.controller.MissionCloneRequest
|
||||
import space.nstart.pcp.pcp_mission_planing_service.entity.DropModeEntity
|
||||
import space.nstart.pcp.pcp_mission_planing_service.entity.DropModeSurveyEntity
|
||||
import space.nstart.pcp.pcp_mission_planing_service.entity.MissionEntity
|
||||
import space.nstart.pcp.pcp_mission_planing_service.entity.PlanEntity
|
||||
import space.nstart.pcp.pcp_mission_planing_service.entity.SurveyModeEntity
|
||||
import space.nstart.pcp.pcp_mission_planing_service.entity.SurveyModeSource
|
||||
import space.nstart.pcp.pcp_mission_planing_service.repository.DropModeRepository
|
||||
import space.nstart.pcp.pcp_mission_planing_service.repository.DropModeSurveyRepository
|
||||
import space.nstart.pcp.pcp_mission_planing_service.repository.MissionRepository
|
||||
import space.nstart.pcp.pcp_mission_planing_service.repository.ModeBookingRepository
|
||||
import space.nstart.pcp.pcp_mission_planing_service.repository.ModeRepository
|
||||
import space.nstart.pcp.pcp_mission_planing_service.repository.PlanRepository
|
||||
import space.nstart.pcp.pcp_mission_planing_service.repository.SurveyModeRepository
|
||||
import space.nstart.pcp.pcp_mission_planing_service.service.satellite_type.DefaultSatelliteMissionType
|
||||
import space.nstart.pcp.pcp_mission_planing_service.service.satellite_type.EmissioSatelliteMissionType
|
||||
import space.nstart.pcp.pcp_mission_planing_service.service.satellite_type.ReflixioSatelliteMissionType
|
||||
import space.nstart.pcp.pcp_mission_planing_service.service.satellite_type.SatelliteMissionTypeFactory
|
||||
import space.nstart.pcp.pcp_types_lib.dto.routes.SurveyModeStatus
|
||||
import java.time.LocalDateTime
|
||||
import java.time.OffsetDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.util.Optional
|
||||
import java.util.UUID
|
||||
|
||||
class MissionCloneTest {
|
||||
|
||||
private val missionStore = linkedMapOf<UUID, MissionEntity>()
|
||||
private val planStore = linkedMapOf<Long, PlanEntity>()
|
||||
private val surveyStore = linkedMapOf<Long, SurveyModeEntity>()
|
||||
private val dropStore = linkedMapOf<Long, DropModeEntity>()
|
||||
private val dropSurveyStore = mutableListOf<DropModeSurveyEntity>()
|
||||
|
||||
private var missionAutoId = 1L
|
||||
private var planAutoId = 100L
|
||||
private var modeAutoId = 10L
|
||||
|
||||
private val missionRepository = mock(MissionRepository::class.java).also { repo ->
|
||||
`when`(repo.findById(anyUUID())).thenAnswer { inv ->
|
||||
Optional.ofNullable(missionStore[inv.arguments[0]])
|
||||
}
|
||||
`when`(repo.save(anyNonNull<MissionEntity>())).thenAnswer { inv ->
|
||||
val m = inv.arguments[0] as MissionEntity
|
||||
missionStore[m.missionId] = m
|
||||
m
|
||||
}
|
||||
}
|
||||
|
||||
private val planRepository = mock(PlanRepository::class.java).also { repo ->
|
||||
`when`(repo.save(anyNonNull<PlanEntity>())).thenAnswer { inv ->
|
||||
val p = inv.arguments[0] as PlanEntity
|
||||
val id = ++planAutoId
|
||||
val saved = PlanEntity(id = id, mission = p.mission, planNumber = p.planNumber, planStart = p.planStart, planStop = p.planStop)
|
||||
planStore[id] = saved
|
||||
saved
|
||||
}
|
||||
}
|
||||
|
||||
private val modeRepository = mock(ModeRepository::class.java).also { repo ->
|
||||
`when`(repo.findAllByPlan_Mission_MissionIdOrderByTimeStartAsc(anyUUID())).thenAnswer { inv ->
|
||||
val missionId = inv.arguments[0] as UUID
|
||||
(surveyStore.values.filter { it.plan?.mission?.missionId == missionId } +
|
||||
dropStore.values.filter { it.plan?.mission?.missionId == missionId })
|
||||
}
|
||||
}
|
||||
|
||||
private val surveyModeRepository = mock(SurveyModeRepository::class.java).also { repo ->
|
||||
`when`(repo.save(anyNonNull<SurveyModeEntity>())).thenAnswer { inv ->
|
||||
val s = inv.arguments[0] as SurveyModeEntity
|
||||
val id = ++modeAutoId
|
||||
val saved = SurveyModeEntity(id = id, plan = s.plan, timeStart = s.timeStart, revolution = s.revolution,
|
||||
lat = s.lat, long = s.long, duration = s.duration, contourWkt = s.contourWkt, roll = s.roll,
|
||||
status = s.status, source = s.source)
|
||||
surveyStore[id] = saved
|
||||
saved
|
||||
}
|
||||
}
|
||||
|
||||
private val dropModeRepository = mock(DropModeRepository::class.java).also { repo ->
|
||||
`when`(repo.save(anyNonNull<DropModeEntity>())).thenAnswer { inv ->
|
||||
val d = inv.arguments[0] as DropModeEntity
|
||||
val id = ++modeAutoId
|
||||
val saved = DropModeEntity(id = id, plan = d.plan, timeStart = d.timeStart, revolution = d.revolution,
|
||||
station = d.station, duration = d.duration)
|
||||
dropStore[id] = saved
|
||||
saved
|
||||
}
|
||||
}
|
||||
|
||||
private val dropModeSurveyRepository = mock(DropModeSurveyRepository::class.java).also { repo ->
|
||||
`when`(repo.findAllByDropMode_IdOrderBySurveyMode_TimeStartAsc(anyLong())).thenAnswer { inv ->
|
||||
val dropId = inv.arguments[0] as Long
|
||||
dropSurveyStore.filter { it.dropMode?.id == dropId }
|
||||
}
|
||||
`when`(repo.save(anyNonNull<DropModeSurveyEntity>())).thenAnswer { inv ->
|
||||
val link = inv.arguments[0] as DropModeSurveyEntity
|
||||
dropSurveyStore.add(link)
|
||||
link
|
||||
}
|
||||
}
|
||||
|
||||
private val service = MissionPlaningService(
|
||||
missionRepository = missionRepository,
|
||||
modeRepository = modeRepository,
|
||||
planRepository = planRepository,
|
||||
surveyModeRepository = surveyModeRepository,
|
||||
dropModeRepository = dropModeRepository,
|
||||
dropModeSurveyRepository = dropModeSurveyRepository,
|
||||
modeBookingRepository = mock(ModeBookingRepository::class.java),
|
||||
bookedSlotsEventPublisher = mock(BookedSlotsEventPublisher::class.java),
|
||||
ballisticsService = mock(BallisticsService::class.java),
|
||||
complexMissionService = mock(ComplexMissionService::class.java),
|
||||
satelliteCatalogClient = mock(space.nstart.pcp.pcp_mission_planing_service.service.catalog.SatelliteCatalogClient::class.java),
|
||||
satelliteMissionTypeFactory = SatelliteMissionTypeFactory(
|
||||
listOf(DefaultSatelliteMissionType(), EmissioSatelliteMissionType(), ReflixioSatelliteMissionType()),
|
||||
DefaultSatelliteMissionType()
|
||||
)
|
||||
)
|
||||
|
||||
// --- replace: полный клон ---
|
||||
|
||||
@Test
|
||||
fun `replace clone copies all survey and drop modes with remapped survey links`() {
|
||||
val (missionId, _, survey1, survey2, drop) = buildSourceMission(t(1), t(5))
|
||||
addDropLink(drop, survey1)
|
||||
addDropLink(drop, survey2)
|
||||
|
||||
val result = service.cloneMission(missionId, MissionCloneRequest(mode = "replace"))
|
||||
|
||||
val clonedSurveys = surveyStore.values.filter { it.plan?.mission?.missionId == result.newMissionId }
|
||||
val clonedDrops = dropStore.values.filter { it.plan?.mission?.missionId == result.newMissionId }
|
||||
assertEquals(2, clonedSurveys.size)
|
||||
assertEquals(1, clonedDrops.size)
|
||||
// Ссылки переведены на новые survey IDs
|
||||
val clonedLinks = dropSurveyStore.filter { it.dropMode?.plan?.mission?.missionId == result.newMissionId }
|
||||
assertEquals(2, clonedLinks.size)
|
||||
assertTrue(clonedLinks.all { it.surveyMode?.plan?.mission?.missionId == result.newMissionId })
|
||||
}
|
||||
|
||||
// --- fork-forward: фильтрация по окну ---
|
||||
|
||||
@Test
|
||||
fun `fork-forward clone includes mode that starts before window but ends inside`() {
|
||||
// Включение начинается за 5 мин до окна и заканчивается внутри → пересекается.
|
||||
// buildSourceMission создаёт 2 survey (оба перекрывают окно) + 1 drop.
|
||||
val (missionId) = buildSourceMission(t(0).minusMinutes(5), t(0).plusMinutes(10))
|
||||
val result = service.cloneMission(
|
||||
missionId,
|
||||
MissionCloneRequest(mode = "fork-forward", windowStart = t(0), windowStop = t(1))
|
||||
)
|
||||
val cloned = surveyStore.values.filter { it.plan?.mission?.missionId == result.newMissionId }
|
||||
assertEquals(2, cloned.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fork-forward clone excludes mode whose stop equals window start (closed left is open right semantics)`() {
|
||||
// Включение заканчивается ровно в windowStart → не пересекается (modeStop > windowStart is false).
|
||||
// Явно создаём одну съёмку длительностью 30 минут, заканчивающуюся в t(0).
|
||||
val source = MissionEntity(satelliteId = 100)
|
||||
missionStore[source.missionId] = source
|
||||
val plan = PlanEntity(id = ++planAutoId, mission = source, planNumber = 1)
|
||||
planStore[plan.id!!] = plan
|
||||
// duration 1800s = 30 min; начало: t(0) - 30min = строго до окна, конец ровно в t(0).
|
||||
survey(plan, t(0).minusMinutes(30), duration = 1800.0)
|
||||
|
||||
val result = service.cloneMission(
|
||||
source.missionId,
|
||||
MissionCloneRequest(mode = "fork-forward", windowStart = t(0), windowStop = t(1))
|
||||
)
|
||||
val cloned = surveyStore.values.filter { it.plan?.mission?.missionId == result.newMissionId }
|
||||
assertTrue(cloned.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fork-forward clone excludes mode starting exactly at window stop (open upper bound)`() {
|
||||
// Включение начинается ровно в windowStop → не пересекается (open right bound [start, stop)).
|
||||
// Явно создаём одну съёмку: start = t(1) (= windowStop), duration 10min.
|
||||
val source = MissionEntity(satelliteId = 100)
|
||||
missionStore[source.missionId] = source
|
||||
val plan = PlanEntity(id = ++planAutoId, mission = source, planNumber = 1)
|
||||
planStore[plan.id!!] = plan
|
||||
survey(plan, t(1), duration = 600.0)
|
||||
|
||||
val result = service.cloneMission(
|
||||
source.missionId,
|
||||
MissionCloneRequest(mode = "fork-forward", windowStart = t(0), windowStop = t(1))
|
||||
)
|
||||
val cloned = surveyStore.values.filter { it.plan?.mission?.missionId == result.newMissionId }
|
||||
assertTrue(cloned.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fork-forward drops drop mode when all its surveys are outside window`() {
|
||||
// Две съёмки вне окна; сброс на них тоже не клонируется (пустой drop).
|
||||
val source = MissionEntity(satelliteId = 100)
|
||||
missionStore[source.missionId] = source
|
||||
val sourcePlan = PlanEntity(id = ++planAutoId, mission = source, planNumber = 1)
|
||||
planStore[sourcePlan.id!!] = sourcePlan
|
||||
val outSurvey = survey(sourcePlan, start = t(0).minusHours(2), duration = 60.0)
|
||||
val drop = drop(sourcePlan, start = t(0).minusMinutes(30))
|
||||
addDropLink(drop, outSurvey)
|
||||
|
||||
val result = service.cloneMission(
|
||||
source.missionId,
|
||||
MissionCloneRequest(mode = "fork-forward", windowStart = t(1), windowStop = t(2))
|
||||
)
|
||||
val clonedDrops = dropStore.values.filter { it.plan?.mission?.missionId == result.newMissionId }
|
||||
assertTrue(clonedDrops.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fork-forward requires windowStart and windowStop`() {
|
||||
val (missionId) = buildSourceMission(t(0), t(1))
|
||||
assertThrows<ResponseStatusException> {
|
||||
service.cloneMission(missionId, MissionCloneRequest(mode = "fork-forward"))
|
||||
}
|
||||
}
|
||||
|
||||
// --- helpers ---
|
||||
|
||||
data class SourceFixture(
|
||||
val missionId: UUID,
|
||||
val plan: PlanEntity,
|
||||
val survey1: SurveyModeEntity,
|
||||
val survey2: SurveyModeEntity,
|
||||
val drop: DropModeEntity
|
||||
)
|
||||
|
||||
private fun buildSourceMission(surveyStart: LocalDateTime, surveyStop: LocalDateTime): SourceFixture {
|
||||
val mission = MissionEntity(satelliteId = 100)
|
||||
missionStore[mission.missionId] = mission
|
||||
val plan = PlanEntity(id = ++planAutoId, mission = mission, planNumber = 1)
|
||||
planStore[plan.id!!] = plan
|
||||
val duration = java.time.Duration.between(surveyStart, surveyStop).toMillis().toDouble() / 1000.0
|
||||
val s1 = survey(plan, surveyStart.atOffset(ZoneOffset.UTC), duration)
|
||||
val s2 = survey(plan, surveyStart.plusMinutes(1).atOffset(ZoneOffset.UTC), duration)
|
||||
val d = drop(plan, surveyStart.plusMinutes(5))
|
||||
return SourceFixture(mission.missionId, plan, s1, s2, d)
|
||||
}
|
||||
|
||||
private fun survey(plan: PlanEntity, startOffset: OffsetDateTime, duration: Double = 300.0): SurveyModeEntity {
|
||||
val id = ++modeAutoId
|
||||
val s = SurveyModeEntity(id = id, plan = plan, timeStart = startOffset, revolution = id,
|
||||
lat = 55.0, long = 37.0, duration = duration, roll = 0.0, source = SurveyModeSource.MANUAL,
|
||||
status = SurveyModeStatus.PLANNED)
|
||||
surveyStore[id] = s
|
||||
return s
|
||||
}
|
||||
|
||||
private fun survey(plan: PlanEntity, start: LocalDateTime, duration: Double = 300.0) =
|
||||
survey(plan, start.atOffset(ZoneOffset.UTC), duration)
|
||||
|
||||
private fun drop(plan: PlanEntity, start: LocalDateTime): DropModeEntity {
|
||||
val id = ++modeAutoId
|
||||
val d = DropModeEntity(id = id, plan = plan, timeStart = start.atOffset(ZoneOffset.UTC),
|
||||
revolution = id, station = "102", duration = 60.0)
|
||||
dropStore[id] = d
|
||||
return d
|
||||
}
|
||||
|
||||
private fun addDropLink(drop: DropModeEntity, survey: SurveyModeEntity) {
|
||||
dropSurveyStore.add(DropModeSurveyEntity(dropMode = drop, surveyMode = survey))
|
||||
}
|
||||
|
||||
private fun t(hour: Int): LocalDateTime = LocalDateTime.of(2026, 6, 10, hour, 0, 0)
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun <T> anyNonNull(): T { org.mockito.ArgumentMatchers.any<T>(); return null as T }
|
||||
private fun anyUUID(): UUID { org.mockito.ArgumentMatchers.any<UUID>(); return UUID.randomUUID() }
|
||||
private fun anyLong(): Long { org.mockito.ArgumentMatchers.anyLong(); return 0L }
|
||||
}
|
||||
Reference in New Issue
Block a user