pcp-mission-planing: Ф4 — валидация-заглушка POST /missions/{id}/validate (контракт конфликтов, пустой diff)
This commit is contained in:
+19
@@ -252,6 +252,25 @@ class MissionController(
|
||||
return ResponseEntity.noContent().build()
|
||||
}
|
||||
|
||||
@Operation(
|
||||
summary = "Проверить миссию-черновик на конфликты",
|
||||
description = "Считает конфликты/наложения включений плана-черновика и возвращает дифф по " +
|
||||
"режимам. MVP: валидатор-заглушка — контракт корректен, список конфликтов пуст; " +
|
||||
"реальные правила (пересечения по времени, видимость цели, ЗРВ, порядок сброса, борт) — " +
|
||||
"отдельной задачей."
|
||||
)
|
||||
@ApiResponses(
|
||||
value = [
|
||||
ApiResponse(responseCode = "200", description = "Валидация выполнена, конфликты в теле ответа"),
|
||||
ApiResponse(responseCode = "404", description = "Миссия не найдена")
|
||||
]
|
||||
)
|
||||
@PostMapping("/{mission_id}/validate")
|
||||
fun validateMission(
|
||||
@Parameter(description = "Идентификатор миссии-черновика")
|
||||
@PathVariable("mission_id") id: UUID
|
||||
): MissionValidationResponse = missionPlaningService.validateMission(id)
|
||||
|
||||
|
||||
|
||||
@Operation(summary = "Создать миссию")
|
||||
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package space.nstart.pcp.pcp_mission_planing_service.controller
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
* Коды правил валидации. MVP: валидатор-заглушка их **не вычисляет** — список существует как
|
||||
* стабильный контракт для фронта, чтобы появление реальных правил (раздел 6 дизайн-дока) не ломало схему.
|
||||
*/
|
||||
object ConflictRule {
|
||||
/** Пересечение по времени между включениями (съёмка↔съёмка, съёмка↔сброс, сброс↔сброс). */
|
||||
const val TIME_OVERLAP = "TIME_OVERLAP"
|
||||
/** Окно съёмки вне окна видимости цели (баллистика). */
|
||||
const val TARGET_VISIBILITY = "TARGET_VISIBILITY"
|
||||
/** Окно сброса вне зоны радиовидимости станции. */
|
||||
const val COMMS_SESSION = "COMMS_SESSION"
|
||||
/** Сбрасываемая съёмка не завершается строго до начала зоны сброса. */
|
||||
const val DROP_ORDER = "DROP_ORDER"
|
||||
/** Бортовые ограничения: длительность/скважность, энергетика, память. */
|
||||
const val ONBOARD_LIMIT = "ONBOARD_LIMIT"
|
||||
}
|
||||
|
||||
/** Уровень конфликта: [ERROR] блокирует «Применить», [WARNING] — информативный. */
|
||||
object ConflictSeverity {
|
||||
const val ERROR = "error"
|
||||
const val WARNING = "warning"
|
||||
}
|
||||
|
||||
/**
|
||||
* Один конфликт на затронутом включении. Возвращается диффом по режимам (а не как весь план),
|
||||
* чтобы фронт мог точечно подсвечивать проблемные включения.
|
||||
*
|
||||
* @param modeId id режима (survey или drop), к которому относится конфликт.
|
||||
* @param rule код правила, см. [ConflictRule].
|
||||
* @param severity уровень, см. [ConflictSeverity].
|
||||
* @param message человекочитаемое описание для оператора.
|
||||
* @param relatedModeId id второго участника парного правила (например, пересечения по времени), если есть.
|
||||
*/
|
||||
data class ModeConflict(
|
||||
val modeId: Long,
|
||||
val rule: String,
|
||||
val severity: String,
|
||||
val message: String,
|
||||
val relatedModeId: Long? = null
|
||||
)
|
||||
|
||||
/**
|
||||
* Результат валидации миссии-черновика.
|
||||
*
|
||||
* MVP: [conflicts] всегда пуст — реальные правила ещё не считаются (TODO раздела 6 дизайн-дока).
|
||||
* Контракт стабилен: фронт уже завязывается на него и не сломается, когда правила появятся.
|
||||
*/
|
||||
data class MissionValidationResponse(
|
||||
val missionId: UUID,
|
||||
val conflicts: List<ModeConflict>
|
||||
)
|
||||
+16
@@ -32,6 +32,7 @@ import space.nstart.pcp.pcp_types_lib.dto.satellite.mission.SurveyRouteBuildResp
|
||||
import space.nstart.pcp.pcp_mission_planing_service.controller.CloneMode
|
||||
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.controller.MissionValidationResponse
|
||||
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
|
||||
@@ -910,6 +911,21 @@ class MissionPlaningService(
|
||||
logger.info("Удалён сброс (drop_mode id={}), снято связей со съёмками={}", dropId, links.size)
|
||||
}
|
||||
|
||||
/**
|
||||
* Валидация миссии-черновика на конфликты/наложения включений.
|
||||
*
|
||||
* MVP-заглушка: проверяет существование миссии (404, если нет) и возвращает корректный контракт
|
||||
* с **пустым** списком конфликтов. Реальные правила (пересечения по времени, видимость цели, ЗРВ
|
||||
* станции, порядок сброса, бортовые ограничения) — TODO раздела 6 дизайн-дока. Контракт
|
||||
* ([MissionValidationResponse]/[ModeConflict]) стабилен, чтобы фронт не ломался при их появлении.
|
||||
*/
|
||||
fun validateMission(missionId: UUID): MissionValidationResponse {
|
||||
if (!missionRepository.existsById(missionId)) {
|
||||
throw notFound(missionId)
|
||||
}
|
||||
return MissionValidationResponse(missionId = missionId, conflicts = emptyList())
|
||||
}
|
||||
|
||||
/**
|
||||
* Строит контур полосы съёмки по параметрам видимости точки.
|
||||
* Чистый расчёт без персистенции; переиспользуется [buildSurveyRoute] и [saveSurveyRoute].
|
||||
|
||||
+19
@@ -199,6 +199,25 @@ class MissionEditModesTest {
|
||||
assertEquals(HttpStatus.NOT_FOUND, ex.statusCode)
|
||||
}
|
||||
|
||||
// --- validate (заглушка) ---
|
||||
|
||||
@Test
|
||||
fun `validate of an existing mission returns an empty conflict list`() {
|
||||
val missionId = UUID.randomUUID()
|
||||
`when`(missionRepository.existsById(missionId)).thenReturn(true)
|
||||
|
||||
val result = service.validateMission(missionId)
|
||||
|
||||
assertEquals(missionId, result.missionId)
|
||||
assertTrue(result.conflicts.isEmpty(), "MVP-заглушка не считает конфликтов")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `validate of a missing mission yields 404`() {
|
||||
val ex = assertThrows<ResponseStatusException> { service.validateMission(UUID.randomUUID()) }
|
||||
assertEquals(HttpStatus.NOT_FOUND, ex.statusCode)
|
||||
}
|
||||
|
||||
// --- helpers ---
|
||||
|
||||
private fun survey(): SurveyModeEntity {
|
||||
|
||||
Reference in New Issue
Block a user