From f5d960150c15d37277f4083b5fd054a7a76d8b7b Mon Sep 17 00:00:00 2001 From: Maxime Perrault Date: Wed, 15 Jan 2025 18:48:04 +0100 Subject: [PATCH 01/14] tech: add use case validation annotation and MissionValidator --- backend/build.gradle.kts | 6 +- .../missions/CreateOrUpdateEnvActions.kt | 3 + .../missions/CreateOrUpdateMission.kt | 6 +- ...eMissionWithActionsAndAttachedReporting.kt | 7 +- .../domain/validators/UseCaseValidation.kt | 9 + .../validators/UseCaseValidationAspect.kt | 31 +++ .../monitorenv/domain/validators/Validator.kt | 5 + .../validators/mission/MissionValidator.kt | 150 +++++++++++ .../api/endpoints/bff/v1/Missions.kt | 16 +- .../actions/fixtures/EnvActionFixture.kt | 51 ++++ .../fixtures/ControlUnitFixture.kt | 18 ++ .../use_cases/missions/PatchMissionUTest.kt | 1 - .../missions/fixtures/MissionFixture.kt | 15 +- .../mission/MissionValidatorUTest.kt | 248 ++++++++++++++++++ .../endpoints/publicapi/v2/MissionsITest.kt | 2 +- 15 files changed, 555 insertions(+), 13 deletions(-) create mode 100644 backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/UseCaseValidation.kt create mode 100644 backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/UseCaseValidationAspect.kt create mode 100644 backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/Validator.kt create mode 100644 backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidator.kt create mode 100644 backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/controlUnit/fixtures/ControlUnitFixture.kt create mode 100644 backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidatorUTest.kt diff --git a/backend/build.gradle.kts b/backend/build.gradle.kts index 20e09ec68..ef568709f 100644 --- a/backend/build.gradle.kts +++ b/backend/build.gradle.kts @@ -2,12 +2,12 @@ plugins { `java-library` `maven-publish` id("org.springframework.boot") version "3.4.1" - id("org.jetbrains.kotlin.plugin.spring") version "2.1.0" + id("org.jlleitschuh.gradle.ktlint") version "12.1.2" kotlin("jvm") version "2.1.0" - id("org.jetbrains.kotlin.plugin.allopen") version "2.1.0" + kotlin("plugin.spring") version "2.1.0" + kotlin("plugin.allopen") version "2.1.0" kotlin("plugin.noarg") version "2.1.0" kotlin("plugin.jpa") version "2.1.0" - id("org.jlleitschuh.gradle.ktlint") version "12.1.2" kotlin("plugin.serialization") version "2.1.0" } diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/CreateOrUpdateEnvActions.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/CreateOrUpdateEnvActions.kt index ede57e8af..ac57ef648 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/CreateOrUpdateEnvActions.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/CreateOrUpdateEnvActions.kt @@ -11,6 +11,8 @@ import fr.gouv.cacem.monitorenv.domain.repositories.IDepartmentAreaRepository import fr.gouv.cacem.monitorenv.domain.repositories.IFacadeAreasRepository import fr.gouv.cacem.monitorenv.domain.repositories.IMissionRepository import fr.gouv.cacem.monitorenv.domain.repositories.IPostgisFunctionRepository +import fr.gouv.cacem.monitorenv.domain.validators.UseCaseValidation +import fr.gouv.cacem.monitorenv.domain.validators.mission.MissionValidator import org.slf4j.LoggerFactory @UseCase @@ -24,6 +26,7 @@ class CreateOrUpdateEnvActions( @Throws(IllegalArgumentException::class) fun execute( + @UseCaseValidation(validator = MissionValidator::class) mission: MissionEntity, envActions: List?, ): MissionEntity { diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/CreateOrUpdateMission.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/CreateOrUpdateMission.kt index 28e331463..3c0d01061 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/CreateOrUpdateMission.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/CreateOrUpdateMission.kt @@ -6,6 +6,8 @@ import fr.gouv.cacem.monitorenv.domain.repositories.IFacadeAreasRepository import fr.gouv.cacem.monitorenv.domain.repositories.IMissionRepository import fr.gouv.cacem.monitorenv.domain.repositories.IPostgisFunctionRepository import fr.gouv.cacem.monitorenv.domain.use_cases.missions.events.UpdateMissionEvent +import fr.gouv.cacem.monitorenv.domain.validators.UseCaseValidation +import fr.gouv.cacem.monitorenv.domain.validators.mission.MissionValidator import org.slf4j.LoggerFactory import org.springframework.context.ApplicationEventPublisher @@ -19,7 +21,9 @@ class CreateOrUpdateMission( private val logger = LoggerFactory.getLogger(CreateOrUpdateMission::class.java) @Throws(IllegalArgumentException::class) - fun execute(mission: MissionEntity): MissionEntity { + fun execute( + @UseCaseValidation(validator = MissionValidator::class) mission: MissionEntity, + ): MissionEntity { logger.info("Attempt to CREATE or UPDATE mission ${mission.id}") val normalizedMission = diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/CreateOrUpdateMissionWithActionsAndAttachedReporting.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/CreateOrUpdateMissionWithActionsAndAttachedReporting.kt index 751fbd21e..86dfe2e0a 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/CreateOrUpdateMissionWithActionsAndAttachedReporting.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/CreateOrUpdateMissionWithActionsAndAttachedReporting.kt @@ -3,13 +3,15 @@ package fr.gouv.cacem.monitorenv.domain.use_cases.missions import fr.gouv.cacem.monitorenv.config.UseCase -import fr.gouv.cacem.monitorenv.domain.entities.mission.* +import fr.gouv.cacem.monitorenv.domain.entities.mission.MissionEntity import fr.gouv.cacem.monitorenv.domain.exceptions.ReportingAlreadyAttachedException import fr.gouv.cacem.monitorenv.domain.repositories.IReportingRepository import fr.gouv.cacem.monitorenv.domain.use_cases.missions.dtos.EnvActionAttachedToReportingIds import fr.gouv.cacem.monitorenv.domain.use_cases.missions.dtos.MissionDetailsDTO +import fr.gouv.cacem.monitorenv.domain.validators.UseCaseValidation +import fr.gouv.cacem.monitorenv.domain.validators.mission.MissionValidator import org.slf4j.LoggerFactory -import java.util.* +import java.util.UUID @UseCase class CreateOrUpdateMissionWithActionsAndAttachedReporting( @@ -26,6 +28,7 @@ class CreateOrUpdateMissionWithActionsAndAttachedReporting( @Throws(IllegalArgumentException::class) fun execute( + @UseCaseValidation(validator = MissionValidator::class) mission: MissionEntity, attachedReportingIds: List, envActionsAttachedToReportingIds: List, diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/UseCaseValidation.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/UseCaseValidation.kt new file mode 100644 index 000000000..7fb14da6e --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/UseCaseValidation.kt @@ -0,0 +1,9 @@ +package fr.gouv.cacem.monitorenv.domain.validators + +import kotlin.reflect.KClass + +@Target(AnnotationTarget.VALUE_PARAMETER) +@Retention(AnnotationRetention.RUNTIME) +annotation class UseCaseValidation( + val validator: KClass>, +) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/UseCaseValidationAspect.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/UseCaseValidationAspect.kt new file mode 100644 index 000000000..60fcf6e5b --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/UseCaseValidationAspect.kt @@ -0,0 +1,31 @@ +package fr.gouv.cacem.monitorenv.domain.validators + +import org.aspectj.lang.JoinPoint +import org.aspectj.lang.annotation.Aspect +import org.aspectj.lang.annotation.Before +import org.aspectj.lang.reflect.MethodSignature +import org.springframework.stereotype.Component + +@Component +@Aspect +class UseCaseValidationAspect { + @Before("execution(* fr.gouv.cacem.monitorenv.domain.use_cases..*.execute(..))") + fun before(joinPoint: JoinPoint) { + val method = (joinPoint.signature as MethodSignature).method + + // Parcourir les paramètres de la méthode + method.parameters.forEachIndexed { index, parameter -> + val annotation = parameter.getAnnotation(UseCaseValidation::class.java) + if (annotation != null) { + // Récupérer l'argument associé à ce paramètre + val arg = joinPoint.args[index] + + // Instancier et exécuter le validateur spécifié dans l'annotation + val validator = + annotation.validator.objectInstance + ?: annotation.validator.java.getDeclaredConstructor().newInstance() + (validator as Validator).validate(arg) // Valide l'objet + } + } + } +} diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/Validator.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/Validator.kt new file mode 100644 index 000000000..d0bcb4d2d --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/Validator.kt @@ -0,0 +1,5 @@ +package fr.gouv.cacem.monitorenv.domain.validators + +interface Validator { + fun validate(obj: T) +} diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidator.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidator.kt new file mode 100644 index 000000000..52be5a044 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidator.kt @@ -0,0 +1,150 @@ +package fr.gouv.cacem.monitorenv.domain.validators.mission + +import fr.gouv.cacem.monitorenv.domain.entities.mission.MissionEntity +import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.EnvActionEntity +import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionControl.EnvActionControlEntity +import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionControl.infraction.InfractionTypeEnum +import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionSurveillance.EnvActionSurveillanceEntity +import fr.gouv.cacem.monitorenv.domain.exceptions.BackendUsageErrorCode +import fr.gouv.cacem.monitorenv.domain.exceptions.BackendUsageException +import fr.gouv.cacem.monitorenv.domain.validators.Validator +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Component +import java.time.ZonedDateTime + +private const val NB_CHAR_MAX = 3 + +@Component +class MissionValidator : Validator { + private val logger = LoggerFactory.getLogger(MissionValidator::class.java) + + override fun validate(mission: MissionEntity) { + logger.info("Validating mission: ${mission.id}") + val isMissionEnded = mission.endDateTimeUtc != null && ZonedDateTime.now().isAfter(mission.endDateTimeUtc) + + validateOngoingMission(mission) + } + + private fun validateOngoingMission(mission: MissionEntity) { + if (mission.startDateTimeUtc.isAfter(mission.endDateTimeUtc)) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "La date de fin doit être postérieure à la date de début", + ) + } + if (mission.missionTypes.isEmpty()) { + throw BackendUsageException(BackendUsageErrorCode.UNVALID_PROPERTY, "Le type de mission est requis") + } + + if (mission.controlUnits.isEmpty()) { + throw BackendUsageException(BackendUsageErrorCode.UNVALID_PROPERTY, "Une unité de contrôle est requise") + } + + if (mission.completedBy !== null && mission.completedBy.length != NB_CHAR_MAX) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "Le trigramme \"complété par\" doit avoir 3 lettres", + ) + } + if (mission.openBy !== null && mission.openBy.length != NB_CHAR_MAX) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "Le trigramme \"ouvert par\" doit avoir 3 lettres", + ) + } + validateEnvAction(mission) + } + + private fun validateEnvAction(mission: MissionEntity) { + mission.envActions?.forEach { envAction -> + if (envAction is EnvActionControlEntity) { + validateControl(envAction, mission) + } + + if (envAction is EnvActionSurveillanceEntity) { + validateSurveillance(envAction, mission) + } + } + } + + private fun validateControl( + control: EnvActionControlEntity, + mission: MissionEntity, + ) { + validateEnvAction(control, mission) + + val sumOfNbTarget = control.infractions?.sumOf { infraction -> infraction.nbTarget } + if (sumOfNbTarget != null) { + if (control.actionNumberOfControls == null || sumOfNbTarget > control.actionNumberOfControls) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "Le nombre de cibles excède le nombre total de contrôles", + ) + } + } + control.infractions?.forEach { infraction -> + if (infraction.infractionType !== InfractionTypeEnum.WAITING && infraction.natinf?.isEmpty() == true) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "Une infraction doit avoir une natinf si le type d'infraction n'est pas \"En attente\"", + ) + } + if (infraction.nbTarget < 1) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "le nombre minimum de cible est 1", + ) + } + } + } + + private fun validateSurveillance( + surveillance: EnvActionSurveillanceEntity, + mission: MissionEntity, + ) { + validateEnvAction(surveillance, mission) + + if (surveillance.completedBy !== null && surveillance.completedBy.length != NB_CHAR_MAX) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "Le trigramme \"complété par\" doit avoir 3 lettres", + ) + } + } + + private fun validateEnvAction( + envAction: EnvActionEntity, + mission: MissionEntity, + ) { + if (envAction.actionStartDateTimeUtc?.isBefore(mission.startDateTimeUtc) == true) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "La date de début du contrôle doit être postérieure à celle du début de mission", + ) + } + if (envAction.actionStartDateTimeUtc?.isAfter(mission.endDateTimeUtc) == true) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "La date de début du contrôle doit être antérieure à celle de fin de mission", + ) + } + if (envAction.actionEndDateTimeUtc?.isAfter(mission.endDateTimeUtc) == true) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "La date de fin du contrôle doit être antérieure à celle de fin de mission", + ) + } + if (envAction.actionEndDateTimeUtc?.isBefore(mission.startDateTimeUtc) == true) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "La date de fin du contrôle doit être postérieure à celle du début de mission", + ) + } + if (envAction.openBy?.length != NB_CHAR_MAX) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "Le trigramme \"ouvert par\" doit avoir 3 lettres", + ) + } + } +} diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/bff/v1/Missions.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/bff/v1/Missions.kt index 30196a286..f3ed8140c 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/bff/v1/Missions.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/bff/v1/Missions.kt @@ -3,7 +3,12 @@ package fr.gouv.cacem.monitorenv.infrastructure.api.endpoints.bff.v1 import fr.gouv.cacem.monitorenv.domain.entities.mission.CanDeleteMissionResponse import fr.gouv.cacem.monitorenv.domain.entities.mission.MissionSourceEnum import fr.gouv.cacem.monitorenv.domain.entities.mission.MissionTypeEnum -import fr.gouv.cacem.monitorenv.domain.use_cases.missions.* +import fr.gouv.cacem.monitorenv.domain.use_cases.missions.CanDeleteMission +import fr.gouv.cacem.monitorenv.domain.use_cases.missions.CreateOrUpdateMissionWithActionsAndAttachedReporting +import fr.gouv.cacem.monitorenv.domain.use_cases.missions.DeleteMission +import fr.gouv.cacem.monitorenv.domain.use_cases.missions.GetEngagedControlUnits +import fr.gouv.cacem.monitorenv.domain.use_cases.missions.GetFullMissionWithFishAndRapportNavActions +import fr.gouv.cacem.monitorenv.domain.use_cases.missions.GetFullMissions import fr.gouv.cacem.monitorenv.infrastructure.api.adapters.bff.inputs.missions.CreateOrUpdateMissionDataInput import fr.gouv.cacem.monitorenv.infrastructure.api.adapters.bff.outputs.LegacyControlUnitAndMissionSourcesDataOutput import fr.gouv.cacem.monitorenv.infrastructure.api.adapters.bff.outputs.missions.MissionDataOutput @@ -15,7 +20,14 @@ import jakarta.websocket.server.PathParam import org.springframework.format.annotation.DateTimeFormat import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.* +import org.springframework.web.bind.annotation.DeleteMapping +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PutMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.bind.annotation.RestController import java.time.ZonedDateTime @RestController("MissionsV1") diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/actions/fixtures/EnvActionFixture.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/actions/fixtures/EnvActionFixture.kt index e0578e7dc..4a6f12425 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/actions/fixtures/EnvActionFixture.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/actions/fixtures/EnvActionFixture.kt @@ -6,10 +6,18 @@ import fr.gouv.cacem.monitorenv.domain.entities.mission.MonitorFishActionTypeEnu import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.ActionTypeEnum import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.EnvActionControlPlanEntity import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.EnvActionEntity +import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionControl.EnvActionControlEntity +import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionControl.infraction.AdministrativeResponseEnum +import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionControl.infraction.FormalNoticeEnum +import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionControl.infraction.InfractionEntity +import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionControl.infraction.InfractionTypeEnum +import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionControl.infraction.SeizureTypeEnum +import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionSurveillance.EnvActionSurveillanceEntity import fr.gouv.cacem.monitorenv.domain.entities.mission.monitorfish.MonitorFishMissionActionEntity import fr.gouv.cacem.monitorenv.domain.mappers.EnvActionMapper import java.time.ZonedDateTime import java.util.UUID +import kotlin.random.Random class EnvActionFixture { companion object { @@ -45,6 +53,49 @@ class EnvActionFixture { ) } + fun anEnvActionControl( + startTime: ZonedDateTime? = null, + endTime: ZonedDateTime? = null, + openBy: String = "MPE", + infractions: List = listOf(), + actionNumberOfControls: Int? = infractions.size, + ): EnvActionControlEntity { + return EnvActionControlEntity( + id = UUID.randomUUID(), + actionStartDateTimeUtc = startTime, + actionEndDateTimeUtc = endTime, + openBy = openBy, + infractions = infractions, + actionNumberOfControls = actionNumberOfControls, + ) + } + + fun anEnvActionSurveillance( + startTime: ZonedDateTime? = null, + endTime: ZonedDateTime? = null, + openBy: String? = null, + ): EnvActionSurveillanceEntity { + return EnvActionSurveillanceEntity( + id = UUID.randomUUID(), + actionStartDateTimeUtc = startTime, + actionEndDateTimeUtc = endTime, + openBy = openBy, + awareness = null, + ) + } + + fun anInfraction( + infractionType: InfractionTypeEnum = InfractionTypeEnum.WAITING, + nbTarget: Int = 1, + ) = InfractionEntity( + id = Random.nextInt().toString(), + administrativeResponse = AdministrativeResponseEnum.NONE, + infractionType = infractionType, + formalNotice = FormalNoticeEnum.NO, + seizure = SeizureTypeEnum.NO, + nbTarget = nbTarget, + ) + fun aMonitorFishAction(missionId: Int): MonitorFishMissionActionEntity { return MonitorFishMissionActionEntity( id = 1, diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/controlUnit/fixtures/ControlUnitFixture.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/controlUnit/fixtures/ControlUnitFixture.kt new file mode 100644 index 000000000..8fa26c60d --- /dev/null +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/controlUnit/fixtures/ControlUnitFixture.kt @@ -0,0 +1,18 @@ +package fr.gouv.cacem.monitorenv.domain.use_cases.controlUnit.fixtures + +import fr.gouv.cacem.monitorenv.domain.entities.controlUnit.LegacyControlUnitEntity + +class ControlUnitFixture { + companion object { + fun aLegacyControlUnit(): LegacyControlUnitEntity { + return LegacyControlUnitEntity( + id = 1, + administration = "DIRM / DM", + isArchived = false, + name = "Cross Etel", + resources = listOf(), + contact = null, + ) + } + } +} diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/PatchMissionUTest.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/PatchMissionUTest.kt index 8cbc1d11e..843cb7269 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/PatchMissionUTest.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/PatchMissionUTest.kt @@ -49,7 +49,6 @@ class PatchMissionUTest { ) given(missionRepository.findById(id)).willReturn(missionFromDatabase) - patchEntity.execute(missionFromDatabase, patchableMission) given(missionRepository.save(missionPatched)).willReturn(MissionDetailsDTO(missionPatched)) // When diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/fixtures/MissionFixture.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/fixtures/MissionFixture.kt index 49ec57bfc..47721f457 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/fixtures/MissionFixture.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/fixtures/MissionFixture.kt @@ -1,9 +1,11 @@ package fr.gouv.cacem.monitorenv.domain.use_cases.missions.fixtures +import fr.gouv.cacem.monitorenv.domain.entities.controlUnit.LegacyControlUnitEntity import fr.gouv.cacem.monitorenv.domain.entities.mission.MissionEntity import fr.gouv.cacem.monitorenv.domain.entities.mission.MissionSourceEnum import fr.gouv.cacem.monitorenv.domain.entities.mission.MissionTypeEnum import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.EnvActionEntity +import fr.gouv.cacem.monitorenv.domain.use_cases.controlUnit.fixtures.ControlUnitFixture.Companion.aLegacyControlUnit import fr.gouv.cacem.monitorenv.domain.use_cases.missions.dtos.MissionDetailsDTO import java.time.ZonedDateTime import kotlin.random.Random @@ -14,14 +16,20 @@ class MissionFixture { id: Int? = Random.nextInt(), startDateTimeUtc: ZonedDateTime = ZonedDateTime.parse("2022-01-15T04:50:09Z"), endDateTimeUtc: ZonedDateTime? = ZonedDateTime.parse("2022-01-23T20:29:03Z"), + controlUnits: List = listOf(aLegacyControlUnit()), observationsByUnit: String? = null, - envActions: List = listOf(), + missionTypes: List = listOf(MissionTypeEnum.LAND), + openBy: String? = null, + completedBy: String? = null, + envAction: List = emptyList(), ): MissionEntity { return MissionEntity( id = id, + controlUnits = controlUnits, observationsByUnit = observationsByUnit, - missionTypes = listOf(MissionTypeEnum.LAND), - envActions = envActions, + openBy = openBy, + completedBy = completedBy, + missionTypes = missionTypes, facade = "Outre-Mer", geom = null, startDateTimeUtc = startDateTimeUtc, @@ -33,6 +41,7 @@ class MissionFixture { isGeometryComputedFromControls = false, updatedAtUtc = null, createdAtUtc = null, + envActions = envAction, ) } diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidatorUTest.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidatorUTest.kt new file mode 100644 index 000000000..1c654bc04 --- /dev/null +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidatorUTest.kt @@ -0,0 +1,248 @@ +package fr.gouv.cacem.monitorenv.domain.validators.mission + +import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionControl.infraction.InfractionTypeEnum +import fr.gouv.cacem.monitorenv.domain.exceptions.BackendUsageException +import fr.gouv.cacem.monitorenv.domain.use_cases.actions.fixtures.EnvActionFixture.Companion.anEnvActionControl +import fr.gouv.cacem.monitorenv.domain.use_cases.actions.fixtures.EnvActionFixture.Companion.anEnvActionSurveillance +import fr.gouv.cacem.monitorenv.domain.use_cases.actions.fixtures.EnvActionFixture.Companion.anInfraction +import fr.gouv.cacem.monitorenv.domain.use_cases.missions.fixtures.MissionFixture.Companion.aMissionEntity +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Assertions.assertThrows +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource +import org.junit.jupiter.params.provider.ValueSource +import java.time.ZonedDateTime + +class MissionValidatorUTest { + private val missionValidator = MissionValidator() + + @Test + fun `validate should throw an exception if startDateTimeUtc is after endDateTimeUtc`() { + val startDateTimeUtc = ZonedDateTime.parse("2020-03-04T00:00:00.000Z") + val mission = + aMissionEntity( + startDateTimeUtc = startDateTimeUtc, + endDateTimeUtc = startDateTimeUtc.minusSeconds(1), + ) + + val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } + assertThat(assertThrows.message).isEqualTo("La date de fin doit être postérieure à la date de début") + } + + @Test + fun `validate should throw an exception if controlUnits is empty`() { + val mission = aMissionEntity(controlUnits = emptyList()) + + val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } + assertThat(assertThrows.message).isEqualTo("Une unité de contrôle est requise") + } + + @Test + fun `validate should throw an exception if missionTypes is empty`() { + val mission = aMissionEntity(missionTypes = emptyList()) + + val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } + assertThat(assertThrows.message).isEqualTo("Le type de mission est requis") + } + + @Test + fun `validate should throw an exception if there is a control with a start date before mission starting date`() { + val startDateTimeUtc = ZonedDateTime.parse("2020-03-04T00:00:00.000Z") + val anEnvActionControl = anEnvActionControl(startTime = startDateTimeUtc.minusSeconds(1)) + val mission = aMissionEntity(startDateTimeUtc = startDateTimeUtc, envAction = listOf(anEnvActionControl)) + + val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } + assertThat( + assertThrows.message, + ).isEqualTo("La date de début du contrôle doit être postérieure à celle du début de mission") + } + + @Test + fun `validate should throw an exception if there is a control with a start date after mission ending date`() { + val startDateTimeUtc = ZonedDateTime.parse("2019-03-04T00:00:00.000Z") + val endDateTimeUtc = ZonedDateTime.parse("2020-03-04T00:00:00.000Z") + val anEnvActionControl = anEnvActionControl(endTime = endDateTimeUtc.plusSeconds(1)) + val mission = + aMissionEntity( + startDateTimeUtc = startDateTimeUtc, + endDateTimeUtc = endDateTimeUtc, + envAction = listOf(anEnvActionControl), + ) + + val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } + assertThat( + assertThrows.message, + ).isEqualTo("La date de fin du contrôle doit être antérieure à celle de fin de mission") + } + + @Test + fun `validate should throw an exception if there is a control with an end date after mission ending date`() { + val startDateTimeUtc = ZonedDateTime.parse("2019-03-04T00:00:00.000Z") + val endDateTimeUtc = ZonedDateTime.parse("2020-03-04T00:00:00.000Z") + val anEnvActionControl = anEnvActionControl(endTime = endDateTimeUtc.plusSeconds(1)) + val mission = + aMissionEntity( + startDateTimeUtc = startDateTimeUtc, + endDateTimeUtc = endDateTimeUtc, + envAction = listOf(anEnvActionControl), + ) + + val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } + assertThat( + assertThrows.message, + ).isEqualTo("La date de fin du contrôle doit être antérieure à celle de fin de mission") + } + + @Test + fun `validate should throw an exception if there is a control with an end date before mission starting date`() { + val startDateTimeUtc = ZonedDateTime.parse("2020-03-04T00:00:00.000Z") + val anEnvActionControl = anEnvActionControl(endTime = startDateTimeUtc.minusSeconds(1)) + val mission = aMissionEntity(startDateTimeUtc = startDateTimeUtc, envAction = listOf(anEnvActionControl)) + + val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } + assertThat( + assertThrows.message, + ).isEqualTo("La date de fin du contrôle doit être postérieure à celle du début de mission") + } + + @ParameterizedTest + @EnumSource(value = InfractionTypeEnum::class, names = ["WAITING"], mode = EnumSource.Mode.EXCLUDE) + fun `validate should throw an exception if there is a control with infractionType other than WAITING that doesnt have a NATINF`( + infractionType: InfractionTypeEnum, + ) { + val anEnvActionControl = anEnvActionControl(infractions = listOf(anInfraction(infractionType = infractionType))) + val mission = aMissionEntity(envAction = listOf(anEnvActionControl)) + + val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } + assertThat( + assertThrows.message, + ).isEqualTo("Une infraction doit avoir une natinf si le type d'infraction n'est pas \"En attente\"") + } + + @Test + fun `validate should throw an exception if there is a control with infraction and nbTarget is less than 1`() { + val anEnvActionControl = anEnvActionControl(infractions = listOf(anInfraction(nbTarget = 0))) + val mission = aMissionEntity(envAction = listOf(anEnvActionControl)) + + val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } + assertThat(assertThrows.message).isEqualTo("le nombre minimum de cible est 1") + } + + @Test + fun `validate should throw an exception if there is a control with infractions that got more nbTarget than the mission actionNumberOfControls `() { + val anEnvActionControl = + anEnvActionControl( + actionNumberOfControls = 10, + infractions = listOf(anInfraction(nbTarget = 10), anInfraction(nbTarget = 5)), + ) + val mission = aMissionEntity(envAction = listOf(anEnvActionControl)) + + val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } + assertThat(assertThrows.message).isEqualTo("Le nombre de cibles excède le nombre total de contrôles") + } + + @Test + fun `validate should pass if there is a control with infractionType = WAITING that doesnt have a NATINF`() { + val anEnvActionControl = + anEnvActionControl(infractions = listOf(anInfraction(infractionType = InfractionTypeEnum.WAITING))) + val mission = aMissionEntity(envAction = listOf(anEnvActionControl)) + + missionValidator.validate(mission) + } + + @ParameterizedTest + @ValueSource(strings = ["A", "AA", "AAAA"]) + fun `validate should throw an exception if there is a control with openBy is not a trigram`(openBy: String) { + val anEnvActionControl = anEnvActionControl(openBy = openBy) + val mission = aMissionEntity(envAction = listOf(anEnvActionControl)) + + val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } + assertThat(assertThrows.message).isEqualTo("Le trigramme \"ouvert par\" doit avoir 3 lettres") + } + + @Test + fun `validate should throw an exception if there is a surveillance with a start date before mission starting date`() { + val startDateTimeUtc = ZonedDateTime.parse("2020-03-04T00:00:00.000Z") + val anEnvActionSurveillance = anEnvActionSurveillance(startTime = startDateTimeUtc.minusSeconds(1)) + val mission = aMissionEntity(startDateTimeUtc = startDateTimeUtc, envAction = listOf(anEnvActionSurveillance)) + + val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } + assertThat( + assertThrows.message, + ).isEqualTo("La date de début du contrôle doit être postérieure à celle du début de mission") + } + + @Test + fun `validate should throw an exception if there is a surveillance with a start date after mission ending date`() { + val startDateTimeUtc = ZonedDateTime.parse("2019-03-04T00:00:00.000Z") + val endDateTimeUtc = ZonedDateTime.parse("2020-03-04T00:00:00.000Z") + val anEnvActionSurveillance = anEnvActionSurveillance(endTime = endDateTimeUtc.plusSeconds(1)) + val mission = + aMissionEntity( + startDateTimeUtc = startDateTimeUtc, + endDateTimeUtc = endDateTimeUtc, + envAction = listOf(anEnvActionSurveillance), + ) + + val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } + assertThat( + assertThrows.message, + ).isEqualTo("La date de fin du contrôle doit être antérieure à celle de fin de mission") + } + + @Test + fun `validate should throw an exception if there is a surveillance with an end date after mission ending date`() { + val startDateTimeUtc = ZonedDateTime.parse("2019-03-04T00:00:00.000Z") + val endDateTimeUtc = ZonedDateTime.parse("2020-03-04T00:00:00.000Z") + val anEnvActionSurveillance = anEnvActionSurveillance(endTime = endDateTimeUtc.plusSeconds(1)) + val mission = + aMissionEntity( + startDateTimeUtc = startDateTimeUtc, + endDateTimeUtc = endDateTimeUtc, + envAction = listOf(anEnvActionSurveillance), + ) + + val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } + assertThat( + assertThrows.message, + ).isEqualTo("La date de fin du contrôle doit être antérieure à celle de fin de mission") + } + + @Test + fun `validate should throw an exception if there is a surveillance with an end date before mission starting date`() { + val startDateTimeUtc = ZonedDateTime.parse("2020-03-04T00:00:00.000Z") + val anEnvActionControl = anEnvActionSurveillance(endTime = startDateTimeUtc.minusSeconds(1)) + val mission = aMissionEntity(startDateTimeUtc = startDateTimeUtc, envAction = listOf(anEnvActionControl)) + + val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } + assertThat( + assertThrows.message, + ).isEqualTo("La date de fin du contrôle doit être postérieure à celle du début de mission") + } + + @ParameterizedTest + @ValueSource(strings = ["A", "AA", "AAAA"]) + fun `validate should throw an exception if openBy is not a trigram`(openBy: String) { + val mission = aMissionEntity(openBy = openBy) + + val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } + assertThat(assertThrows.message).isEqualTo("Le trigramme \"ouvert par\" doit avoir 3 lettres") + } + + @ParameterizedTest + @ValueSource(strings = ["A", "AA", "AAAA"]) + fun `validate should throw an exception if completedBy is not a trigram`(completedBy: String) { + val mission = aMissionEntity(completedBy = completedBy) + + val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } + assertThat(assertThrows.message).isEqualTo("Le trigramme \"complété par\" doit avoir 3 lettres") + } + + @Test + fun `validate should pass for a valid MissionEntity`() { + val mission = aMissionEntity() + + missionValidator.validate(mission) + } +} diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/publicapi/v2/MissionsITest.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/publicapi/v2/MissionsITest.kt index e35118d54..ff3016f1a 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/publicapi/v2/MissionsITest.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/publicapi/v2/MissionsITest.kt @@ -74,9 +74,9 @@ class MissionsITest { val patchedMission = aMissionEntity( id = id, - observationsByUnit = observationsByUnit, startDateTimeUtc = startDateTimeUtc, endDateTimeUtc = endDateTimeUtc, + observationsByUnit = observationsByUnit, ) val patchableMissionEntity = PatchableMissionEntity( From 32aa0a0e13ee4fe9fbc03fb675907d4c04bd7502 Mon Sep 17 00:00:00 2001 From: Maxime Perrault Date: Thu, 16 Jan 2025 09:48:46 +0100 Subject: [PATCH 02/14] tech: add case for completed mission --- .../validators/mission/MissionValidator.kt | 26 ++++++++++++++----- .../actions/fixtures/EnvActionFixture.kt | 6 +++++ .../mission/MissionValidatorUTest.kt | 25 ++++++++++++++++++ 3 files changed, 51 insertions(+), 6 deletions(-) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidator.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidator.kt index 52be5a044..650c39693 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidator.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidator.kt @@ -2,6 +2,7 @@ package fr.gouv.cacem.monitorenv.domain.validators.mission import fr.gouv.cacem.monitorenv.domain.entities.mission.MissionEntity import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.EnvActionEntity +import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionControl.ActionTargetTypeEnum import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionControl.EnvActionControlEntity import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionControl.infraction.InfractionTypeEnum import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionSurveillance.EnvActionSurveillanceEntity @@ -20,12 +21,11 @@ class MissionValidator : Validator { override fun validate(mission: MissionEntity) { logger.info("Validating mission: ${mission.id}") - val isMissionEnded = mission.endDateTimeUtc != null && ZonedDateTime.now().isAfter(mission.endDateTimeUtc) - validateOngoingMission(mission) + validateMission(mission) } - private fun validateOngoingMission(mission: MissionEntity) { + private fun validateMission(mission: MissionEntity) { if (mission.startDateTimeUtc.isAfter(mission.endDateTimeUtc)) { throw BackendUsageException( BackendUsageErrorCode.UNVALID_PROPERTY, @@ -52,13 +52,16 @@ class MissionValidator : Validator { "Le trigramme \"ouvert par\" doit avoir 3 lettres", ) } - validateEnvAction(mission) + validateEnvActions(mission) } - private fun validateEnvAction(mission: MissionEntity) { + private fun validateEnvActions(mission: MissionEntity) { + val isMissionEnded = + mission.endDateTimeUtc != null && (mission.endDateTimeUtc?.isAfter(ZonedDateTime.now()) == true) + mission.envActions?.forEach { envAction -> if (envAction is EnvActionControlEntity) { - validateControl(envAction, mission) + validateControl(envAction, mission, isMissionEnded) } if (envAction is EnvActionSurveillanceEntity) { @@ -70,9 +73,19 @@ class MissionValidator : Validator { private fun validateControl( control: EnvActionControlEntity, mission: MissionEntity, + isMissionEnded: Boolean ) { validateEnvAction(control, mission) + if (isMissionEnded) { + if (control.vehicleType === null && control.actionTargetType === ActionTargetTypeEnum.VEHICLE) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "Le type de véhicule est obligatoire", + ) + } + } + val sumOfNbTarget = control.infractions?.sumOf { infraction -> infraction.nbTarget } if (sumOfNbTarget != null) { if (control.actionNumberOfControls == null || sumOfNbTarget > control.actionNumberOfControls) { @@ -82,6 +95,7 @@ class MissionValidator : Validator { ) } } + control.infractions?.forEach { infraction -> if (infraction.infractionType !== InfractionTypeEnum.WAITING && infraction.natinf?.isEmpty() == true) { throw BackendUsageException( diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/actions/fixtures/EnvActionFixture.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/actions/fixtures/EnvActionFixture.kt index 4a6f12425..99e6bb672 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/actions/fixtures/EnvActionFixture.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/actions/fixtures/EnvActionFixture.kt @@ -1,11 +1,13 @@ package fr.gouv.cacem.monitorenv.domain.use_cases.actions.fixtures import com.fasterxml.jackson.databind.ObjectMapper +import fr.gouv.cacem.monitorenv.domain.entities.VehicleTypeEnum import fr.gouv.cacem.monitorenv.domain.entities.mission.ActionCompletionEnum import fr.gouv.cacem.monitorenv.domain.entities.mission.MonitorFishActionTypeEnum import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.ActionTypeEnum import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.EnvActionControlPlanEntity import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.EnvActionEntity +import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionControl.ActionTargetTypeEnum import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionControl.EnvActionControlEntity import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionControl.infraction.AdministrativeResponseEnum import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionControl.infraction.FormalNoticeEnum @@ -59,6 +61,8 @@ class EnvActionFixture { openBy: String = "MPE", infractions: List = listOf(), actionNumberOfControls: Int? = infractions.size, + actionTargetTypeEnum: ActionTargetTypeEnum? = null, + vehicleTypeEnum: VehicleTypeEnum? = null, ): EnvActionControlEntity { return EnvActionControlEntity( id = UUID.randomUUID(), @@ -67,6 +71,8 @@ class EnvActionFixture { openBy = openBy, infractions = infractions, actionNumberOfControls = actionNumberOfControls, + actionTargetType = actionTargetTypeEnum, + vehicleType = vehicleTypeEnum ) } diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidatorUTest.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidatorUTest.kt index 1c654bc04..b679885dd 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidatorUTest.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidatorUTest.kt @@ -1,5 +1,6 @@ package fr.gouv.cacem.monitorenv.domain.validators.mission +import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionControl.ActionTargetTypeEnum import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionControl.infraction.InfractionTypeEnum import fr.gouv.cacem.monitorenv.domain.exceptions.BackendUsageException import fr.gouv.cacem.monitorenv.domain.use_cases.actions.fixtures.EnvActionFixture.Companion.anEnvActionControl @@ -161,6 +162,30 @@ class MissionValidatorUTest { assertThat(assertThrows.message).isEqualTo("Le trigramme \"ouvert par\" doit avoir 3 lettres") } + @Test + fun `validate should throw an exception if there is a control actionTargetType as VEHICULE without vehiculeType when mission has ended`( + ) { + val endDateTimeUtc = ZonedDateTime.now().plusSeconds(1) + val anEnvActionControl = anEnvActionControl(actionTargetTypeEnum = ActionTargetTypeEnum.VEHICLE) + val mission = aMissionEntity(endDateTimeUtc = endDateTimeUtc, envAction = listOf(anEnvActionControl)) + + val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } + assertThat(assertThrows.message).isEqualTo("Le type de véhicule est obligatoire") + } + + + @ParameterizedTest + @EnumSource(value = ActionTargetTypeEnum::class, names = ["VEHICLE"], mode = EnumSource.Mode.EXCLUDE) + fun `validate should pass if there is a control actionTargetType as targetType other than VEHICLE without vehiculeType when mission has ended`( + targetType: ActionTargetTypeEnum, + ) { + val endDateTimeUtc = ZonedDateTime.now().plusSeconds(1) + val anEnvActionControl = anEnvActionControl(actionTargetTypeEnum = targetType) + val mission = aMissionEntity(endDateTimeUtc = endDateTimeUtc, envAction = listOf(anEnvActionControl)) + + missionValidator.validate(mission) + } + @Test fun `validate should throw an exception if there is a surveillance with a start date before mission starting date`() { val startDateTimeUtc = ZonedDateTime.parse("2020-03-04T00:00:00.000Z") From b20c7f66cd85c1351c2b50bb2f8478e250429033 Mon Sep 17 00:00:00 2001 From: Maxime Perrault Date: Thu, 16 Jan 2025 17:09:23 +0100 Subject: [PATCH 03/14] tech: add case for envaction dates --- .../validators/mission/MissionValidator.kt | 31 +++--- .../actions/fixtures/EnvActionFixture.kt | 2 +- .../mission/MissionValidatorUTest.kt | 97 ++++++++++++++----- 3 files changed, 94 insertions(+), 36 deletions(-) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidator.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidator.kt index 650c39693..02894c61a 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidator.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidator.kt @@ -1,6 +1,7 @@ package fr.gouv.cacem.monitorenv.domain.validators.mission import fr.gouv.cacem.monitorenv.domain.entities.mission.MissionEntity +import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.ActionTypeEnum import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.EnvActionEntity import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionControl.ActionTargetTypeEnum import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionControl.EnvActionControlEntity @@ -87,15 +88,14 @@ class MissionValidator : Validator { } val sumOfNbTarget = control.infractions?.sumOf { infraction -> infraction.nbTarget } - if (sumOfNbTarget != null) { - if (control.actionNumberOfControls == null || sumOfNbTarget > control.actionNumberOfControls) { - throw BackendUsageException( - BackendUsageErrorCode.UNVALID_PROPERTY, - "Le nombre de cibles excède le nombre total de contrôles", - ) - } + if (sumOfNbTarget != 0 && sumOfNbTarget != null && (control.actionNumberOfControls != null && sumOfNbTarget > control.actionNumberOfControls)) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "Le nombre de cibles excède le nombre total de contrôles", + ) } + control.infractions?.forEach { infraction -> if (infraction.infractionType !== InfractionTypeEnum.WAITING && infraction.natinf?.isEmpty() == true) { throw BackendUsageException( @@ -130,28 +130,33 @@ class MissionValidator : Validator { envAction: EnvActionEntity, mission: MissionEntity, ) { - if (envAction.actionStartDateTimeUtc?.isBefore(mission.startDateTimeUtc) == true) { + val actionType = if (envAction.actionType === ActionTypeEnum.CONTROL) "du contrôle" else "de la surveillance" + if (envAction.actionStartDateTimeUtc?.isAfter(mission.startDateTimeUtc) == false + && envAction.actionStartDateTimeUtc?.isEqual(mission.startDateTimeUtc) == false + ) { throw BackendUsageException( BackendUsageErrorCode.UNVALID_PROPERTY, - "La date de début du contrôle doit être postérieure à celle du début de mission", + "La date de début $actionType doit être postérieure à celle du début de mission", ) } if (envAction.actionStartDateTimeUtc?.isAfter(mission.endDateTimeUtc) == true) { throw BackendUsageException( BackendUsageErrorCode.UNVALID_PROPERTY, - "La date de début du contrôle doit être antérieure à celle de fin de mission", + "La date de début $actionType doit être antérieure à celle de fin de mission", ) } - if (envAction.actionEndDateTimeUtc?.isAfter(mission.endDateTimeUtc) == true) { + if (envAction.actionEndDateTimeUtc?.isBefore(mission.endDateTimeUtc) == false && + envAction.actionEndDateTimeUtc?.isEqual(mission.endDateTimeUtc) == false + ) { throw BackendUsageException( BackendUsageErrorCode.UNVALID_PROPERTY, - "La date de fin du contrôle doit être antérieure à celle de fin de mission", + "La date de fin $actionType doit être antérieure à celle de fin de mission", ) } if (envAction.actionEndDateTimeUtc?.isBefore(mission.startDateTimeUtc) == true) { throw BackendUsageException( BackendUsageErrorCode.UNVALID_PROPERTY, - "La date de fin du contrôle doit être postérieure à celle du début de mission", + "La date de fin $actionType doit être postérieure à celle du début de mission", ) } if (envAction.openBy?.length != NB_CHAR_MAX) { diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/actions/fixtures/EnvActionFixture.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/actions/fixtures/EnvActionFixture.kt index 99e6bb672..f73f4f9cd 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/actions/fixtures/EnvActionFixture.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/actions/fixtures/EnvActionFixture.kt @@ -79,7 +79,7 @@ class EnvActionFixture { fun anEnvActionSurveillance( startTime: ZonedDateTime? = null, endTime: ZonedDateTime? = null, - openBy: String? = null, + openBy: String? = "CDA", ): EnvActionSurveillanceEntity { return EnvActionSurveillanceEntity( id = UUID.randomUUID(), diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidatorUTest.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidatorUTest.kt index b679885dd..8f0839c26 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidatorUTest.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidatorUTest.kt @@ -47,6 +47,25 @@ class MissionValidatorUTest { assertThat(assertThrows.message).isEqualTo("Le type de mission est requis") } + + @ParameterizedTest + @ValueSource(strings = ["A", "AA", "AAAA"]) + fun `validate should throw an exception if openBy is not a trigram`(openBy: String) { + val mission = aMissionEntity(openBy = openBy) + + val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } + assertThat(assertThrows.message).isEqualTo("Le trigramme \"ouvert par\" doit avoir 3 lettres") + } + + @ParameterizedTest + @ValueSource(strings = ["A", "AA", "AAAA"]) + fun `validate should throw an exception if completedBy is not a trigram`(completedBy: String) { + val mission = aMissionEntity(completedBy = completedBy) + + val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } + assertThat(assertThrows.message).isEqualTo("Le trigramme \"complété par\" doit avoir 3 lettres") + } + @Test fun `validate should throw an exception if there is a control with a start date before mission starting date`() { val startDateTimeUtc = ZonedDateTime.parse("2020-03-04T00:00:00.000Z") @@ -98,8 +117,14 @@ class MissionValidatorUTest { @Test fun `validate should throw an exception if there is a control with an end date before mission starting date`() { val startDateTimeUtc = ZonedDateTime.parse("2020-03-04T00:00:00.000Z") + val endDateTimeUtc = ZonedDateTime.parse("2021-03-04T00:00:00.000Z") + val anEnvActionControl = anEnvActionControl(endTime = startDateTimeUtc.minusSeconds(1)) - val mission = aMissionEntity(startDateTimeUtc = startDateTimeUtc, envAction = listOf(anEnvActionControl)) + val mission = aMissionEntity( + startDateTimeUtc = startDateTimeUtc, + endDateTimeUtc = endDateTimeUtc, + envAction = listOf(anEnvActionControl) + ) val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } assertThat( @@ -107,6 +132,20 @@ class MissionValidatorUTest { ).isEqualTo("La date de fin du contrôle doit être postérieure à celle du début de mission") } + @Test + fun `validate should pass if there is a control with an date equal to mission's`() { + val startDateTimeUtc = ZonedDateTime.parse("2020-03-04T00:00:00.000Z") + val endDateTimeUtc = ZonedDateTime.parse("2021-03-04T00:00:00.000Z") + val anEnvActionControl = anEnvActionControl(startTime = startDateTimeUtc, endTime = endDateTimeUtc) + val mission = aMissionEntity( + startDateTimeUtc = startDateTimeUtc, + endDateTimeUtc = endDateTimeUtc, + envAction = listOf(anEnvActionControl) + ) + + missionValidator.validate(mission) + } + @ParameterizedTest @EnumSource(value = InfractionTypeEnum::class, names = ["WAITING"], mode = EnumSource.Mode.EXCLUDE) fun `validate should throw an exception if there is a control with infractionType other than WAITING that doesnt have a NATINF`( @@ -143,6 +182,18 @@ class MissionValidatorUTest { assertThat(assertThrows.message).isEqualTo("Le nombre de cibles excède le nombre total de contrôles") } + @Test + fun `validate should pass if there is a control with infractions that got less nbTarget than the mission actionNumberOfControls `() { + val anEnvActionControl = + anEnvActionControl( + actionNumberOfControls = 2, + infractions = listOf(anInfraction(nbTarget = 1)), + ) + val mission = aMissionEntity(envAction = listOf(anEnvActionControl)) + + missionValidator.validate(mission) + } + @Test fun `validate should pass if there is a control with infractionType = WAITING that doesnt have a NATINF`() { val anEnvActionControl = @@ -195,7 +246,7 @@ class MissionValidatorUTest { val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } assertThat( assertThrows.message, - ).isEqualTo("La date de début du contrôle doit être postérieure à celle du début de mission") + ).isEqualTo("La date de début de la surveillance doit être postérieure à celle du début de mission") } @Test @@ -213,7 +264,7 @@ class MissionValidatorUTest { val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } assertThat( assertThrows.message, - ).isEqualTo("La date de fin du contrôle doit être antérieure à celle de fin de mission") + ).isEqualTo("La date de fin de la surveillance doit être antérieure à celle de fin de mission") } @Test @@ -231,37 +282,39 @@ class MissionValidatorUTest { val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } assertThat( assertThrows.message, - ).isEqualTo("La date de fin du contrôle doit être antérieure à celle de fin de mission") + ).isEqualTo("La date de fin de la surveillance doit être antérieure à celle de fin de mission") } @Test fun `validate should throw an exception if there is a surveillance with an end date before mission starting date`() { val startDateTimeUtc = ZonedDateTime.parse("2020-03-04T00:00:00.000Z") - val anEnvActionControl = anEnvActionSurveillance(endTime = startDateTimeUtc.minusSeconds(1)) - val mission = aMissionEntity(startDateTimeUtc = startDateTimeUtc, envAction = listOf(anEnvActionControl)) + val endDateTimeUtc = ZonedDateTime.parse("2021-03-04T00:00:00.000Z") + + val anEnvActionSurveillance = anEnvActionSurveillance(endTime = startDateTimeUtc.minusSeconds(1)) + val mission = aMissionEntity( + startDateTimeUtc = startDateTimeUtc, + endDateTimeUtc = endDateTimeUtc, + envAction = listOf(anEnvActionSurveillance) + ) val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } assertThat( assertThrows.message, - ).isEqualTo("La date de fin du contrôle doit être postérieure à celle du début de mission") - } - - @ParameterizedTest - @ValueSource(strings = ["A", "AA", "AAAA"]) - fun `validate should throw an exception if openBy is not a trigram`(openBy: String) { - val mission = aMissionEntity(openBy = openBy) - - val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } - assertThat(assertThrows.message).isEqualTo("Le trigramme \"ouvert par\" doit avoir 3 lettres") + ).isEqualTo("La date de fin de la surveillance doit être postérieure à celle du début de mission") } - @ParameterizedTest - @ValueSource(strings = ["A", "AA", "AAAA"]) - fun `validate should throw an exception if completedBy is not a trigram`(completedBy: String) { - val mission = aMissionEntity(completedBy = completedBy) + @Test + fun `validate should pass if there is a surveillance with an date equal to mission's`() { + val startDateTimeUtc = ZonedDateTime.parse("2020-03-04T00:00:00.000Z") + val endDateTimeUtc = ZonedDateTime.parse("2020-03-04T00:00:00.000Z") + val anEnvActionSurveillance = anEnvActionSurveillance(startTime = startDateTimeUtc, endTime = endDateTimeUtc) + val mission = aMissionEntity( + startDateTimeUtc = startDateTimeUtc, + endDateTimeUtc = endDateTimeUtc, + envAction = listOf(anEnvActionSurveillance) + ) - val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } - assertThat(assertThrows.message).isEqualTo("Le trigramme \"complété par\" doit avoir 3 lettres") + missionValidator.validate(mission) } @Test From 7ffe641c7724d185b738ce0ad8c2112d4caf9203 Mon Sep 17 00:00:00 2001 From: Maxime Perrault Date: Thu, 16 Jan 2025 17:10:55 +0100 Subject: [PATCH 04/14] tech: add case for envaction dates --- .../monitorenv/domain/validators/Validator.kt | 2 +- .../validators/mission/MissionValidator.kt | 19 ++--- .../actions/fixtures/EnvActionFixture.kt | 2 +- .../missions/fixtures/MissionFixture.kt | 4 +- .../mission/MissionValidatorUTest.kt | 77 ++++++++++--------- 5 files changed, 51 insertions(+), 53 deletions(-) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/Validator.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/Validator.kt index d0bcb4d2d..c9a543e4a 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/Validator.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/Validator.kt @@ -1,5 +1,5 @@ package fr.gouv.cacem.monitorenv.domain.validators -interface Validator { +fun interface Validator { fun validate(obj: T) } diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidator.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidator.kt index 02894c61a..81c6eeb2a 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidator.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidator.kt @@ -74,17 +74,15 @@ class MissionValidator : Validator { private fun validateControl( control: EnvActionControlEntity, mission: MissionEntity, - isMissionEnded: Boolean + isMissionEnded: Boolean, ) { validateEnvAction(control, mission) - if (isMissionEnded) { - if (control.vehicleType === null && control.actionTargetType === ActionTargetTypeEnum.VEHICLE) { - throw BackendUsageException( - BackendUsageErrorCode.UNVALID_PROPERTY, - "Le type de véhicule est obligatoire", - ) - } + if (isMissionEnded && control.vehicleType === null && control.actionTargetType === ActionTargetTypeEnum.VEHICLE) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "Le type de véhicule est obligatoire", + ) } val sumOfNbTarget = control.infractions?.sumOf { infraction -> infraction.nbTarget } @@ -95,7 +93,6 @@ class MissionValidator : Validator { ) } - control.infractions?.forEach { infraction -> if (infraction.infractionType !== InfractionTypeEnum.WAITING && infraction.natinf?.isEmpty() == true) { throw BackendUsageException( @@ -131,8 +128,8 @@ class MissionValidator : Validator { mission: MissionEntity, ) { val actionType = if (envAction.actionType === ActionTypeEnum.CONTROL) "du contrôle" else "de la surveillance" - if (envAction.actionStartDateTimeUtc?.isAfter(mission.startDateTimeUtc) == false - && envAction.actionStartDateTimeUtc?.isEqual(mission.startDateTimeUtc) == false + if (envAction.actionStartDateTimeUtc?.isAfter(mission.startDateTimeUtc) == false && + envAction.actionStartDateTimeUtc?.isEqual(mission.startDateTimeUtc) == false ) { throw BackendUsageException( BackendUsageErrorCode.UNVALID_PROPERTY, diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/actions/fixtures/EnvActionFixture.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/actions/fixtures/EnvActionFixture.kt index f73f4f9cd..dc43369ed 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/actions/fixtures/EnvActionFixture.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/actions/fixtures/EnvActionFixture.kt @@ -72,7 +72,7 @@ class EnvActionFixture { infractions = infractions, actionNumberOfControls = actionNumberOfControls, actionTargetType = actionTargetTypeEnum, - vehicleType = vehicleTypeEnum + vehicleType = vehicleTypeEnum, ) } diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/fixtures/MissionFixture.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/fixtures/MissionFixture.kt index 47721f457..38f8ca1d0 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/fixtures/MissionFixture.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/fixtures/MissionFixture.kt @@ -21,7 +21,7 @@ class MissionFixture { missionTypes: List = listOf(MissionTypeEnum.LAND), openBy: String? = null, completedBy: String? = null, - envAction: List = emptyList(), + envActions: List = emptyList(), ): MissionEntity { return MissionEntity( id = id, @@ -41,7 +41,7 @@ class MissionFixture { isGeometryComputedFromControls = false, updatedAtUtc = null, createdAtUtc = null, - envActions = envAction, + envActions = envActions, ) } diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidatorUTest.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidatorUTest.kt index 8f0839c26..ae06383f0 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidatorUTest.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidatorUTest.kt @@ -47,7 +47,6 @@ class MissionValidatorUTest { assertThat(assertThrows.message).isEqualTo("Le type de mission est requis") } - @ParameterizedTest @ValueSource(strings = ["A", "AA", "AAAA"]) fun `validate should throw an exception if openBy is not a trigram`(openBy: String) { @@ -70,7 +69,7 @@ class MissionValidatorUTest { fun `validate should throw an exception if there is a control with a start date before mission starting date`() { val startDateTimeUtc = ZonedDateTime.parse("2020-03-04T00:00:00.000Z") val anEnvActionControl = anEnvActionControl(startTime = startDateTimeUtc.minusSeconds(1)) - val mission = aMissionEntity(startDateTimeUtc = startDateTimeUtc, envAction = listOf(anEnvActionControl)) + val mission = aMissionEntity(startDateTimeUtc = startDateTimeUtc, envActions = listOf(anEnvActionControl)) val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } assertThat( @@ -87,7 +86,7 @@ class MissionValidatorUTest { aMissionEntity( startDateTimeUtc = startDateTimeUtc, endDateTimeUtc = endDateTimeUtc, - envAction = listOf(anEnvActionControl), + envActions = listOf(anEnvActionControl), ) val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } @@ -105,7 +104,7 @@ class MissionValidatorUTest { aMissionEntity( startDateTimeUtc = startDateTimeUtc, endDateTimeUtc = endDateTimeUtc, - envAction = listOf(anEnvActionControl), + envActions = listOf(anEnvActionControl), ) val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } @@ -120,11 +119,12 @@ class MissionValidatorUTest { val endDateTimeUtc = ZonedDateTime.parse("2021-03-04T00:00:00.000Z") val anEnvActionControl = anEnvActionControl(endTime = startDateTimeUtc.minusSeconds(1)) - val mission = aMissionEntity( - startDateTimeUtc = startDateTimeUtc, - endDateTimeUtc = endDateTimeUtc, - envAction = listOf(anEnvActionControl) - ) + val mission = + aMissionEntity( + startDateTimeUtc = startDateTimeUtc, + endDateTimeUtc = endDateTimeUtc, + envActions = listOf(anEnvActionControl), + ) val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } assertThat( @@ -137,11 +137,12 @@ class MissionValidatorUTest { val startDateTimeUtc = ZonedDateTime.parse("2020-03-04T00:00:00.000Z") val endDateTimeUtc = ZonedDateTime.parse("2021-03-04T00:00:00.000Z") val anEnvActionControl = anEnvActionControl(startTime = startDateTimeUtc, endTime = endDateTimeUtc) - val mission = aMissionEntity( - startDateTimeUtc = startDateTimeUtc, - endDateTimeUtc = endDateTimeUtc, - envAction = listOf(anEnvActionControl) - ) + val mission = + aMissionEntity( + startDateTimeUtc = startDateTimeUtc, + endDateTimeUtc = endDateTimeUtc, + envActions = listOf(anEnvActionControl), + ) missionValidator.validate(mission) } @@ -152,7 +153,7 @@ class MissionValidatorUTest { infractionType: InfractionTypeEnum, ) { val anEnvActionControl = anEnvActionControl(infractions = listOf(anInfraction(infractionType = infractionType))) - val mission = aMissionEntity(envAction = listOf(anEnvActionControl)) + val mission = aMissionEntity(envActions = listOf(anEnvActionControl)) val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } assertThat( @@ -163,7 +164,7 @@ class MissionValidatorUTest { @Test fun `validate should throw an exception if there is a control with infraction and nbTarget is less than 1`() { val anEnvActionControl = anEnvActionControl(infractions = listOf(anInfraction(nbTarget = 0))) - val mission = aMissionEntity(envAction = listOf(anEnvActionControl)) + val mission = aMissionEntity(envActions = listOf(anEnvActionControl)) val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } assertThat(assertThrows.message).isEqualTo("le nombre minimum de cible est 1") @@ -176,7 +177,7 @@ class MissionValidatorUTest { actionNumberOfControls = 10, infractions = listOf(anInfraction(nbTarget = 10), anInfraction(nbTarget = 5)), ) - val mission = aMissionEntity(envAction = listOf(anEnvActionControl)) + val mission = aMissionEntity(envActions = listOf(anEnvActionControl)) val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } assertThat(assertThrows.message).isEqualTo("Le nombre de cibles excède le nombre total de contrôles") @@ -189,7 +190,7 @@ class MissionValidatorUTest { actionNumberOfControls = 2, infractions = listOf(anInfraction(nbTarget = 1)), ) - val mission = aMissionEntity(envAction = listOf(anEnvActionControl)) + val mission = aMissionEntity(envActions = listOf(anEnvActionControl)) missionValidator.validate(mission) } @@ -198,7 +199,7 @@ class MissionValidatorUTest { fun `validate should pass if there is a control with infractionType = WAITING that doesnt have a NATINF`() { val anEnvActionControl = anEnvActionControl(infractions = listOf(anInfraction(infractionType = InfractionTypeEnum.WAITING))) - val mission = aMissionEntity(envAction = listOf(anEnvActionControl)) + val mission = aMissionEntity(envActions = listOf(anEnvActionControl)) missionValidator.validate(mission) } @@ -207,24 +208,22 @@ class MissionValidatorUTest { @ValueSource(strings = ["A", "AA", "AAAA"]) fun `validate should throw an exception if there is a control with openBy is not a trigram`(openBy: String) { val anEnvActionControl = anEnvActionControl(openBy = openBy) - val mission = aMissionEntity(envAction = listOf(anEnvActionControl)) + val mission = aMissionEntity(envActions = listOf(anEnvActionControl)) val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } assertThat(assertThrows.message).isEqualTo("Le trigramme \"ouvert par\" doit avoir 3 lettres") } @Test - fun `validate should throw an exception if there is a control actionTargetType as VEHICULE without vehiculeType when mission has ended`( - ) { + fun `validate should throw an exception if there is a control actionTargetType as VEHICULE without vehiculeType when mission has ended`() { val endDateTimeUtc = ZonedDateTime.now().plusSeconds(1) val anEnvActionControl = anEnvActionControl(actionTargetTypeEnum = ActionTargetTypeEnum.VEHICLE) - val mission = aMissionEntity(endDateTimeUtc = endDateTimeUtc, envAction = listOf(anEnvActionControl)) + val mission = aMissionEntity(endDateTimeUtc = endDateTimeUtc, envActions = listOf(anEnvActionControl)) val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } assertThat(assertThrows.message).isEqualTo("Le type de véhicule est obligatoire") } - @ParameterizedTest @EnumSource(value = ActionTargetTypeEnum::class, names = ["VEHICLE"], mode = EnumSource.Mode.EXCLUDE) fun `validate should pass if there is a control actionTargetType as targetType other than VEHICLE without vehiculeType when mission has ended`( @@ -232,7 +231,7 @@ class MissionValidatorUTest { ) { val endDateTimeUtc = ZonedDateTime.now().plusSeconds(1) val anEnvActionControl = anEnvActionControl(actionTargetTypeEnum = targetType) - val mission = aMissionEntity(endDateTimeUtc = endDateTimeUtc, envAction = listOf(anEnvActionControl)) + val mission = aMissionEntity(endDateTimeUtc = endDateTimeUtc, envActions = listOf(anEnvActionControl)) missionValidator.validate(mission) } @@ -241,7 +240,7 @@ class MissionValidatorUTest { fun `validate should throw an exception if there is a surveillance with a start date before mission starting date`() { val startDateTimeUtc = ZonedDateTime.parse("2020-03-04T00:00:00.000Z") val anEnvActionSurveillance = anEnvActionSurveillance(startTime = startDateTimeUtc.minusSeconds(1)) - val mission = aMissionEntity(startDateTimeUtc = startDateTimeUtc, envAction = listOf(anEnvActionSurveillance)) + val mission = aMissionEntity(startDateTimeUtc = startDateTimeUtc, envActions = listOf(anEnvActionSurveillance)) val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } assertThat( @@ -258,7 +257,7 @@ class MissionValidatorUTest { aMissionEntity( startDateTimeUtc = startDateTimeUtc, endDateTimeUtc = endDateTimeUtc, - envAction = listOf(anEnvActionSurveillance), + envActions = listOf(anEnvActionSurveillance), ) val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } @@ -276,7 +275,7 @@ class MissionValidatorUTest { aMissionEntity( startDateTimeUtc = startDateTimeUtc, endDateTimeUtc = endDateTimeUtc, - envAction = listOf(anEnvActionSurveillance), + envActions = listOf(anEnvActionSurveillance), ) val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } @@ -291,11 +290,12 @@ class MissionValidatorUTest { val endDateTimeUtc = ZonedDateTime.parse("2021-03-04T00:00:00.000Z") val anEnvActionSurveillance = anEnvActionSurveillance(endTime = startDateTimeUtc.minusSeconds(1)) - val mission = aMissionEntity( - startDateTimeUtc = startDateTimeUtc, - endDateTimeUtc = endDateTimeUtc, - envAction = listOf(anEnvActionSurveillance) - ) + val mission = + aMissionEntity( + startDateTimeUtc = startDateTimeUtc, + endDateTimeUtc = endDateTimeUtc, + envActions = listOf(anEnvActionSurveillance), + ) val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } assertThat( @@ -308,11 +308,12 @@ class MissionValidatorUTest { val startDateTimeUtc = ZonedDateTime.parse("2020-03-04T00:00:00.000Z") val endDateTimeUtc = ZonedDateTime.parse("2020-03-04T00:00:00.000Z") val anEnvActionSurveillance = anEnvActionSurveillance(startTime = startDateTimeUtc, endTime = endDateTimeUtc) - val mission = aMissionEntity( - startDateTimeUtc = startDateTimeUtc, - endDateTimeUtc = endDateTimeUtc, - envAction = listOf(anEnvActionSurveillance) - ) + val mission = + aMissionEntity( + startDateTimeUtc = startDateTimeUtc, + endDateTimeUtc = endDateTimeUtc, + envActions = listOf(anEnvActionSurveillance), + ) missionValidator.validate(mission) } From 1e3d627fbc988cfa85b347e62a8bbef0960c4c42 Mon Sep 17 00:00:00 2001 From: Maxime Perrault Date: Mon, 20 Jan 2025 16:10:01 +0100 Subject: [PATCH 05/14] tech: add mission validation --- .../validators/mission/MissionValidator.kt | 39 ++++++++++++++--- .../mission/MissionValidatorUTest.kt | 43 +++++++++++++++++++ 2 files changed, 76 insertions(+), 6 deletions(-) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidator.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidator.kt index 81c6eeb2a..27ce9cfff 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidator.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidator.kt @@ -66,7 +66,7 @@ class MissionValidator : Validator { } if (envAction is EnvActionSurveillanceEntity) { - validateSurveillance(envAction, mission) + validateSurveillance(envAction, mission, isMissionEnded) } } } @@ -78,11 +78,14 @@ class MissionValidator : Validator { ) { validateEnvAction(control, mission) - if (isMissionEnded && control.vehicleType === null && control.actionTargetType === ActionTargetTypeEnum.VEHICLE) { - throw BackendUsageException( - BackendUsageErrorCode.UNVALID_PROPERTY, - "Le type de véhicule est obligatoire", - ) + if (isMissionEnded) { + if (control.vehicleType === null && control.actionTargetType === ActionTargetTypeEnum.VEHICLE) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "Le type de véhicule est obligatoire", + ) + } + validateControlPlan(control) } val sumOfNbTarget = control.infractions?.sumOf { infraction -> infraction.nbTarget } @@ -112,6 +115,7 @@ class MissionValidator : Validator { private fun validateSurveillance( surveillance: EnvActionSurveillanceEntity, mission: MissionEntity, + isMissionEnded: Boolean, ) { validateEnvAction(surveillance, mission) @@ -121,6 +125,9 @@ class MissionValidator : Validator { "Le trigramme \"complété par\" doit avoir 3 lettres", ) } + if (isMissionEnded) { + validateControlPlan(surveillance) + } } private fun validateEnvAction( @@ -162,5 +169,25 @@ class MissionValidator : Validator { "Le trigramme \"ouvert par\" doit avoir 3 lettres", ) } + + + } + + private fun validateControlPlan(envAction: EnvActionEntity) { + if (envAction.controlPlans?.isEmpty() == true) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "Le plan de contrôle est obligatoire", + ) + } else { + envAction.controlPlans?.forEach { controlPlan -> + if (controlPlan.subThemeIds?.isEmpty() == true) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "Le sous-thème du plan de contrôle est obligatoire", + ) + } + } + } } } diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidatorUTest.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidatorUTest.kt index ae06383f0..f684257c3 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidatorUTest.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidatorUTest.kt @@ -1,5 +1,6 @@ package fr.gouv.cacem.monitorenv.domain.validators.mission +import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.EnvActionControlPlanEntity import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionControl.ActionTargetTypeEnum import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionControl.infraction.InfractionTypeEnum import fr.gouv.cacem.monitorenv.domain.exceptions.BackendUsageException @@ -214,6 +215,27 @@ class MissionValidatorUTest { assertThat(assertThrows.message).isEqualTo("Le trigramme \"ouvert par\" doit avoir 3 lettres") } + @Test + fun `validate should throw an exception if there is a control without control plans when mission has ended`() { + val endDateTimeUtc = ZonedDateTime.now().plusSeconds(1) + val anEnvActionControl = anEnvActionControl(controlPlans = listOf()) + val mission = aMissionEntity(endDateTimeUtc = endDateTimeUtc, envActions = listOf(anEnvActionControl)) + + val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } + assertThat(assertThrows.message).isEqualTo("Le plan de contrôle est obligatoire") + } + + @Test + fun `validate should throw an exception if there is a control with control plan without subtheme when mission has ended`() { + val endDateTimeUtc = ZonedDateTime.now().plusSeconds(1) + val anEnvActionControl = + anEnvActionControl(controlPlans = listOf(EnvActionControlPlanEntity(subThemeIds = listOf()))) + val mission = aMissionEntity(endDateTimeUtc = endDateTimeUtc, envActions = listOf(anEnvActionControl)) + + val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } + assertThat(assertThrows.message).isEqualTo("Le sous-thème du plan de contrôle est obligatoire") + } + @Test fun `validate should throw an exception if there is a control actionTargetType as VEHICULE without vehiculeType when mission has ended`() { val endDateTimeUtc = ZonedDateTime.now().plusSeconds(1) @@ -318,6 +340,27 @@ class MissionValidatorUTest { missionValidator.validate(mission) } + @Test + fun `validate should throw an exception if there is a surveillance without control plans when mission has ended`() { + val endDateTimeUtc = ZonedDateTime.now().plusSeconds(1) + val anEnvActionSurveillance = anEnvActionSurveillance(controlPlans = listOf()) + val mission = aMissionEntity(endDateTimeUtc = endDateTimeUtc, envActions = listOf(anEnvActionSurveillance)) + + val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } + assertThat(assertThrows.message).isEqualTo("Le plan de contrôle est obligatoire") + } + + @Test + fun `validate should throw an exception if there is a surveillance with control plan without subtheme when mission has ended`() { + val endDateTimeUtc = ZonedDateTime.now().plusSeconds(1) + val anEnvActionSurveillance = + anEnvActionSurveillance(controlPlans = listOf(EnvActionControlPlanEntity(subThemeIds = listOf()))) + val mission = aMissionEntity(endDateTimeUtc = endDateTimeUtc, envActions = listOf(anEnvActionSurveillance)) + + val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } + assertThat(assertThrows.message).isEqualTo("Le sous-thème du plan de contrôle est obligatoire") + } + @Test fun `validate should pass for a valid MissionEntity`() { val mission = aMissionEntity() From be69de95714db8e598ba18eaed8f4d9db6b15189 Mon Sep 17 00:00:00 2001 From: Maxime Perrault Date: Mon, 20 Jan 2025 16:10:48 +0100 Subject: [PATCH 06/14] tech: add vigilance area validation --- .../CreateOrUpdateVigilanceArea.kt | 7 +- .../validators/mission/MissionValidator.kt | 2 - .../vigilance_area/VigilanceAreaValidator.kt | 71 +++++++++ .../fixtures/VigilanceAreaFixture.kt | 33 +++-- .../VigilanceAreaValidatorUTest.kt | 140 ++++++++++++++++++ 5 files changed, 239 insertions(+), 14 deletions(-) create mode 100644 backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/vigilance_area/VigilanceAreaValidator.kt create mode 100644 backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/vigilance_area/VigilanceAreaValidatorUTest.kt diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/vigilanceArea/CreateOrUpdateVigilanceArea.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/vigilanceArea/CreateOrUpdateVigilanceArea.kt index 5ad5e3ae4..413c45151 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/vigilanceArea/CreateOrUpdateVigilanceArea.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/vigilanceArea/CreateOrUpdateVigilanceArea.kt @@ -7,6 +7,8 @@ import fr.gouv.cacem.monitorenv.domain.exceptions.BackendUsageException import fr.gouv.cacem.monitorenv.domain.repositories.IFacadeAreasRepository import fr.gouv.cacem.monitorenv.domain.repositories.IVigilanceAreaRepository import fr.gouv.cacem.monitorenv.domain.use_cases.dashboard.SaveDashboard +import fr.gouv.cacem.monitorenv.domain.validators.UseCaseValidation +import fr.gouv.cacem.monitorenv.domain.validators.vigilance_area.VigilanceAreaValidator import org.slf4j.LoggerFactory @UseCase @@ -16,7 +18,10 @@ class CreateOrUpdateVigilanceArea( ) { private val logger = LoggerFactory.getLogger(SaveDashboard::class.java) - fun execute(vigilanceArea: VigilanceAreaEntity): VigilanceAreaEntity { + fun execute( + @UseCaseValidation(validator = VigilanceAreaValidator::class) vigilanceArea: + VigilanceAreaEntity, + ): VigilanceAreaEntity { logger.info("Attempt to CREATE or UPDATE vigilance area ${vigilanceArea.id}") try { val seaFront = diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidator.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidator.kt index 27ce9cfff..2d684311c 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidator.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidator.kt @@ -169,8 +169,6 @@ class MissionValidator : Validator { "Le trigramme \"ouvert par\" doit avoir 3 lettres", ) } - - } private fun validateControlPlan(envAction: EnvActionEntity) { diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/vigilance_area/VigilanceAreaValidator.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/vigilance_area/VigilanceAreaValidator.kt new file mode 100644 index 000000000..7a38f9650 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/vigilance_area/VigilanceAreaValidator.kt @@ -0,0 +1,71 @@ +package fr.gouv.cacem.monitorenv.domain.validators.vigilance_area + +import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.EndingConditionEnum +import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.FrequencyEnum +import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.VigilanceAreaEntity +import fr.gouv.cacem.monitorenv.domain.exceptions.BackendUsageErrorCode +import fr.gouv.cacem.monitorenv.domain.exceptions.BackendUsageException +import fr.gouv.cacem.monitorenv.domain.validators.Validator +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Component + +private const val NB_CHAR_MAX = 3 + +@Component +class VigilanceAreaValidator : Validator { + private val logger = LoggerFactory.getLogger(VigilanceAreaValidator::class.java) + + override fun validate(vigilanceArea: VigilanceAreaEntity) { + logger.info("Validating vigilance area: ${vigilanceArea.id}") + + if (!vigilanceArea.isDraft) { + if (vigilanceArea.createdBy !== null && vigilanceArea.createdBy.length != NB_CHAR_MAX) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "Le trigramme \"créé par\" doit avoir 3 lettres", + ) + } + if (vigilanceArea.themes?.isEmpty() == true) { + throw BackendUsageException(BackendUsageErrorCode.UNVALID_PROPERTY, "Un thème est obligatoire") + } + if (!vigilanceArea.isAtAllTimes) { + if (vigilanceArea.startDatePeriod === null) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "La date de début est obligatoire", + ) + } + if (vigilanceArea.endDatePeriod === null) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "La date de fin est obligatoire", + ) + } + if (vigilanceArea.frequency === null) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "La fréquence est obligatoire", + ) + } + if (vigilanceArea.frequency !== FrequencyEnum.NONE && vigilanceArea.endingCondition === null) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "La condition de fin est obligatoire", + ) + } + if (vigilanceArea.endingCondition === EndingConditionEnum.END_DATE && vigilanceArea.endingOccurrenceDate === null) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "La date de fin de l'occurence est obligatoire", + ) + } + if (vigilanceArea.endingCondition === EndingConditionEnum.OCCURENCES_NUMBER && (vigilanceArea.endingOccurrencesNumber === null || vigilanceArea.endingOccurrencesNumber == 0)) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "Le nombre d'occurence est obligatoire", + ) + } + } + } + } +} diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/vigilanceArea/fixtures/VigilanceAreaFixture.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/vigilanceArea/fixtures/VigilanceAreaFixture.kt index 6a573d821..3e959fef9 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/vigilanceArea/fixtures/VigilanceAreaFixture.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/vigilanceArea/fixtures/VigilanceAreaFixture.kt @@ -10,33 +10,44 @@ import java.time.ZonedDateTime class VigilanceAreaFixture { companion object { - fun aVigilanceAreaEntity(): VigilanceAreaEntity { + fun aVigilanceAreaEntity( + createdBy: String = "ABC", + isDraft: Boolean = true, + themes: List = listOf("AMP"), + startDate: ZonedDateTime? = ZonedDateTime.parse("2024-01-15T00:00:00Z"), + endDate: ZonedDateTime? = ZonedDateTime.parse("2024-01-15T23:59:59Z"), + frequency: FrequencyEnum? = FrequencyEnum.ALL_WEEKS, + endingOccurenceDate: ZonedDateTime? = null, + endCondition: EndingConditionEnum = EndingConditionEnum.OCCURENCES_NUMBER, + endingOccurrencesNumber: Int? = 2, + isAtAllTimes: Boolean = false, + ): VigilanceAreaEntity { return VigilanceAreaEntity( id = 1, comments = "Basic area comments", computedEndDate = ZonedDateTime.parse("2024-01-25T00:00:00Z"), - createdBy = "ABC", - endDatePeriod = ZonedDateTime.parse("2024-01-15T23:59:59Z"), - endingCondition = EndingConditionEnum.OCCURENCES_NUMBER, - endingOccurrenceDate = null, - endingOccurrencesNumber = 2, + createdBy = createdBy, + endDatePeriod = endDate, + endingCondition = endCondition, + endingOccurrenceDate = endingOccurenceDate, + endingOccurrencesNumber = endingOccurrencesNumber, images = null, - frequency = FrequencyEnum.ALL_WEEKS, + frequency = frequency, geom = null, isArchived = false, isDeleted = false, - isDraft = true, + isDraft = isDraft, links = listOf(), linkedAMPs = listOf(1, 2), linkedRegulatoryAreas = listOf(1, 2), name = "Basic Area", source = "Internal", - startDatePeriod = ZonedDateTime.parse("2024-01-15T00:00:00Z"), - themes = listOf("AMP"), + startDatePeriod = startDate, + themes = themes, visibility = VisibilityEnum.PUBLIC, createdAt = null, updatedAt = null, - isAtAllTimes = false, + isAtAllTimes = isAtAllTimes, ) } diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/vigilance_area/VigilanceAreaValidatorUTest.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/vigilance_area/VigilanceAreaValidatorUTest.kt new file mode 100644 index 000000000..370d39456 --- /dev/null +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/vigilance_area/VigilanceAreaValidatorUTest.kt @@ -0,0 +1,140 @@ +package fr.gouv.cacem.monitorenv.domain.validators.vigilance_area + +import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.EndingConditionEnum +import fr.gouv.cacem.monitorenv.domain.exceptions.BackendUsageException +import fr.gouv.cacem.monitorenv.domain.use_cases.vigilanceArea.fixtures.VigilanceAreaFixture.Companion.aVigilanceAreaEntity +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Assertions.assertThrows +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.NullSource +import org.junit.jupiter.params.provider.ValueSource + +class VigilanceAreaValidatorUTest { + private val vigilanceAreaValidator = VigilanceAreaValidator() + + @ParameterizedTest + @ValueSource(strings = ["A", "AA", "AAAA"]) + fun `validate should throw an exception if createdBy is not a trigram when it is published`(createdBy: String) { + val vigilanceArea = aVigilanceAreaEntity(createdBy = createdBy, isDraft = false) + + val assertThrows = + assertThrows(BackendUsageException::class.java) { vigilanceAreaValidator.validate(vigilanceArea) } + assertThat(assertThrows.message).isEqualTo("Le trigramme \"créé par\" doit avoir 3 lettres") + } + + @Test + fun `validate should throw an exception if themes is empty when it is published`() { + val vigilanceArea = aVigilanceAreaEntity(themes = listOf(), isDraft = false) + + val assertThrows = + assertThrows(BackendUsageException::class.java) { vigilanceAreaValidator.validate(vigilanceArea) } + assertThat(assertThrows.message).isEqualTo("Un thème est obligatoire") + } + + @Test + fun `validate should throw an exception if startDate is null when it is published`() { + val vigilanceArea = aVigilanceAreaEntity(startDate = null, isDraft = false) + + val assertThrows = + assertThrows(BackendUsageException::class.java) { vigilanceAreaValidator.validate(vigilanceArea) } + assertThat(assertThrows.message).isEqualTo("La date de début est obligatoire") + } + + @Test + fun `validate should throw an exception if endDate is null when it is published`() { + val vigilanceArea = aVigilanceAreaEntity(endDate = null, isDraft = false) + + val assertThrows = + assertThrows(BackendUsageException::class.java) { vigilanceAreaValidator.validate(vigilanceArea) } + assertThat(assertThrows.message).isEqualTo("La date de fin est obligatoire") + } + + @Test + fun `validate should throw an exception if frequency is null when it is published`() { + val vigilanceArea = aVigilanceAreaEntity(frequency = null, isDraft = false) + + val assertThrows = + assertThrows(BackendUsageException::class.java) { vigilanceAreaValidator.validate(vigilanceArea) } + assertThat(assertThrows.message).isEqualTo("La fréquence est obligatoire") + } + + @Test + fun `validate should throw an exception if endingCondition is END_DATE and endingOccurenceDate is null when it is published`() { + val vigilanceArea = + aVigilanceAreaEntity( + endCondition = EndingConditionEnum.END_DATE, + endingOccurenceDate = null, + isDraft = false, + ) + + val assertThrows = + assertThrows(BackendUsageException::class.java) { vigilanceAreaValidator.validate(vigilanceArea) } + assertThat(assertThrows.message).isEqualTo("La date de fin de l'occurence est obligatoire") + } + + @ParameterizedTest + @ValueSource(ints = [0]) + @NullSource + fun `validate should throw an exception if endingCondition is OCCURENCES_NUMBER and endingOccurrencesNumber is null or zero when it is published`( + endingOccurrencesNumber: Int?, + ) { + val vigilanceArea = + aVigilanceAreaEntity( + endCondition = EndingConditionEnum.OCCURENCES_NUMBER, + endingOccurrencesNumber = endingOccurrencesNumber, + isDraft = false, + ) + + val assertThrows = + assertThrows(BackendUsageException::class.java) { vigilanceAreaValidator.validate(vigilanceArea) } + assertThat(assertThrows.message).isEqualTo("Le nombre d'occurence est obligatoire") + } + + @Test + fun `validate should throw an exception if frequency is null when it is published and it is not limitless`() { + val vigilanceArea = aVigilanceAreaEntity(frequency = null, isDraft = false) + + val assertThrows = + assertThrows(BackendUsageException::class.java) { vigilanceAreaValidator.validate(vigilanceArea) } + assertThat(assertThrows.message).isEqualTo("La fréquence est obligatoire") + } + + @Test + fun `validate should pass if the vigilance area is limitless`() { + val vigilanceArea = + aVigilanceAreaEntity( + startDate = null, + endDate = null, + endingOccurenceDate = null, + endingOccurrencesNumber = 0, + isAtAllTimes = true, + endCondition = EndingConditionEnum.END_DATE, + ) + + vigilanceAreaValidator.validate(vigilanceArea) + } + + @Test + fun `validate should pass if the vigilance area is draft`() { + val vigilanceArea = + aVigilanceAreaEntity( + isDraft = true, + startDate = null, + endDate = null, + endingOccurenceDate = null, + endingOccurrencesNumber = 0, + isAtAllTimes = false, + endCondition = EndingConditionEnum.END_DATE, + ) + + vigilanceAreaValidator.validate(vigilanceArea) + } + + @Test + fun `validate should pass for a valid VigilanceAreaEntity`() { + val vigilanceArea = aVigilanceAreaEntity() + + vigilanceAreaValidator.validate(vigilanceArea) + } +} From e6793efa129cbe93418cffe5eea92b0cebbb78a1 Mon Sep 17 00:00:00 2001 From: Maxime Perrault Date: Mon, 20 Jan 2025 16:11:08 +0100 Subject: [PATCH 07/14] tech: add reporting validation --- .../reportings/CreateOrUpdateReporting.kt | 7 +- .../reporting/ReportingValidator.kt | 69 ++++++++++++++++++ .../actions/fixtures/EnvActionFixture.kt | 5 ++ .../reportings/fixtures/ReportingFixture.kt | 22 +++++- .../reporting/ReportingValidatorUTest.kt | 72 +++++++++++++++++++ 5 files changed, 171 insertions(+), 4 deletions(-) create mode 100644 backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/reporting/ReportingValidator.kt create mode 100644 backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/reporting/ReportingValidatorUTest.kt diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/reportings/CreateOrUpdateReporting.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/reportings/CreateOrUpdateReporting.kt index 59ba001ff..f860fb35f 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/reportings/CreateOrUpdateReporting.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/reportings/CreateOrUpdateReporting.kt @@ -6,6 +6,8 @@ import fr.gouv.cacem.monitorenv.domain.exceptions.ReportingAlreadyAttachedExcept import fr.gouv.cacem.monitorenv.domain.repositories.* import fr.gouv.cacem.monitorenv.domain.use_cases.reportings.dtos.ReportingDetailsDTO import fr.gouv.cacem.monitorenv.domain.use_cases.reportings.events.UpdateReportingEvent +import fr.gouv.cacem.monitorenv.domain.validators.UseCaseValidation +import fr.gouv.cacem.monitorenv.domain.validators.reporting.ReportingValidator import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.context.ApplicationEventPublisher @@ -20,7 +22,10 @@ class CreateOrUpdateReporting( private val logger: Logger = LoggerFactory.getLogger(CreateOrUpdateReporting::class.java) @Throws(IllegalArgumentException::class) - fun execute(reporting: ReportingEntity): ReportingDetailsDTO { + fun execute( + @UseCaseValidation(validator = ReportingValidator::class) + reporting: ReportingEntity, + ): ReportingDetailsDTO { logger.info("Attempt to CREATE or UPDATE reporting ${reporting.id}") reporting.validate() diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/reporting/ReportingValidator.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/reporting/ReportingValidator.kt new file mode 100644 index 000000000..507510f3d --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/reporting/ReportingValidator.kt @@ -0,0 +1,69 @@ +package fr.gouv.cacem.monitorenv.domain.validators.reporting + +import fr.gouv.cacem.monitorenv.domain.entities.reporting.ReportingEntity +import fr.gouv.cacem.monitorenv.domain.entities.reporting.SourceTypeEnum +import fr.gouv.cacem.monitorenv.domain.exceptions.BackendUsageErrorCode +import fr.gouv.cacem.monitorenv.domain.exceptions.BackendUsageException +import fr.gouv.cacem.monitorenv.domain.validators.Validator +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Component + +private const val NB_CHAR_MAX = 3 + +@Component +class ReportingValidator : Validator { + private val logger = LoggerFactory.getLogger(ReportingValidator::class.java) + + override fun validate(reporting: ReportingEntity) { + logger.info("Validating reporting: ${reporting.id}") + + if (reporting.openBy !== null && reporting.openBy.length != NB_CHAR_MAX) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "Le trigramme \"ouvert par\" doit avoir 3 lettres", + ) + } + if (reporting.reportingSources.isEmpty()) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "Une source du signalement est obligatoire", + ) + } + if (reporting.validityTime == 0) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "La validité du signalement doit être supérieur à 0", + ) + } + reporting.reportingSources.forEach { source -> + when (source.sourceType) { + SourceTypeEnum.SEMAPHORE -> { + if (source.semaphoreId === null || source.controlUnitId !== null || source.sourceName !== null) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "La source du signalement est invalide", + ) + } + } + + SourceTypeEnum.CONTROL_UNIT -> { + if (source.semaphoreId !== null || source.controlUnitId === null || source.sourceName !== null) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "La source du signalement est invalide", + ) + } + } + + SourceTypeEnum.OTHER -> { + if (source.semaphoreId !== null || source.controlUnitId !== null || source.sourceName === null) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "La source du signalement est invalide", + ) + } + } + } + } + } +} diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/actions/fixtures/EnvActionFixture.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/actions/fixtures/EnvActionFixture.kt index dc43369ed..08e43bb31 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/actions/fixtures/EnvActionFixture.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/actions/fixtures/EnvActionFixture.kt @@ -63,6 +63,8 @@ class EnvActionFixture { actionNumberOfControls: Int? = infractions.size, actionTargetTypeEnum: ActionTargetTypeEnum? = null, vehicleTypeEnum: VehicleTypeEnum? = null, + controlPlans: List? = + listOf(EnvActionControlPlanEntity(subThemeIds = listOf(1))), ): EnvActionControlEntity { return EnvActionControlEntity( id = UUID.randomUUID(), @@ -73,6 +75,7 @@ class EnvActionFixture { actionNumberOfControls = actionNumberOfControls, actionTargetType = actionTargetTypeEnum, vehicleType = vehicleTypeEnum, + controlPlans = controlPlans, ) } @@ -80,6 +83,7 @@ class EnvActionFixture { startTime: ZonedDateTime? = null, endTime: ZonedDateTime? = null, openBy: String? = "CDA", + controlPlans: List? = listOf(), ): EnvActionSurveillanceEntity { return EnvActionSurveillanceEntity( id = UUID.randomUUID(), @@ -87,6 +91,7 @@ class EnvActionFixture { actionEndDateTimeUtc = endTime, openBy = openBy, awareness = null, + controlPlans = controlPlans, ) } diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/reportings/fixtures/ReportingFixture.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/reportings/fixtures/ReportingFixture.kt index 9cc89c22a..30d15408f 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/reportings/fixtures/ReportingFixture.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/reportings/fixtures/ReportingFixture.kt @@ -14,10 +14,12 @@ class ReportingFixture { companion object { fun aReporting( id: Int? = null, - reportingSources: List = listOf(), + reportingSources: List = listOf(aReportingSourceSemaphore()), missionId: Int? = null, attachedToMissionAtUtc: ZonedDateTime? = null, attachedEnvActionId: UUID? = null, + validityTime: Int = 10, + openBy: String = "CDA", ): ReportingEntity { val wktReader = WKTReader() @@ -40,11 +42,11 @@ class ReportingFixture { isControlRequired = true, hasNoUnitAvailable = true, createdAt = ZonedDateTime.parse("2022-01-15T04:50:09Z"), - validityTime = 10, + validityTime = validityTime, isArchived = false, isDeleted = false, updatedAtUtc = ZonedDateTime.now(), - openBy = "CDA", + openBy = openBy, isInfractionProven = true, missionId = missionId, attachedToMissionAtUtc = attachedToMissionAtUtc, @@ -115,5 +117,19 @@ class ReportingFixture { sourceName = null, ) } + + fun aReportingSourceOther( + reportingId: Int? = null, + sourceName: String = "test", + ): ReportingSourceEntity { + return ReportingSourceEntity( + id = null, + reportingId = reportingId, + sourceType = SourceTypeEnum.OTHER, + semaphoreId = null, + controlUnitId = null, + sourceName = sourceName, + ) + } } } diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/reporting/ReportingValidatorUTest.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/reporting/ReportingValidatorUTest.kt new file mode 100644 index 000000000..fe1bd85a9 --- /dev/null +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/reporting/ReportingValidatorUTest.kt @@ -0,0 +1,72 @@ +package fr.gouv.cacem.monitorenv.domain.validators.reporting + +import fr.gouv.cacem.monitorenv.domain.exceptions.BackendUsageException +import fr.gouv.cacem.monitorenv.domain.use_cases.reportings.fixtures.ReportingFixture.Companion.aReporting +import fr.gouv.cacem.monitorenv.domain.use_cases.reportings.fixtures.ReportingFixture.Companion.aReportingSourceControlUnit +import fr.gouv.cacem.monitorenv.domain.use_cases.reportings.fixtures.ReportingFixture.Companion.aReportingSourceOther +import fr.gouv.cacem.monitorenv.domain.use_cases.reportings.fixtures.ReportingFixture.Companion.aReportingSourceSemaphore +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Assertions.assertThrows +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource + +class ReportingValidatorUTest { + private val reportingValidator = ReportingValidator() + + @ParameterizedTest + @ValueSource(strings = ["A", "AA", "AAAA"]) + fun `validate should throw an exception if there is a control with openBy is not a trigram`(openBy: String) { + val reporting = aReporting(openBy = openBy) + + val assertThrows = assertThrows(BackendUsageException::class.java) { reportingValidator.validate(reporting) } + assertThat(assertThrows.message).isEqualTo("Le trigramme \"ouvert par\" doit avoir 3 lettres") + } + + @Test + fun `validate should throw an exception if reportingSource is empty`() { + val reporting = aReporting(reportingSources = listOf()) + + val assertThrows = assertThrows(BackendUsageException::class.java) { reportingValidator.validate(reporting) } + assertThat(assertThrows.message).isEqualTo("Une source du signalement est obligatoire") + } + + @Test + fun `validate should throw an exception if reportingSource from control unit is invalid`() { + val reporting = aReporting(reportingSources = listOf(aReportingSourceControlUnit().copy(semaphoreId = 1))) + + val assertThrows = assertThrows(BackendUsageException::class.java) { reportingValidator.validate(reporting) } + assertThat(assertThrows.message).isEqualTo("La source du signalement est invalide") + } + + @Test + fun `validate should throw an exception if reportingSource from semaphore is invalid`() { + val reporting = aReporting(reportingSources = listOf(aReportingSourceSemaphore().copy(sourceName = "test"))) + + val assertThrows = assertThrows(BackendUsageException::class.java) { reportingValidator.validate(reporting) } + assertThat(assertThrows.message).isEqualTo("La source du signalement est invalide") + } + + @Test + fun `validate should throw an exception if reportingSource from other is invalid`() { + val reporting = aReporting(reportingSources = listOf(aReportingSourceOther().copy(controlUnitId = 1))) + + val assertThrows = assertThrows(BackendUsageException::class.java) { reportingValidator.validate(reporting) } + assertThat(assertThrows.message).isEqualTo("La source du signalement est invalide") + } + + @Test + fun `validate should throw an exception if validityTime is less than 1`() { + val reporting = aReporting(validityTime = 0) + + val assertThrows = assertThrows(BackendUsageException::class.java) { reportingValidator.validate(reporting) } + assertThat(assertThrows.message).isEqualTo("La validité du signalement doit être supérieur à 0") + } + + @Test + fun `validate should pass for a valid ReportingEntity`() { + val reporting = aReporting() + + reportingValidator.validate(reporting) + } +} From f13091650e65b0253de8814e0ef6bc7581582e9f Mon Sep 17 00:00:00 2001 From: Maxime Perrault Date: Mon, 20 Jan 2025 18:49:39 +0100 Subject: [PATCH 08/14] tech: fix isMissionEnded --- .../domain/validators/mission/MissionValidator.kt | 2 +- .../use_cases/actions/fixtures/EnvActionFixture.kt | 3 ++- .../validators/mission/MissionValidatorUTest.kt | 12 ++++++------ 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidator.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidator.kt index 2d684311c..ebd26fdbe 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidator.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidator.kt @@ -58,7 +58,7 @@ class MissionValidator : Validator { private fun validateEnvActions(mission: MissionEntity) { val isMissionEnded = - mission.endDateTimeUtc != null && (mission.endDateTimeUtc?.isAfter(ZonedDateTime.now()) == true) + mission.endDateTimeUtc != null && ZonedDateTime.now().isAfter(mission.endDateTimeUtc) mission.envActions?.forEach { envAction -> if (envAction is EnvActionControlEntity) { diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/actions/fixtures/EnvActionFixture.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/actions/fixtures/EnvActionFixture.kt index 08e43bb31..e6bd30145 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/actions/fixtures/EnvActionFixture.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/actions/fixtures/EnvActionFixture.kt @@ -83,7 +83,8 @@ class EnvActionFixture { startTime: ZonedDateTime? = null, endTime: ZonedDateTime? = null, openBy: String? = "CDA", - controlPlans: List? = listOf(), + controlPlans: List? = + listOf(EnvActionControlPlanEntity(subThemeIds = listOf(1))), ): EnvActionSurveillanceEntity { return EnvActionSurveillanceEntity( id = UUID.randomUUID(), diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidatorUTest.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidatorUTest.kt index f684257c3..6e52b57b3 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidatorUTest.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidatorUTest.kt @@ -217,7 +217,7 @@ class MissionValidatorUTest { @Test fun `validate should throw an exception if there is a control without control plans when mission has ended`() { - val endDateTimeUtc = ZonedDateTime.now().plusSeconds(1) + val endDateTimeUtc = ZonedDateTime.now().minusSeconds(1) val anEnvActionControl = anEnvActionControl(controlPlans = listOf()) val mission = aMissionEntity(endDateTimeUtc = endDateTimeUtc, envActions = listOf(anEnvActionControl)) @@ -227,7 +227,7 @@ class MissionValidatorUTest { @Test fun `validate should throw an exception if there is a control with control plan without subtheme when mission has ended`() { - val endDateTimeUtc = ZonedDateTime.now().plusSeconds(1) + val endDateTimeUtc = ZonedDateTime.now().minusSeconds(1) val anEnvActionControl = anEnvActionControl(controlPlans = listOf(EnvActionControlPlanEntity(subThemeIds = listOf()))) val mission = aMissionEntity(endDateTimeUtc = endDateTimeUtc, envActions = listOf(anEnvActionControl)) @@ -238,7 +238,7 @@ class MissionValidatorUTest { @Test fun `validate should throw an exception if there is a control actionTargetType as VEHICULE without vehiculeType when mission has ended`() { - val endDateTimeUtc = ZonedDateTime.now().plusSeconds(1) + val endDateTimeUtc = ZonedDateTime.now().minusSeconds(1) val anEnvActionControl = anEnvActionControl(actionTargetTypeEnum = ActionTargetTypeEnum.VEHICLE) val mission = aMissionEntity(endDateTimeUtc = endDateTimeUtc, envActions = listOf(anEnvActionControl)) @@ -251,7 +251,7 @@ class MissionValidatorUTest { fun `validate should pass if there is a control actionTargetType as targetType other than VEHICLE without vehiculeType when mission has ended`( targetType: ActionTargetTypeEnum, ) { - val endDateTimeUtc = ZonedDateTime.now().plusSeconds(1) + val endDateTimeUtc = ZonedDateTime.now().minusSeconds(1) val anEnvActionControl = anEnvActionControl(actionTargetTypeEnum = targetType) val mission = aMissionEntity(endDateTimeUtc = endDateTimeUtc, envActions = listOf(anEnvActionControl)) @@ -342,7 +342,7 @@ class MissionValidatorUTest { @Test fun `validate should throw an exception if there is a surveillance without control plans when mission has ended`() { - val endDateTimeUtc = ZonedDateTime.now().plusSeconds(1) + val endDateTimeUtc = ZonedDateTime.now().minusSeconds(1) val anEnvActionSurveillance = anEnvActionSurveillance(controlPlans = listOf()) val mission = aMissionEntity(endDateTimeUtc = endDateTimeUtc, envActions = listOf(anEnvActionSurveillance)) @@ -352,7 +352,7 @@ class MissionValidatorUTest { @Test fun `validate should throw an exception if there is a surveillance with control plan without subtheme when mission has ended`() { - val endDateTimeUtc = ZonedDateTime.now().plusSeconds(1) + val endDateTimeUtc = ZonedDateTime.now().minusSeconds(1) val anEnvActionSurveillance = anEnvActionSurveillance(controlPlans = listOf(EnvActionControlPlanEntity(subThemeIds = listOf()))) val mission = aMissionEntity(endDateTimeUtc = endDateTimeUtc, envActions = listOf(anEnvActionSurveillance)) From 7767efabc1fa5d56a85ed8be015db0de0f05f913 Mon Sep 17 00:00:00 2001 From: Maxime Perrault Date: Tue, 21 Jan 2025 17:08:27 +0100 Subject: [PATCH 09/14] tech: - Mission : add some infraction validation case - reporting : review input, add description validation - Vigilance area : review input, add geom and comment validation --- .../vigilanceArea/VigilanceAreaEntity.kt | 2 +- .../validators/mission/MissionValidator.kt | 72 ++++++++--- .../reporting/ReportingValidator.kt | 8 ++ .../vigilance_area/VigilanceAreaValidator.kt | 12 ++ .../CreateOrUpdateReportingDataInput.kt | 6 +- .../vigilanceArea/VigilanceAreaDataInput.kt | 2 +- .../endpoints/ControllersExceptionHandler.kt | 2 +- .../database/model/VigilanceAreaModel.kt | 2 +- .../actions/fixtures/EnvActionFixture.kt | 27 +++- .../missions/fixtures/MissionFixture.kt | 2 +- .../reportings/fixtures/ReportingFixture.kt | 12 +- .../CreateOrUpdateVigilanceAreaUTests.kt | 1 + .../fixtures/VigilanceAreaFixture.kt | 14 ++- .../mission/MissionValidatorUTest.kt | 117 +++++++++++------- .../reporting/ReportingValidatorUTest.kt | 9 ++ .../VigilanceAreaValidatorUTest.kt | 18 +++ frontend/src/api/types.ts | 12 +- .../components/ReportingForm/Schema/index.tsx | 6 +- 18 files changed, 239 insertions(+), 85 deletions(-) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/entities/vigilanceArea/VigilanceAreaEntity.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/entities/vigilanceArea/VigilanceAreaEntity.kt index b37913e9e..7a639a22d 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/entities/vigilanceArea/VigilanceAreaEntity.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/entities/vigilanceArea/VigilanceAreaEntity.kt @@ -22,7 +22,7 @@ data class VigilanceAreaEntity( val links: List? = null, val linkedAMPs: List? = listOf(), val linkedRegulatoryAreas: List? = listOf(), - val name: String? = null, + val name: String, val seaFront: String? = null, val source: String? = null, val startDatePeriod: ZonedDateTime? = null, diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidator.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidator.kt index ebd26fdbe..0d07140a1 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidator.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidator.kt @@ -5,7 +5,10 @@ import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.ActionTypeEnum import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.EnvActionEntity import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionControl.ActionTargetTypeEnum import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionControl.EnvActionControlEntity +import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionControl.infraction.AdministrativeResponseEnum +import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionControl.infraction.FormalNoticeEnum import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionControl.infraction.InfractionTypeEnum +import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionControl.infraction.SeizureTypeEnum import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionSurveillance.EnvActionSurveillanceEntity import fr.gouv.cacem.monitorenv.domain.exceptions.BackendUsageErrorCode import fr.gouv.cacem.monitorenv.domain.exceptions.BackendUsageException @@ -79,6 +82,12 @@ class MissionValidator : Validator { validateEnvAction(control, mission) if (isMissionEnded) { + if (control.geom === null) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "La géométrie du contrôle est obligatoire", + ) + } if (control.vehicleType === null && control.actionTargetType === ActionTargetTypeEnum.VEHICLE) { throw BackendUsageException( BackendUsageErrorCode.UNVALID_PROPERTY, @@ -109,6 +118,32 @@ class MissionValidator : Validator { "le nombre minimum de cible est 1", ) } + if (isMissionEnded) { + if (infraction.infractionType === InfractionTypeEnum.WAITING) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "Le type d'infraction ne peut pas être \"en attente\"", + ) + } + if (infraction.seizure === SeizureTypeEnum.PENDING) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "L'appréhension/saisie ne peut pas être \"en attente\"", + ) + } + if (infraction.administrativeResponse === AdministrativeResponseEnum.PENDING) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "La réponse administrative ne peut pas être \"en attente\"", + ) + } + if (infraction.formalNotice === FormalNoticeEnum.PENDING) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "La mise en demeure ne peut pas être \"en attente\"", + ) + } + } } } @@ -119,12 +154,31 @@ class MissionValidator : Validator { ) { validateEnvAction(surveillance, mission) + if (surveillance.geom === null) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "La géométrie de la surveillance est obligatoire", + ) + } if (surveillance.completedBy !== null && surveillance.completedBy.length != NB_CHAR_MAX) { throw BackendUsageException( BackendUsageErrorCode.UNVALID_PROPERTY, "Le trigramme \"complété par\" doit avoir 3 lettres", ) } + if (surveillance.actionEndDateTimeUtc?.isAfter(mission.endDateTimeUtc) == true + ) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "La date de fin de la surveillance doit être antérieure à celle de fin de mission", + ) + } + if (surveillance.actionEndDateTimeUtc?.isBefore(mission.startDateTimeUtc) == true) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "La date de fin de la surveillance doit être postérieure à celle du début de mission", + ) + } if (isMissionEnded) { validateControlPlan(surveillance) } @@ -135,9 +189,7 @@ class MissionValidator : Validator { mission: MissionEntity, ) { val actionType = if (envAction.actionType === ActionTypeEnum.CONTROL) "du contrôle" else "de la surveillance" - if (envAction.actionStartDateTimeUtc?.isAfter(mission.startDateTimeUtc) == false && - envAction.actionStartDateTimeUtc?.isEqual(mission.startDateTimeUtc) == false - ) { + if (envAction.actionStartDateTimeUtc?.isBefore(mission.startDateTimeUtc) == true) { throw BackendUsageException( BackendUsageErrorCode.UNVALID_PROPERTY, "La date de début $actionType doit être postérieure à celle du début de mission", @@ -149,20 +201,6 @@ class MissionValidator : Validator { "La date de début $actionType doit être antérieure à celle de fin de mission", ) } - if (envAction.actionEndDateTimeUtc?.isBefore(mission.endDateTimeUtc) == false && - envAction.actionEndDateTimeUtc?.isEqual(mission.endDateTimeUtc) == false - ) { - throw BackendUsageException( - BackendUsageErrorCode.UNVALID_PROPERTY, - "La date de fin $actionType doit être antérieure à celle de fin de mission", - ) - } - if (envAction.actionEndDateTimeUtc?.isBefore(mission.startDateTimeUtc) == true) { - throw BackendUsageException( - BackendUsageErrorCode.UNVALID_PROPERTY, - "La date de fin $actionType doit être postérieure à celle du début de mission", - ) - } if (envAction.openBy?.length != NB_CHAR_MAX) { throw BackendUsageException( BackendUsageErrorCode.UNVALID_PROPERTY, diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/reporting/ReportingValidator.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/reporting/ReportingValidator.kt index 507510f3d..b41316a4f 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/reporting/ReportingValidator.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/reporting/ReportingValidator.kt @@ -2,6 +2,7 @@ package fr.gouv.cacem.monitorenv.domain.validators.reporting import fr.gouv.cacem.monitorenv.domain.entities.reporting.ReportingEntity import fr.gouv.cacem.monitorenv.domain.entities.reporting.SourceTypeEnum +import fr.gouv.cacem.monitorenv.domain.entities.reporting.TargetTypeEnum import fr.gouv.cacem.monitorenv.domain.exceptions.BackendUsageErrorCode import fr.gouv.cacem.monitorenv.domain.exceptions.BackendUsageException import fr.gouv.cacem.monitorenv.domain.validators.Validator @@ -65,5 +66,12 @@ class ReportingValidator : Validator { } } } + + if (reporting.targetType === TargetTypeEnum.OTHER && reporting.description === null) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "La description de la cible est obligatoire", + ) + } } } diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/vigilance_area/VigilanceAreaValidator.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/vigilance_area/VigilanceAreaValidator.kt index 7a38f9650..619806c60 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/vigilance_area/VigilanceAreaValidator.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/vigilance_area/VigilanceAreaValidator.kt @@ -19,6 +19,18 @@ class VigilanceAreaValidator : Validator { logger.info("Validating vigilance area: ${vigilanceArea.id}") if (!vigilanceArea.isDraft) { + if (vigilanceArea.geom === null) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "La géométrie est obligatoire", + ) + } + if (vigilanceArea.comments === null) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "Un commentaire est obligatoire", + ) + } if (vigilanceArea.createdBy !== null && vigilanceArea.createdBy.length != NB_CHAR_MAX) { throw BackendUsageException( BackendUsageErrorCode.UNVALID_PROPERTY, diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/inputs/reportings/CreateOrUpdateReportingDataInput.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/inputs/reportings/CreateOrUpdateReportingDataInput.kt index 7e5ef0e77..0caa04787 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/inputs/reportings/CreateOrUpdateReportingDataInput.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/inputs/reportings/CreateOrUpdateReportingDataInput.kt @@ -18,9 +18,9 @@ data class CreateOrUpdateReportingDataInput( val targetDetails: List? = listOf(), val geom: Geometry? = null, val description: String? = null, - val reportType: ReportingTypeEnum? = null, - val themeId: Int? = null, - val subThemeIds: List? = emptyList(), + val reportType: ReportingTypeEnum, + val themeId: Int, + val subThemeIds: List, val actionTaken: String? = null, val isControlRequired: Boolean? = null, val hasNoUnitAvailable: Boolean? = null, diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/inputs/vigilanceArea/VigilanceAreaDataInput.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/inputs/vigilanceArea/VigilanceAreaDataInput.kt index 68fc35057..a68f20f2b 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/inputs/vigilanceArea/VigilanceAreaDataInput.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/inputs/vigilanceArea/VigilanceAreaDataInput.kt @@ -28,7 +28,7 @@ data class VigilanceAreaDataInput( val links: List? = null, val linkedAMPs: List? = listOf(), val linkedRegulatoryAreas: List? = listOf(), - val name: String? = null, + val name: String, val seaFront: String?, val source: String? = null, val startDatePeriod: ZonedDateTime? = null, diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/ControllersExceptionHandler.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/ControllersExceptionHandler.kt index 38ddf295d..da5189d48 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/ControllersExceptionHandler.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/ControllersExceptionHandler.kt @@ -50,7 +50,7 @@ class ControllersExceptionHandler(val sentryConfig: SentryConfig) { @ExceptionHandler(BackendUsageException::class) fun handleBackendUsageException(e: BackendUsageException): BackendUsageErrorDataOutput { logger.error(e.message) - return BackendUsageErrorDataOutput(code = e.code, data = e.data, message = null) + return BackendUsageErrorDataOutput(code = e.code, data = e.data, message = e.message) } @ResponseStatus(HttpStatus.BAD_REQUEST) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/model/VigilanceAreaModel.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/model/VigilanceAreaModel.kt index 34170cd4a..b816724d1 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/model/VigilanceAreaModel.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/model/VigilanceAreaModel.kt @@ -89,7 +89,7 @@ data class VigilanceAreaModel( val linkedAMPs: List? = listOf(), @Column(name = "linked_regulatory_areas", columnDefinition = "int[]") val linkedRegulatoryAreas: List? = listOf(), - @Column(name = "name") val name: String? = null, + @Column(name = "name", nullable = false) val name: String, @Column(name = "start_date_period") val startDatePeriod: Instant? = null, @Column(name = "sea_front") val seaFront: String? = null, @Column(name = "source") val source: String? = null, diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/actions/fixtures/EnvActionFixture.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/actions/fixtures/EnvActionFixture.kt index e6bd30145..35e0f0f46 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/actions/fixtures/EnvActionFixture.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/actions/fixtures/EnvActionFixture.kt @@ -17,10 +17,18 @@ import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionContr import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionSurveillance.EnvActionSurveillanceEntity import fr.gouv.cacem.monitorenv.domain.entities.mission.monitorfish.MonitorFishMissionActionEntity import fr.gouv.cacem.monitorenv.domain.mappers.EnvActionMapper +import org.locationtech.jts.geom.Geometry +import org.locationtech.jts.io.WKTReader import java.time.ZonedDateTime import java.util.UUID import kotlin.random.Random +private val polygon = + WKTReader() + .read( + "MULTIPOLYGON (((-61.0 14.0, -61.0 15.0, -60.0 15.0, -60.0 14.0, -61.0 14.0)))", + ) + class EnvActionFixture { companion object { fun anEnvAction( @@ -57,7 +65,6 @@ class EnvActionFixture { fun anEnvActionControl( startTime: ZonedDateTime? = null, - endTime: ZonedDateTime? = null, openBy: String = "MPE", infractions: List = listOf(), actionNumberOfControls: Int? = infractions.size, @@ -65,17 +72,18 @@ class EnvActionFixture { vehicleTypeEnum: VehicleTypeEnum? = null, controlPlans: List? = listOf(EnvActionControlPlanEntity(subThemeIds = listOf(1))), + geom: Geometry? = polygon, ): EnvActionControlEntity { return EnvActionControlEntity( id = UUID.randomUUID(), actionStartDateTimeUtc = startTime, - actionEndDateTimeUtc = endTime, openBy = openBy, infractions = infractions, actionNumberOfControls = actionNumberOfControls, actionTargetType = actionTargetTypeEnum, vehicleType = vehicleTypeEnum, controlPlans = controlPlans, + geom = geom, ) } @@ -85,6 +93,7 @@ class EnvActionFixture { openBy: String? = "CDA", controlPlans: List? = listOf(EnvActionControlPlanEntity(subThemeIds = listOf(1))), + geom: Geometry? = polygon, ): EnvActionSurveillanceEntity { return EnvActionSurveillanceEntity( id = UUID.randomUUID(), @@ -93,19 +102,25 @@ class EnvActionFixture { openBy = openBy, awareness = null, controlPlans = controlPlans, + geom = geom, ) } fun anInfraction( - infractionType: InfractionTypeEnum = InfractionTypeEnum.WAITING, + infractionType: InfractionTypeEnum = InfractionTypeEnum.WITHOUT_REPORT, + administrativeResponse: AdministrativeResponseEnum = AdministrativeResponseEnum.NONE, + seizure: SeizureTypeEnum = SeizureTypeEnum.NO, + formalNotice: FormalNoticeEnum = FormalNoticeEnum.NO, nbTarget: Int = 1, + natinf: List = listOf("1234"), ) = InfractionEntity( id = Random.nextInt().toString(), - administrativeResponse = AdministrativeResponseEnum.NONE, + administrativeResponse = administrativeResponse, infractionType = infractionType, - formalNotice = FormalNoticeEnum.NO, - seizure = SeizureTypeEnum.NO, + formalNotice = formalNotice, + seizure = seizure, nbTarget = nbTarget, + natinf = natinf, ) fun aMonitorFishAction(missionId: Int): MonitorFishMissionActionEntity { diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/fixtures/MissionFixture.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/fixtures/MissionFixture.kt index 38f8ca1d0..5613c499c 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/fixtures/MissionFixture.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/missions/fixtures/MissionFixture.kt @@ -15,7 +15,7 @@ class MissionFixture { fun aMissionEntity( id: Int? = Random.nextInt(), startDateTimeUtc: ZonedDateTime = ZonedDateTime.parse("2022-01-15T04:50:09Z"), - endDateTimeUtc: ZonedDateTime? = ZonedDateTime.parse("2022-01-23T20:29:03Z"), + endDateTimeUtc: ZonedDateTime? = ZonedDateTime.now().plusDays(1), controlUnits: List = listOf(aLegacyControlUnit()), observationsByUnit: String? = null, missionTypes: List = listOf(MissionTypeEnum.LAND), diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/reportings/fixtures/ReportingFixture.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/reportings/fixtures/ReportingFixture.kt index 30d15408f..5449f3ff3 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/reportings/fixtures/ReportingFixture.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/reportings/fixtures/ReportingFixture.kt @@ -20,6 +20,10 @@ class ReportingFixture { attachedEnvActionId: UUID? = null, validityTime: Int = 10, openBy: String = "CDA", + targetType: TargetTypeEnum? = TargetTypeEnum.VEHICLE, + description: String? = "description", + themeId: Int? = 1, + subThemeIds: List? = listOf(10, 11), ): ReportingEntity { val wktReader = WKTReader() @@ -29,15 +33,15 @@ class ReportingFixture { return ReportingEntity( id = id, - targetType = TargetTypeEnum.VEHICLE, + targetType = targetType, reportingSources = reportingSources, vehicleType = VehicleTypeEnum.VESSEL, geom = polygon, seaFront = "Facade 1", - description = "description", + description = description, reportType = ReportingTypeEnum.INFRACTION_SUSPICION, - themeId = 1, - subThemeIds = listOf(10, 11), + themeId = themeId, + subThemeIds = subThemeIds, actionTaken = "actions effectuées ", isControlRequired = true, hasNoUnitAvailable = true, diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/vigilanceArea/CreateOrUpdateVigilanceAreaUTests.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/vigilanceArea/CreateOrUpdateVigilanceAreaUTests.kt index 5809b8048..2829114bd 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/vigilanceArea/CreateOrUpdateVigilanceAreaUTests.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/vigilanceArea/CreateOrUpdateVigilanceAreaUTests.kt @@ -47,6 +47,7 @@ class CreateOrUpdateVigilanceAreaUTests { createdAt = null, updatedAt = null, isAtAllTimes = false, + name = "test_name", ) val expectedVigilanceArea = newVigilanceArea.copy(id = 0) diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/vigilanceArea/fixtures/VigilanceAreaFixture.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/vigilanceArea/fixtures/VigilanceAreaFixture.kt index 3e959fef9..e3e370738 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/vigilanceArea/fixtures/VigilanceAreaFixture.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/vigilanceArea/fixtures/VigilanceAreaFixture.kt @@ -6,8 +6,16 @@ import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.ImageEntity import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.LinkEntity import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.VigilanceAreaEntity import fr.gouv.cacem.monitorenv.domain.entities.vigilanceArea.VisibilityEnum +import org.locationtech.jts.geom.MultiPolygon +import org.locationtech.jts.io.WKTReader import java.time.ZonedDateTime +private val polygon = + WKTReader() + .read( + "MULTIPOLYGON (((-61.0 14.0, -61.0 15.0, -60.0 15.0, -60.0 14.0, -61.0 14.0)))", + ) as MultiPolygon + class VigilanceAreaFixture { companion object { fun aVigilanceAreaEntity( @@ -21,10 +29,12 @@ class VigilanceAreaFixture { endCondition: EndingConditionEnum = EndingConditionEnum.OCCURENCES_NUMBER, endingOccurrencesNumber: Int? = 2, isAtAllTimes: Boolean = false, + geom: MultiPolygon? = polygon, + comments: String? = "Basic area comments", ): VigilanceAreaEntity { return VigilanceAreaEntity( id = 1, - comments = "Basic area comments", + comments = comments, computedEndDate = ZonedDateTime.parse("2024-01-25T00:00:00Z"), createdBy = createdBy, endDatePeriod = endDate, @@ -33,7 +43,7 @@ class VigilanceAreaFixture { endingOccurrencesNumber = endingOccurrencesNumber, images = null, frequency = frequency, - geom = null, + geom = geom, isArchived = false, isDeleted = false, isDraft = isDraft, diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidatorUTest.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidatorUTest.kt index 6e52b57b3..c58db389c 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidatorUTest.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidatorUTest.kt @@ -2,7 +2,10 @@ package fr.gouv.cacem.monitorenv.domain.validators.mission import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.EnvActionControlPlanEntity import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionControl.ActionTargetTypeEnum +import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionControl.infraction.AdministrativeResponseEnum +import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionControl.infraction.FormalNoticeEnum import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionControl.infraction.InfractionTypeEnum +import fr.gouv.cacem.monitorenv.domain.entities.mission.envAction.envActionControl.infraction.SeizureTypeEnum import fr.gouv.cacem.monitorenv.domain.exceptions.BackendUsageException import fr.gouv.cacem.monitorenv.domain.use_cases.actions.fixtures.EnvActionFixture.Companion.anEnvActionControl import fr.gouv.cacem.monitorenv.domain.use_cases.actions.fixtures.EnvActionFixture.Companion.anEnvActionSurveillance @@ -79,10 +82,10 @@ class MissionValidatorUTest { } @Test - fun `validate should throw an exception if there is a control with a start date after mission ending date`() { - val startDateTimeUtc = ZonedDateTime.parse("2019-03-04T00:00:00.000Z") - val endDateTimeUtc = ZonedDateTime.parse("2020-03-04T00:00:00.000Z") - val anEnvActionControl = anEnvActionControl(endTime = endDateTimeUtc.plusSeconds(1)) + fun `validate should pass if there is a control with the same start date as mission's`() { + val startDateTimeUtc = ZonedDateTime.parse("2020-03-04T00:00:00.000Z") + val endDateTimeUtc = ZonedDateTime.parse("2021-03-04T00:00:00.000Z") + val anEnvActionControl = anEnvActionControl(startTime = startDateTimeUtc) val mission = aMissionEntity( startDateTimeUtc = startDateTimeUtc, @@ -90,76 +93,90 @@ class MissionValidatorUTest { envActions = listOf(anEnvActionControl), ) - val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } - assertThat( - assertThrows.message, - ).isEqualTo("La date de fin du contrôle doit être antérieure à celle de fin de mission") + missionValidator.validate(mission) } @Test - fun `validate should throw an exception if there is a control with an end date after mission ending date`() { - val startDateTimeUtc = ZonedDateTime.parse("2019-03-04T00:00:00.000Z") - val endDateTimeUtc = ZonedDateTime.parse("2020-03-04T00:00:00.000Z") - val anEnvActionControl = anEnvActionControl(endTime = endDateTimeUtc.plusSeconds(1)) + fun `validate should pass if there is a control without geometry when mission has ended`() { + val endDateTimeUtc = ZonedDateTime.now().minusSeconds(1) + val anEnvActionControl = anEnvActionControl(geom = null) val mission = aMissionEntity( - startDateTimeUtc = startDateTimeUtc, endDateTimeUtc = endDateTimeUtc, envActions = listOf(anEnvActionControl), ) + val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } + assertThat(assertThrows.message).isEqualTo("La géométrie du contrôle est obligatoire") + } + + @ParameterizedTest + @EnumSource(value = InfractionTypeEnum::class, names = ["WAITING"], mode = EnumSource.Mode.EXCLUDE) + fun `validate should throw an exception if there is a control with infractionType other than WAITING that doesnt have a NATINF`( + infractionType: InfractionTypeEnum, + ) { + val anEnvActionControl = + anEnvActionControl(infractions = listOf(anInfraction(infractionType = infractionType, natinf = listOf()))) + val mission = aMissionEntity(envActions = listOf(anEnvActionControl)) + val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } assertThat( assertThrows.message, - ).isEqualTo("La date de fin du contrôle doit être antérieure à celle de fin de mission") + ).isEqualTo("Une infraction doit avoir une natinf si le type d'infraction n'est pas \"En attente\"") } @Test - fun `validate should throw an exception if there is a control with an end date before mission starting date`() { - val startDateTimeUtc = ZonedDateTime.parse("2020-03-04T00:00:00.000Z") - val endDateTimeUtc = ZonedDateTime.parse("2021-03-04T00:00:00.000Z") + fun `validate should throw an exception if there is a control with infractionType is WAITING when mission has ended`() { + val endDateTimeUtc = ZonedDateTime.now().minusSeconds(1) + val anEnvActionControl = + anEnvActionControl(infractions = listOf(anInfraction(infractionType = InfractionTypeEnum.WAITING))) + val mission = aMissionEntity(endDateTimeUtc = endDateTimeUtc, envActions = listOf(anEnvActionControl)) - val anEnvActionControl = anEnvActionControl(endTime = startDateTimeUtc.minusSeconds(1)) - val mission = - aMissionEntity( - startDateTimeUtc = startDateTimeUtc, - endDateTimeUtc = endDateTimeUtc, - envActions = listOf(anEnvActionControl), + val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } + assertThat( + assertThrows.message, + ).isEqualTo("Le type d'infraction ne peut pas être \"en attente\"") + } + + @Test + fun `validate should throw an exception if there is a control with administrativeResponse is WAITING when mission has ended`() { + val endDateTimeUtc = ZonedDateTime.now().minusSeconds(1) + val anEnvActionControl = + anEnvActionControl( + infractions = listOf(anInfraction(administrativeResponse = AdministrativeResponseEnum.PENDING)), ) + val mission = aMissionEntity(endDateTimeUtc = endDateTimeUtc, envActions = listOf(anEnvActionControl)) val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } assertThat( assertThrows.message, - ).isEqualTo("La date de fin du contrôle doit être postérieure à celle du début de mission") + ).isEqualTo("La réponse administrative ne peut pas être \"en attente\"") } @Test - fun `validate should pass if there is a control with an date equal to mission's`() { - val startDateTimeUtc = ZonedDateTime.parse("2020-03-04T00:00:00.000Z") - val endDateTimeUtc = ZonedDateTime.parse("2021-03-04T00:00:00.000Z") - val anEnvActionControl = anEnvActionControl(startTime = startDateTimeUtc, endTime = endDateTimeUtc) - val mission = - aMissionEntity( - startDateTimeUtc = startDateTimeUtc, - endDateTimeUtc = endDateTimeUtc, - envActions = listOf(anEnvActionControl), - ) + fun `validate should throw an exception if there is a control with seizure is PENDING when mission has ended`() { + val endDateTimeUtc = ZonedDateTime.now().minusSeconds(1) + val anEnvActionControl = + anEnvActionControl(infractions = listOf(anInfraction(seizure = SeizureTypeEnum.PENDING))) + val mission = aMissionEntity(endDateTimeUtc = endDateTimeUtc, envActions = listOf(anEnvActionControl)) - missionValidator.validate(mission) + val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } + assertThat( + assertThrows.message, + ).isEqualTo("L'appréhension/saisie ne peut pas être \"en attente\"") } - @ParameterizedTest - @EnumSource(value = InfractionTypeEnum::class, names = ["WAITING"], mode = EnumSource.Mode.EXCLUDE) - fun `validate should throw an exception if there is a control with infractionType other than WAITING that doesnt have a NATINF`( - infractionType: InfractionTypeEnum, - ) { - val anEnvActionControl = anEnvActionControl(infractions = listOf(anInfraction(infractionType = infractionType))) - val mission = aMissionEntity(envActions = listOf(anEnvActionControl)) + @Test + fun `validate should throw an exception if there is a control with formalNotice is PENDING when mission has ended`() { + val endDateTimeUtc = ZonedDateTime.now().minusSeconds(1) + val anEnvActionControl = + anEnvActionControl(infractions = listOf(anInfraction(formalNotice = FormalNoticeEnum.PENDING))) + val mission = aMissionEntity(endDateTimeUtc = endDateTimeUtc, envActions = listOf(anEnvActionControl)) val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } assertThat( assertThrows.message, - ).isEqualTo("Une infraction doit avoir une natinf si le type d'infraction n'est pas \"En attente\"") + ).isEqualTo("La mise en demeure ne peut pas être \"en attente\"") } @Test @@ -340,6 +357,20 @@ class MissionValidatorUTest { missionValidator.validate(mission) } + @Test + fun `validate should pass if there is a surveillance without geometry`() { + val endDateTimeUtc = ZonedDateTime.now().minusSeconds(1) + val anEnvActionSurveillance = anEnvActionSurveillance(geom = null) + val mission = + aMissionEntity( + endDateTimeUtc = endDateTimeUtc, + envActions = listOf(anEnvActionSurveillance), + ) + + val assertThrows = assertThrows(BackendUsageException::class.java) { missionValidator.validate(mission) } + assertThat(assertThrows.message).isEqualTo("La géométrie de la surveillance est obligatoire") + } + @Test fun `validate should throw an exception if there is a surveillance without control plans when mission has ended`() { val endDateTimeUtc = ZonedDateTime.now().minusSeconds(1) diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/reporting/ReportingValidatorUTest.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/reporting/ReportingValidatorUTest.kt index fe1bd85a9..2b16692e1 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/reporting/ReportingValidatorUTest.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/reporting/ReportingValidatorUTest.kt @@ -1,5 +1,6 @@ package fr.gouv.cacem.monitorenv.domain.validators.reporting +import fr.gouv.cacem.monitorenv.domain.entities.reporting.TargetTypeEnum import fr.gouv.cacem.monitorenv.domain.exceptions.BackendUsageException import fr.gouv.cacem.monitorenv.domain.use_cases.reportings.fixtures.ReportingFixture.Companion.aReporting import fr.gouv.cacem.monitorenv.domain.use_cases.reportings.fixtures.ReportingFixture.Companion.aReportingSourceControlUnit @@ -63,6 +64,14 @@ class ReportingValidatorUTest { assertThat(assertThrows.message).isEqualTo("La validité du signalement doit être supérieur à 0") } + @Test + fun `validate should throw an exception if targetType is OTHER without description`() { + val reporting = aReporting(targetType = TargetTypeEnum.OTHER, description = null) + + val assertThrows = assertThrows(BackendUsageException::class.java) { reportingValidator.validate(reporting) } + assertThat(assertThrows.message).isEqualTo("La description de la cible est obligatoire") + } + @Test fun `validate should pass for a valid ReportingEntity`() { val reporting = aReporting() diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/vigilance_area/VigilanceAreaValidatorUTest.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/vigilance_area/VigilanceAreaValidatorUTest.kt index 370d39456..0fa2f3466 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/vigilance_area/VigilanceAreaValidatorUTest.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/vigilance_area/VigilanceAreaValidatorUTest.kt @@ -91,6 +91,24 @@ class VigilanceAreaValidatorUTest { assertThat(assertThrows.message).isEqualTo("Le nombre d'occurence est obligatoire") } + @Test + fun `validate should throw an exception if geometry is null when it is published`() { + val vigilanceArea = aVigilanceAreaEntity(geom = null, isDraft = false) + + val assertThrows = + assertThrows(BackendUsageException::class.java) { vigilanceAreaValidator.validate(vigilanceArea) } + assertThat(assertThrows.message).isEqualTo("La géométrie est obligatoire") + } + + @Test + fun `validate should throw an exception if comments are null when it is published`() { + val vigilanceArea = aVigilanceAreaEntity(comments = null, isDraft = false) + + val assertThrows = + assertThrows(BackendUsageException::class.java) { vigilanceAreaValidator.validate(vigilanceArea) } + assertThat(assertThrows.message).isEqualTo("Un commentaire est obligatoire") + } + @Test fun `validate should throw an exception if frequency is null when it is published and it is not limitless`() { val vigilanceArea = aVigilanceAreaEntity(frequency = null, isDraft = false) diff --git a/frontend/src/api/types.ts b/frontend/src/api/types.ts index 9678c337f..d468bee08 100644 --- a/frontend/src/api/types.ts +++ b/frontend/src/api/types.ts @@ -4,13 +4,23 @@ export enum ApiErrorCode { /** Thrown when attempting to attach a mission to a reporting that has already a mission attached. */ CHILD_ALREADY_ATTACHED = 'CHILD_ALREADY_ATTACHED', + /** Thrown when attempting to find an entity that has does not exist. */ + ENTITY_NOT_FOUND = 'ENTITY_NOT_FOUND', + + /** Thrown when an entity could not be saved. */ + ENTITY_NOT_SAVED = 'ENTITY_NOT_SAVED', + /** Thrown when attempting to delete a mission that has actions created by other applications. */ EXISTING_MISSION_ACTION = 'EXISTING_MISSION_ACTION', + // TO_DELETE ? FOREIGN_KEY_CONSTRAINT = 'FOREIGN_KEY_CONSTRAINT', /** Thrown when attempting to archive an entity linked to non-archived child(ren). */ - UNARCHIVED_CHILD = 'UNARCHIVED_CHILD' + UNARCHIVED_CHILD = 'UNARCHIVED_CHILD', + + /** Thrown when an entity contain an unvalid property. */ + UNVALID_PROPERTY = 'UNVALID_PROPERTY' } export interface BackendApiErrorResponse { diff --git a/frontend/src/features/Reportings/components/ReportingForm/Schema/index.tsx b/frontend/src/features/Reportings/components/ReportingForm/Schema/index.tsx index ac187911a..712805597 100644 --- a/frontend/src/features/Reportings/components/ReportingForm/Schema/index.tsx +++ b/frontend/src/features/Reportings/components/ReportingForm/Schema/index.tsx @@ -53,7 +53,7 @@ export const ReportingSchema: Yup.Schema< > = Yup.object() .shape({ actionTaken: Yup.string().optional(), - createdAt: Yup.string().optional().required('Veuillez définir la date de signalement'), + createdAt: Yup.string().required('Veuillez définir la date de signalement'), description: Yup.string().when('targetType', { is: ReportingTargetTypeEnum.OTHER, otherwise: schema => schema.optional(), @@ -72,7 +72,6 @@ export const ReportingSchema: Yup.Schema< openBy: Yup.string() .min(3, 'Minimum 3 lettres pour le trigramme') .max(3, 'Maximum 3 lettres pour le trigramme') - .nullable() .required('Requis'), reportingSources: Yup.array().of(ReportingSourceSchema).min(1).required(), reportType: Yup.mixed() @@ -97,9 +96,8 @@ export const ReportingSchema: Yup.Schema< ) .optional(), targetType: Yup.string().optional(), - themeId: Yup.number().nullable().required('Veuillez définir la thématique du signalement'), + themeId: Yup.number().required('Veuillez définir la thématique du signalement'), validityTime: Yup.number() - .nullable() .required('Veuillez définir la durée de validité du signalement') .min(1, 'Veuillez définir une durée de validité supérieure à 0'), vehicleType: Yup.string().optional(), From 54c4eda759b34b32115ae39f07d503538ea8b2bf Mon Sep 17 00:00:00 2001 From: Maxime Perrault Date: Tue, 21 Jan 2025 18:20:00 +0100 Subject: [PATCH 10/14] tech: - Mission : add some infraction validation case - reporting : review input, add description validation - Vigilance area : review input, add geom and comment validation --- .../bff/inputs/reportings/CreateOrUpdateReportingDataInput.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/inputs/reportings/CreateOrUpdateReportingDataInput.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/inputs/reportings/CreateOrUpdateReportingDataInput.kt index 0caa04787..0296cf354 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/inputs/reportings/CreateOrUpdateReportingDataInput.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/inputs/reportings/CreateOrUpdateReportingDataInput.kt @@ -16,7 +16,7 @@ data class CreateOrUpdateReportingDataInput( val targetType: TargetTypeEnum? = null, val vehicleType: VehicleTypeEnum? = null, val targetDetails: List? = listOf(), - val geom: Geometry? = null, + val geom: Geometry, val description: String? = null, val reportType: ReportingTypeEnum, val themeId: Int, From 87830fa1edc0624136c9c1e6d021fc8b423d1b52 Mon Sep 17 00:00:00 2001 From: Maxime Perrault Date: Wed, 22 Jan 2025 09:18:38 +0100 Subject: [PATCH 11/14] tech: add user message on unexpected unvalid property --- .../validators/reporting/ReportingValidator.kt | 8 +++++++- .../endpoints/ControllersExceptionHandler.kt | 2 +- .../reporting/ReportingValidatorUTest.kt | 8 ++++++++ frontend/src/api/reportingsAPI.ts | 8 +++++++- .../features/Mission/useCases/saveMission.ts | 13 +++++++++---- .../Reportings/useCases/saveReporting.ts | 18 +++++++++++------- .../useCases/saveVigilanceArea.ts | 3 +++ 7 files changed, 46 insertions(+), 14 deletions(-) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/reporting/ReportingValidator.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/reporting/ReportingValidator.kt index b41316a4f..7d73bb907 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/reporting/ReportingValidator.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/reporting/ReportingValidator.kt @@ -18,7 +18,7 @@ class ReportingValidator : Validator { override fun validate(reporting: ReportingEntity) { logger.info("Validating reporting: ${reporting.id}") - if (reporting.openBy !== null && reporting.openBy.length != NB_CHAR_MAX) { + if (reporting.openBy?.length != NB_CHAR_MAX == true) { throw BackendUsageException( BackendUsageErrorCode.UNVALID_PROPERTY, "Le trigramme \"ouvert par\" doit avoir 3 lettres", @@ -36,6 +36,12 @@ class ReportingValidator : Validator { "La validité du signalement doit être supérieur à 0", ) } + if (reporting.subThemeIds?.isEmpty() == true) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "Un sous-thème est obligatoire", + ) + } reporting.reportingSources.forEach { source -> when (source.sourceType) { SourceTypeEnum.SEMAPHORE -> { diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/ControllersExceptionHandler.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/ControllersExceptionHandler.kt index da5189d48..38ddf295d 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/ControllersExceptionHandler.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/ControllersExceptionHandler.kt @@ -50,7 +50,7 @@ class ControllersExceptionHandler(val sentryConfig: SentryConfig) { @ExceptionHandler(BackendUsageException::class) fun handleBackendUsageException(e: BackendUsageException): BackendUsageErrorDataOutput { logger.error(e.message) - return BackendUsageErrorDataOutput(code = e.code, data = e.data, message = e.message) + return BackendUsageErrorDataOutput(code = e.code, data = e.data, message = null) } @ResponseStatus(HttpStatus.BAD_REQUEST) diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/reporting/ReportingValidatorUTest.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/reporting/ReportingValidatorUTest.kt index 2b16692e1..5eac18000 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/reporting/ReportingValidatorUTest.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/domain/validators/reporting/ReportingValidatorUTest.kt @@ -64,6 +64,14 @@ class ReportingValidatorUTest { assertThat(assertThrows.message).isEqualTo("La validité du signalement doit être supérieur à 0") } + @Test + fun `validate should throw an exception if subthemeIds is empty`() { + val reporting = aReporting(subThemeIds = listOf()) + + val assertThrows = assertThrows(BackendUsageException::class.java) { reportingValidator.validate(reporting) } + assertThat(assertThrows.message).isEqualTo("Un sous-thème est obligatoire") + } + @Test fun `validate should throw an exception if targetType is OTHER without description`() { val reporting = aReporting(targetType = TargetTypeEnum.OTHER, description = null) diff --git a/frontend/src/api/reportingsAPI.ts b/frontend/src/api/reportingsAPI.ts index ebd692544..3a552598a 100644 --- a/frontend/src/api/reportingsAPI.ts +++ b/frontend/src/api/reportingsAPI.ts @@ -1,6 +1,7 @@ import { type EntityState, createEntityAdapter } from '@reduxjs/toolkit' import { monitorenvPrivateApi } from './api' +import { ApiErrorCode } from './types' import { getQueryString } from '../utils/getQueryStringFormatted' import type { Reporting } from '../domain/entities/reporting' @@ -96,7 +97,12 @@ export const reportingsAPI = monitorenvPrivateApi.injectEndpoints({ body: { id, ...patch }, method: 'PUT', url: `/v1/reportings/${id}` - }) + }), + transformErrorResponse: response => { + if (response.data.type === ApiErrorCode.UNVALID_PROPERTY) { + throw new Error('coucou les loulous') + } + } }) }) }) diff --git a/frontend/src/features/Mission/useCases/saveMission.ts b/frontend/src/features/Mission/useCases/saveMission.ts index d17704203..9d0cf36b6 100644 --- a/frontend/src/features/Mission/useCases/saveMission.ts +++ b/frontend/src/features/Mission/useCases/saveMission.ts @@ -16,8 +16,10 @@ import { import { getMissionPageRoute } from '../../../utils/routes' import { missionActions } from '../slice' +import type { HomeAppThunk } from '@store/index' + export const saveMission = - (values, reopen = false, quitAfterSave = false) => + (values, reopen = false, quitAfterSave = false): HomeAppThunk => async (dispatch, getState) => { const { reporting: { reportings }, @@ -98,12 +100,15 @@ export const saveMission = missionUpdated, reportings }) - } else { - if (response.error?.data?.type === ApiErrorCode.CHILD_ALREADY_ATTACHED) { + } else if ('data' in response.error) { + if (response.error.data?.code === ApiErrorCode.CHILD_ALREADY_ATTACHED) { throw Error('Le signalement est déjà rattaché à une mission') } - throw Error('Erreur à la création ou à la modification de la mission') + if (response.error.data?.code === ApiErrorCode.UNVALID_PROPERTY) { + throw Error('Une propriété est invalide') + } } + throw Error('Erreur à la création ou à la modification de la mission') } catch (error) { dispatch(setToast({ containerId: 'sideWindow', message: error })) } diff --git a/frontend/src/features/Reportings/useCases/saveReporting.ts b/frontend/src/features/Reportings/useCases/saveReporting.ts index dca7b5c7c..d7339a6a8 100644 --- a/frontend/src/features/Reportings/useCases/saveReporting.ts +++ b/frontend/src/features/Reportings/useCases/saveReporting.ts @@ -12,9 +12,10 @@ import { mainWindowActions } from '../../MainWindow/slice' import { isNewReporting } from '../utils' import type { Reporting } from '../../../domain/entities/reporting' +import type { HomeAppThunk } from '@store/index' export const saveReporting = - (values: Reporting | Partial, reportingContext: ReportingContext, quitAfterSave = false) => + (values: Reporting | Partial, reportingContext: ReportingContext, quitAfterSave = false): HomeAppThunk => async dispatch => { const valuesToSave = omit(values, ['attachedMission']) const reportingIsNew = isNewReporting(values.id) @@ -26,12 +27,12 @@ export const saveReporting = await dispatch(reportingActions.setIsListeningToEvents(false)) try { const response = await dispatch(endpoint.initiate(newOrNextReportingData)) - if ('data' in response) { + if (response.data) { if (reportingIsNew) { const newReporting = { context: reportingContext, isFormDirty: false, - reporting: response.data + reporting: { ...response.data, id: response.data.id! } } dispatch( @@ -43,7 +44,7 @@ export const saveReporting = reportingActions.setReporting({ context: reportingContext, isFormDirty: false, - reporting: response.data + reporting: { ...response.data, id: response.data.id! } }) ) } @@ -67,12 +68,15 @@ export const saveReporting = ) dispatch(updateMapInteractionListeners(MapInteractionListenerEnum.NONE)) dispatch(reportingActions.deleteSelectedReporting(values.id)) - } else { - if (response.error.data?.type === ApiErrorCode.CHILD_ALREADY_ATTACHED) { + } else if ('data' in response.error) { + if (response.error.data?.code === ApiErrorCode.CHILD_ALREADY_ATTACHED) { throw Error('Le signalement est déjà rattaché à une mission') } - throw Error('Erreur à la création ou à la modification du signalement') + if (response.error.data?.code === ApiErrorCode.UNVALID_PROPERTY) { + throw Error('Une propriété est invalide') + } } + throw Error('Erreur à la création ou à la modification du signalement') } catch (error) { dispatch(setToast({ containerId: reportingContext, message: error })) } diff --git a/frontend/src/features/VigilanceArea/useCases/saveVigilanceArea.ts b/frontend/src/features/VigilanceArea/useCases/saveVigilanceArea.ts index 851dfae44..0db6bc6f5 100644 --- a/frontend/src/features/VigilanceArea/useCases/saveVigilanceArea.ts +++ b/frontend/src/features/VigilanceArea/useCases/saveVigilanceArea.ts @@ -1,3 +1,4 @@ +import { ApiErrorCode } from '@api/types' import { vigilanceAreasAPI } from '@api/vigilanceAreasAPI' import { addMainWindowBanner } from '@features/MainWindow/useCases/addMainWindowBanner' import { customDayjs, Level } from '@mtes-mct/monitor-ui' @@ -59,6 +60,8 @@ export const saveVigilanceArea = }) ) } + } else if ('data' in response.error && response.error.data?.code === ApiErrorCode.UNVALID_PROPERTY) { + throw Error('Une propriété est invalide') } } catch (error) { dispatch( From 889cb33e2a5bc662d37d39cea48874aed45ec342 Mon Sep 17 00:00:00 2001 From: Maxime Perrault Date: Wed, 22 Jan 2025 09:30:20 +0100 Subject: [PATCH 12/14] tech: refactor (extract method) --- .../validators/mission/MissionValidator.kt | 7 ++ .../reporting/ReportingValidator.kt | 36 +++---- .../vigilance_area/VigilanceAreaValidator.kt | 94 ++++++++++--------- .../features/Mission/useCases/saveMission.ts | 2 +- .../Reportings/useCases/saveReporting.ts | 2 +- 5 files changed, 78 insertions(+), 63 deletions(-) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidator.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidator.kt index 0d07140a1..7c2be4019 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidator.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/mission/MissionValidator.kt @@ -97,6 +97,13 @@ class MissionValidator : Validator { validateControlPlan(control) } + validateInfractions(control, isMissionEnded) + } + + private fun validateInfractions( + control: EnvActionControlEntity, + isMissionEnded: Boolean, + ) { val sumOfNbTarget = control.infractions?.sumOf { infraction -> infraction.nbTarget } if (sumOfNbTarget != 0 && sumOfNbTarget != null && (control.actionNumberOfControls != null && sumOfNbTarget > control.actionNumberOfControls)) { throw BackendUsageException( diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/reporting/ReportingValidator.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/reporting/ReportingValidator.kt index 7d73bb907..57b789f62 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/reporting/ReportingValidator.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/reporting/ReportingValidator.kt @@ -24,12 +24,6 @@ class ReportingValidator : Validator { "Le trigramme \"ouvert par\" doit avoir 3 lettres", ) } - if (reporting.reportingSources.isEmpty()) { - throw BackendUsageException( - BackendUsageErrorCode.UNVALID_PROPERTY, - "Une source du signalement est obligatoire", - ) - } if (reporting.validityTime == 0) { throw BackendUsageException( BackendUsageErrorCode.UNVALID_PROPERTY, @@ -42,13 +36,30 @@ class ReportingValidator : Validator { "Un sous-thème est obligatoire", ) } + if (reporting.targetType === TargetTypeEnum.OTHER && reporting.description === null) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "La description de la cible est obligatoire", + ) + } + validateReportingSources(reporting) + } + + private fun validateReportingSources(reporting: ReportingEntity) { + if (reporting.reportingSources.isEmpty()) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "Une source du signalement est obligatoire", + ) + } reporting.reportingSources.forEach { source -> + val errorMessage = "La source du signalement est invalide" when (source.sourceType) { SourceTypeEnum.SEMAPHORE -> { if (source.semaphoreId === null || source.controlUnitId !== null || source.sourceName !== null) { throw BackendUsageException( BackendUsageErrorCode.UNVALID_PROPERTY, - "La source du signalement est invalide", + errorMessage, ) } } @@ -57,7 +68,7 @@ class ReportingValidator : Validator { if (source.semaphoreId !== null || source.controlUnitId === null || source.sourceName !== null) { throw BackendUsageException( BackendUsageErrorCode.UNVALID_PROPERTY, - "La source du signalement est invalide", + errorMessage, ) } } @@ -66,18 +77,11 @@ class ReportingValidator : Validator { if (source.semaphoreId !== null || source.controlUnitId !== null || source.sourceName === null) { throw BackendUsageException( BackendUsageErrorCode.UNVALID_PROPERTY, - "La source du signalement est invalide", + errorMessage, ) } } } } - - if (reporting.targetType === TargetTypeEnum.OTHER && reporting.description === null) { - throw BackendUsageException( - BackendUsageErrorCode.UNVALID_PROPERTY, - "La description de la cible est obligatoire", - ) - } } } diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/vigilance_area/VigilanceAreaValidator.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/vigilance_area/VigilanceAreaValidator.kt index 619806c60..247b0f7e8 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/vigilance_area/VigilanceAreaValidator.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/validators/vigilance_area/VigilanceAreaValidator.kt @@ -19,64 +19,68 @@ class VigilanceAreaValidator : Validator { logger.info("Validating vigilance area: ${vigilanceArea.id}") if (!vigilanceArea.isDraft) { - if (vigilanceArea.geom === null) { + validatePublishedVigilanceArea(vigilanceArea) + } + } + + private fun validatePublishedVigilanceArea(vigilanceArea: VigilanceAreaEntity) { + if (vigilanceArea.geom === null) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "La géométrie est obligatoire", + ) + } + if (vigilanceArea.comments === null) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "Un commentaire est obligatoire", + ) + } + if (vigilanceArea.createdBy !== null && vigilanceArea.createdBy.length != NB_CHAR_MAX) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "Le trigramme \"créé par\" doit avoir 3 lettres", + ) + } + if (vigilanceArea.themes?.isEmpty() == true) { + throw BackendUsageException(BackendUsageErrorCode.UNVALID_PROPERTY, "Un thème est obligatoire") + } + if (!vigilanceArea.isAtAllTimes) { + if (vigilanceArea.startDatePeriod === null) { throw BackendUsageException( BackendUsageErrorCode.UNVALID_PROPERTY, - "La géométrie est obligatoire", + "La date de début est obligatoire", ) } - if (vigilanceArea.comments === null) { + if (vigilanceArea.endDatePeriod === null) { throw BackendUsageException( BackendUsageErrorCode.UNVALID_PROPERTY, - "Un commentaire est obligatoire", + "La date de fin est obligatoire", ) } - if (vigilanceArea.createdBy !== null && vigilanceArea.createdBy.length != NB_CHAR_MAX) { + if (vigilanceArea.frequency === null) { throw BackendUsageException( BackendUsageErrorCode.UNVALID_PROPERTY, - "Le trigramme \"créé par\" doit avoir 3 lettres", + "La fréquence est obligatoire", ) } - if (vigilanceArea.themes?.isEmpty() == true) { - throw BackendUsageException(BackendUsageErrorCode.UNVALID_PROPERTY, "Un thème est obligatoire") + if (vigilanceArea.frequency !== FrequencyEnum.NONE && vigilanceArea.endingCondition === null) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "La condition de fin est obligatoire", + ) + } + if (vigilanceArea.endingCondition === EndingConditionEnum.END_DATE && vigilanceArea.endingOccurrenceDate === null) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "La date de fin de l'occurence est obligatoire", + ) } - if (!vigilanceArea.isAtAllTimes) { - if (vigilanceArea.startDatePeriod === null) { - throw BackendUsageException( - BackendUsageErrorCode.UNVALID_PROPERTY, - "La date de début est obligatoire", - ) - } - if (vigilanceArea.endDatePeriod === null) { - throw BackendUsageException( - BackendUsageErrorCode.UNVALID_PROPERTY, - "La date de fin est obligatoire", - ) - } - if (vigilanceArea.frequency === null) { - throw BackendUsageException( - BackendUsageErrorCode.UNVALID_PROPERTY, - "La fréquence est obligatoire", - ) - } - if (vigilanceArea.frequency !== FrequencyEnum.NONE && vigilanceArea.endingCondition === null) { - throw BackendUsageException( - BackendUsageErrorCode.UNVALID_PROPERTY, - "La condition de fin est obligatoire", - ) - } - if (vigilanceArea.endingCondition === EndingConditionEnum.END_DATE && vigilanceArea.endingOccurrenceDate === null) { - throw BackendUsageException( - BackendUsageErrorCode.UNVALID_PROPERTY, - "La date de fin de l'occurence est obligatoire", - ) - } - if (vigilanceArea.endingCondition === EndingConditionEnum.OCCURENCES_NUMBER && (vigilanceArea.endingOccurrencesNumber === null || vigilanceArea.endingOccurrencesNumber == 0)) { - throw BackendUsageException( - BackendUsageErrorCode.UNVALID_PROPERTY, - "Le nombre d'occurence est obligatoire", - ) - } + if (vigilanceArea.endingCondition === EndingConditionEnum.OCCURENCES_NUMBER && (vigilanceArea.endingOccurrencesNumber === null || vigilanceArea.endingOccurrencesNumber == 0)) { + throw BackendUsageException( + BackendUsageErrorCode.UNVALID_PROPERTY, + "Le nombre d'occurence est obligatoire", + ) } } } diff --git a/frontend/src/features/Mission/useCases/saveMission.ts b/frontend/src/features/Mission/useCases/saveMission.ts index 9d0cf36b6..05b16cc47 100644 --- a/frontend/src/features/Mission/useCases/saveMission.ts +++ b/frontend/src/features/Mission/useCases/saveMission.ts @@ -101,7 +101,7 @@ export const saveMission = reportings }) } else if ('data' in response.error) { - if (response.error.data?.code === ApiErrorCode.CHILD_ALREADY_ATTACHED) { + if (response.error.data?.type === ApiErrorCode.CHILD_ALREADY_ATTACHED) { throw Error('Le signalement est déjà rattaché à une mission') } if (response.error.data?.code === ApiErrorCode.UNVALID_PROPERTY) { diff --git a/frontend/src/features/Reportings/useCases/saveReporting.ts b/frontend/src/features/Reportings/useCases/saveReporting.ts index d7339a6a8..2e9235d6d 100644 --- a/frontend/src/features/Reportings/useCases/saveReporting.ts +++ b/frontend/src/features/Reportings/useCases/saveReporting.ts @@ -69,7 +69,7 @@ export const saveReporting = dispatch(updateMapInteractionListeners(MapInteractionListenerEnum.NONE)) dispatch(reportingActions.deleteSelectedReporting(values.id)) } else if ('data' in response.error) { - if (response.error.data?.code === ApiErrorCode.CHILD_ALREADY_ATTACHED) { + if (response.error.data?.type === ApiErrorCode.CHILD_ALREADY_ATTACHED) { throw Error('Le signalement est déjà rattaché à une mission') } if (response.error.data?.code === ApiErrorCode.UNVALID_PROPERTY) { From 8cab1ea926bc446f04114f9be0baf29b50d006f1 Mon Sep 17 00:00:00 2001 From: Maxime Perrault Date: Thu, 23 Jan 2025 18:22:05 +0100 Subject: [PATCH 13/14] tech: add geom to envaction surveillance --- .../V666.10__insert_dummy_env_actions.sql | 194 +++++++++--------- 1 file changed, 99 insertions(+), 95 deletions(-) diff --git a/backend/src/main/resources/db/testdata/V666.10__insert_dummy_env_actions.sql b/backend/src/main/resources/db/testdata/V666.10__insert_dummy_env_actions.sql index 6634b2a18..1a2795915 100644 --- a/backend/src/main/resources/db/testdata/V666.10__insert_dummy_env_actions.sql +++ b/backend/src/main/resources/db/testdata/V666.10__insert_dummy_env_actions.sql @@ -10,137 +10,141 @@ INSERT INTO public.env_actions (id, mission_id, action_type, value, action_start is_safety_equipment_and_standards_compliance_control, is_seafarers_control, open_by, completed_by) VALUES ('e2257638-ddef-4611-960c-7675a3254c38', 38, 'SURVEILLANCE', '{ - "observations": "" + "observations": "" }', '2022-07-30 08:53:31.588693', '0106000020E61000000300000001030000000100000005000000E1AC900B314306C0DCABC1C17F1C484077EC6F225D5006C0E9C04905DB0A4840C4FDB241475F05C0D322916C64104840C4FDB241475F05C061C3D32BE51E4840E1AC900B314306C0DCABC1C17F1C4840010300000001000000050000001A381C6D873C05C0857E01182A1748407A5824FD283005C06AB86D846A13484012C925C8E7D104C048BD6DC7D014484056F6FAE640DF04C04921B9CACD1748401A381C6D873C05C0857E01182A17484001030000000100000005000000BA44FD4709AE06C0BD44AB4926174840374C3CB9097B06C0416F22E1981148409F7BAC6C615606C0DA75EB0C3E164840F5F0C8CCC36906C01B578E56561A4840BA44FD4709AE06C0BD44AB4926174840', NULL, '56', '2022-07-30 10:53:31.588693', NULL, NULL, NULL, NULL, 'ABC', NULL), ('f3e90d3a-6ba4-4bb3-805e-d391508aa46d', 38, 'CONTROL', '{ - "infractions": [ - { - "id": "6670e718-3ecd-46c1-8149-8b963c6f72dd", - "natinf": [ - "10041" - ], - "vesselSize": 23, - "vesselType": "COMMERCIAL", - "companyName": "MASOCIETE", - "formalNotice": "YES", - "observations": "RAS", - "infractionType": "WITH_REPORT", - "administrativeResponse": "REGULARIZATION", - "registrationNumber": null, - "controlledPersonIdentity": null, - "seizure": "NO" - } - ], - "vehicleType": null, - "observations": null, - "actionTargetType": "COMPANY", - "actionNumberOfControls": 1 + "infractions": [ + { + "id": "6670e718-3ecd-46c1-8149-8b963c6f72dd", + "natinf": [ + "10041" + ], + "vesselSize": 23, + "vesselType": "COMMERCIAL", + "companyName": "MASOCIETE", + "formalNotice": "YES", + "observations": "RAS", + "infractionType": "WITH_REPORT", + "administrativeResponse": "REGULARIZATION", + "registrationNumber": null, + "controlledPersonIdentity": null, + "seizure": "NO" + } + ], + "vehicleType": null, + "observations": null, + "actionTargetType": "COMPANY", + "actionNumberOfControls": 1 }', '2022-07-29 11:53:31.588693', '0104000020E6100000010000000101000000399291D4BE1805C09E1A585CD6154840', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'ABC', 'DEF'), ('475d2887-5344-46cd-903b-8cb5e42f9a9c', 49, 'SURVEILLANCE', '{ - "duration": 0.0, - "observations": "RAS", - "protectedSpecies": [] + "duration": 0.0, + "observations": "RAS", + "protectedSpecies": [] }', NULL, '0106000020E61000000100000001030000000100000005000000D56979C3E95203C0BC117648B972474084387273B24D02C00C726AA38C6E4740BFFBD6B9762002C0349A2D10497347407A8D399212A102C0546E1659817A4740D56979C3E95203C0BC117648B9724740', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'ABC', NULL), ('16eeb9e8-f30c-430e-b36b-32b4673f81ce', 49, 'NOTE', '{ - "observations": "Note libre" + "observations": "Note libre" }', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), ('6d4b7d0a-79ce-47cf-ac26-2024d2b27f28', 49, 'CONTROL', '{ - "infractions": [ - { - "id": "e56648c1-6ca3-4d5e-a5d2-114aa7c17126", - "natinf": [ - "10231", - "10228" - ], - "vesselSize": 11, - "vesselType": null, - "companyName": null, - "formalNotice": "PENDING", - "observations": "RAS", - "infractionType": "WAITING", - "administrativeResponse": "PENDING", - "registrationNumber": null, - "controlledPersonIdentity": "M DURAND", - "seizure": "YES" - } - ], - "vehicleType": null, - "actionTargetType": "INDIVIDUAL", - "actionNumberOfControls": 1 + "infractions": [ + { + "id": "e56648c1-6ca3-4d5e-a5d2-114aa7c17126", + "natinf": [ + "10231", + "10228" + ], + "vesselSize": 11, + "vesselType": null, + "companyName": null, + "formalNotice": "PENDING", + "observations": "RAS", + "infractionType": "WAITING", + "administrativeResponse": "PENDING", + "registrationNumber": null, + "controlledPersonIdentity": "M DURAND", + "seizure": "YES" + } + ], + "vehicleType": null, + "actionTargetType": "INDIVIDUAL", + "actionNumberOfControls": 1 }', NULL, '0104000020E61000000100000001010000003B0DADC6D4BB01C0A8387A2964714740', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'ABC', null), ('c52c6f20-e495-4b29-b3df-d7edfb67fdd7', 34, 'SURVEILLANCE', '{ - "duration": 0.0, - "observations": "RAS", - "protectedSpecies": [] + "duration": 0.0, + "observations": "RAS", + "protectedSpecies": [] }', '2022-07-16 10:03:12.588693', '0106000020E61000000100000001030000000100000009000000AD0812BCE168E4BFCCDEEA3227BD4840BE63AEABD812E4BF1C5E8873F8AC484044BD156CA117DABF84C0E2AF49AC48408E16A14DE463CCBFBC9F7168A2A5484008BF4C12D0F97B3F9494F5EA3CAB4840399BF9438A28B43FDC4BF050D9BB48404BAA02B73C2CCCBF24A79C8362CD4840BC46F7A9D24DE1BFA0238D36B2D04840AD0812BCE168E4BFCCDEEA3227BD4840', 'MEMN', NULL, '2022-07-16 12:03:12.588693', NULL, NULL, NULL, NULL, 'ABC', 'DEF'), ('b8007c8a-5135-4bc3-816f-c69c7b75d807', 34, 'CONTROL', '{ - "observations": "RAS", - "infractions": [ - { - "id": "5d5b7829-68cd-4436-8c0b-1cc8db7788a0", - "natinf": [ - "10038", - "10231" - ], - "vesselSize": 45, - "vesselType": "COMMERCIAL", - "companyName": null, - "formalNotice": "PENDING", - "observations": "Pas d''observations", - "infractionType": "WITH_REPORT", - "administrativeResponse": "PENDING", - "registrationNumber": "BALTIK", - "controlledPersonIdentity": "John Doe", - "seizure": "NO" - } - ], - "vehicleType": "VESSEL", - "actionTargetType": "VEHICLE", - "actionNumberOfControls": 1 + "observations": "RAS", + "infractions": [ + { + "id": "5d5b7829-68cd-4436-8c0b-1cc8db7788a0", + "natinf": [ + "10038", + "10231" + ], + "vesselSize": 45, + "vesselType": "COMMERCIAL", + "companyName": null, + "formalNotice": "PENDING", + "observations": "Pas d''observations", + "infractionType": "WITH_REPORT", + "administrativeResponse": "PENDING", + "registrationNumber": "BALTIK", + "controlledPersonIdentity": "John Doe", + "seizure": "NO" + } + ], + "vehicleType": "VESSEL", + "actionTargetType": "VEHICLE", + "actionNumberOfControls": 1 }', '2022-07-16 09:01:12.588693', '0104000020E610000001000000010100000047A07E6651E3DEBF044620AB65C54840', NULL, NULL, '2022-07-16 12:03:12.588693', NULL, NULL, NULL, NULL, 'ABC', NULL), ('4d9a3139-6c60-49a5-b443-0e6238a6a120', 41, 'CONTROL', '{ - "infractions": [], - "vehicleType": null, - "observations": "", - "actionTargetType": null, - "actionNumberOfControls": null + "infractions": [], + "vehicleType": null, + "observations": "", + "actionTargetType": null, + "actionNumberOfControls": null }', NULL, NULL, NULL, NULL, NULL, TRUE, TRUE, TRUE, TRUE, 'ABC', 'DEF'), ('5865b619-3280-4c67-94ca-9f15da7d5aa7', 27, 'CONTROL', '{ - "infractions": [], - "vehicleType": "VESSEL", - "observations": "", - "actionTargetType": "VEHICLE", - "actionNumberOfControls": 1 + "infractions": [], + "vehicleType": "VESSEL", + "observations": "", + "actionTargetType": "VEHICLE", + "actionNumberOfControls": 1 }', '2022-07-01 02:44:16.588693', NULL, NULL, NULL, NULL, FALSE, FALSE, FALSE, FALSE, 'ABC', 'EFG') ; INSERT INTO public.env_actions VALUES ('2cdcd429-19ab-45ed-a892-7c695bd256e2', 53, 'SURVEILLANCE', '{ - "observations": "RAS" -}', '2022-11-21 14:29:55.588693', NULL, NULL, NULL, '2022-11-22 12:14:48.588693', NULL, NULL, NULL, NULL, 'TO_COMPLETE', + "observations": "RAS" +}', '2022-11-21 14:29:55.588693', + '0106000020E610000001000000010300000001000000040000003CE3086FE6CC13C01444234445F44740EFDAD0710F1D13C07CA5E067610548406DBC9AAB788B13C0781CCFCB160748403CE3086FE6CC13C01444234445F44740', + NULL, NULL, '2022-11-22 12:14:48.588693', NULL, NULL, NULL, NULL, 'TO_COMPLETE', 'ABC', 'DEF'), ('3480657f-7845-4eb4-aa06-07b174b1da45', 53, 'CONTROL', '{ - "observations": "RAS", - "infractions": [], - "vehicleType": "VESSEL", - "actionTargetType": "VEHICLE", - "actionNumberOfControls": 0 + "observations": "RAS", + "infractions": [], + "vehicleType": "VESSEL", + "actionTargetType": "VEHICLE", + "actionNumberOfControls": 0 }', '2022-11-22 10:14:48.588693', '0104000020E610000001000000010100000047A07E6651E3DEBF044620AB65C54840', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'TO_COMPLETE', 'ABC', 'DEF'), ('9969413b-b394-4db4-985f-b00743ffb833', 53, 'SURVEILLANCE', '{ - "observations": "RAS", - "protectedSpecies": [] - }', '2022-11-21 18:29:55.588693', NULL, NULL, NULL, '2022-11-22 13:14:48.588693', NULL, NULL, NULL, NULL, + "observations": "RAS", + "protectedSpecies": [] + }', '2022-11-21 18:29:55.588693', + '0106000020E610000001000000010300000001000000040000003CE3086FE6CC13C01444234445F44740EFDAD0710F1D13C07CA5E067610548406DBC9AAB788B13C0781CCFCB160748403CE3086FE6CC13C01444234445F44740', + NULL, NULL, '2022-11-22 13:14:48.588693', NULL, NULL, NULL, NULL, 'TO_COMPLETE', 'ABC', 'DEF') ; From 4932c2cbbb851d672ba8de252e1152e306cabebf Mon Sep 17 00:00:00 2001 From: Maxime Perrault Date: Mon, 27 Jan 2025 18:53:44 +0100 Subject: [PATCH 14/14] fix: test e2e --- .../V666.12__insert_dummy_reportings.sql | 83 ++++++++++++++----- .../mission_form/mission_actions.spec.ts | 36 +++++++- .../mission_dates_validation.spec.ts | 25 ++++++ 3 files changed, 122 insertions(+), 22 deletions(-) diff --git a/backend/src/main/resources/db/testdata/V666.12__insert_dummy_reportings.sql b/backend/src/main/resources/db/testdata/V666.12__insert_dummy_reportings.sql index 3c038201a..527273632 100644 --- a/backend/src/main/resources/db/testdata/V666.12__insert_dummy_reportings.sql +++ b/backend/src/main/resources/db/testdata/V666.12__insert_dummy_reportings.sql @@ -4,49 +4,90 @@ FROM reportings; CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; INSERT INTO public.reportings -VALUES (1, 2300001, 'VEHICLE', NULL, '[{"mmsi": "", "vesselName": ""}]', +VALUES (1, 2300001, 'VEHICLE', NULL, '[ + { + "mmsi": "", + "vesselName": "" + } +]', '0104000020E610000001000000010100000045035E406AC113C00CF3FE4C1D354840', 'NAMO', 'Description 1', 'INFRACTION_SUSPICION', 'Rejets illicites', '{"Jet de déchet","Carénage sauvage"}', 'ACTION TAKEN', true, true, now() - INTERVAL '3 days', 24, false, false, NULL, NULL, NULL, NULL, NULL, 19, false, now() - INTERVAL '3 days'); INSERT INTO public.reportings -VALUES (2, 2300002, 'VEHICLE', 'VESSEL', '[{"mmsi": "", "vesselName": ""}]', +VALUES (2, 2300002, 'VEHICLE', 'VESSEL', '[ + { + "mmsi": "", + "vesselName": "" + } +]', '0104000020E610000001000000010100000062C9C715311E13C050CB31D53D4F4840', 'NAMO', 'Description 2', 'INFRACTION_SUSPICION', 'Police des mouillages', '{ZMEL}', 'ACTION TAKEN', true, true, now() - INTERVAL '2 days', 2, false, false, NULL, NULL, NULL, NULL, NULL, 12, false, now() - INTERVAL '2 days'); INSERT INTO public.reportings -VALUES (3, 2300003, 'VEHICLE', 'VESSEL', '[{"mmsi": "012314231345", "vesselName": "Vessel 3"}]', +VALUES (3, 2300003, 'VEHICLE', 'VESSEL', '[ + { + "mmsi": "012314231345", + "vesselName": "Vessel 3" + } +]', '0106000020E610000001000000010300000001000000040000006E15B8857C090CC02C07754424784840552A202082FC09C0C0FD120D667A484025BF296025E00BC0805DC2889C7F48406E15B8857C090CC02C07754424784840', 'NAMO', 'Description 3', 'INFRACTION_SUSPICION', 'Police des mouillages', '{ZMEL}', 'ACTION TAKEN', true, true, now() - INTERVAL '1 hour', 1, false, false, NULL, NULL, NULL, NULL, NULL, 12, false, now() - INTERVAL '1 hour'); INSERT INTO public.reportings -VALUES (4, 2300004, 'INDIVIDUAL', NULL, '[{"operatorName": "Mr le dirigeant"}]', +VALUES (4, 2300004, 'INDIVIDUAL', NULL, '[ + { + "operatorName": "Mr le dirigeant" + } +]', '0106000020E6100000010000000103000000010000000500000012F330DDB98206C0CD5EF048C0BF4840FE7303CB321005C092CE3C90A7CC4840820740BBC76A06C07E8D665D8AD94840310109D4AC5507C002C775BEE5C2484012F330DDB98206C0CD5EF048C0BF4840', 'MED', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque ultrices risus ac arcu pellentesque, et tempor justo tempor. Pellentesque sem nisl, tempor id augue non, interdum sollicitudin felis.', 'OBSERVATION', 'Pêche à pied', '{Braconnage}', NULL, true, true, now() - INTERVAL '3 hour', 4, false, false, NULL, NULL, NULL, NULL, NULL, 18, false, now() - INTERVAL '3 hour'); INSERT INTO public.reportings -VALUES (5, 2300005, 'VEHICLE', 'VESSEL', '[{"mmsi": "9876543210", "vesselName": ""}]', +VALUES (5, 2300005, 'VEHICLE', 'VESSEL', '[ + { + "mmsi": "9876543210", + "vesselName": "" + } +]', '0104000020E6100000010000000101000000417927B8BBBBD73F06D9D38A46E24840', 'Guadeloupe', - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', 'OBSERVATION', NULL, NULL, NULL, true, true, - now() - INTERVAL '1 hour', 6, false, false, NULL, NULL, NULL, NULL, NULL, NULL, false, + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', 'OBSERVATION', 'Pêche à pied', + '{Braconnage}', NULL, + true, true, + now() - INTERVAL '1 hour', 6, false, false, 'CDA', NULL, NULL, NULL, NULL, 18, false, now() - INTERVAL '1 hour'); INSERT INTO public.reportings -VALUES (6, 2300006, 'COMPANY', NULL, '[{"vesselName": "Héron", "operatorName": "La société"}]', +VALUES (6, 2300006, 'COMPANY', NULL, '[ + { + "vesselName": "Héron", + "operatorName": "La société" + } +]', '0104000020E6100000010000000101000000A22CD736208DF9BF242A543717D44540', 'Guadeloupe', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', 'INFRACTION_SUSPICION', 'Police des mouillages', '{ZMEL}', NULL, true, true, now() - INTERVAL '75 minutes', 6, false, false, 'ABC', 34, now() - INTERVAL '15 minutes', NULL, 'b8007c8a-5135-4bc3-816f-c69c7b75d807', 12, false, now() - INTERVAL '60 minutes'); INSERT INTO public.reportings -VALUES (7, 2300007, 'COMPANY', NULL, '[{"vesselName": "", "operatorName": "Good Company"}]', +VALUES (7, 2300007, 'COMPANY', NULL, '[ + { + "vesselName": "", + "operatorName": "Good Company" + } +]', '0104000020E6100000010000000101000000BDAE0F9A19C010C068D780A5708E4740', 'NAMO', 'Lorem LoremLorem ipsum dolor sit amet, consectetur adipiscing elit.', 'OBSERVATION', NULL, NULL, NULL, true, true, now() - INTERVAL '90 minutes', 6, false, false, 'DEF', 34, now() - INTERVAL '25 minutes', NULL, NULL, NULL, false, now() - INTERVAL '60 minutes'); INSERT INTO public.reportings -VALUES (8, 2300008, 'COMPANY', NULL, '[{"vesselName": "Mr le gérant", "operatorName": ""}]', +VALUES (8, 2300008, 'COMPANY', NULL, '[ + { + "vesselName": "Mr le gérant", + "operatorName": "" + } +]', '0104000020E61000000100000001010000005BE1449141C0F5BF89869C29BA034740', 'NAMO', 'Lorem LoremLorem ipsum dolor sit amet, consectetur adipiscing elit.', 'INFRACTION_SUSPICION', NULL, NULL, NULL, true, true, now() - INTERVAL '90 minutes', 6, false, false, 'GHI', 38, now() - INTERVAL '25 minutes', NULL, @@ -60,28 +101,28 @@ INSERT INTO reportings (id, reporting_id, target_type, vehicle_type, target_deta created_at, validity_time, is_deleted, mission_id, attached_to_mission_at_utc, detached_from_mission_at_utc, attached_env_action_id, open_by, updated_at_utc) VALUES (9, 2300009, 'COMPANY', NULL, '[ - { - "operatorName": "Good Company", - "vesselName": "Mr le gérant" - } + { + "operatorName": "Good Company", + "vesselName": "Mr le gérant" + } ]', ST_GeomFromText('MULTIPOINT((-4.40757547 48.65546589))', 4326), 'NAMO', 'Lorem LoremLorem ipsum dolor sit amet, consectetur adipiscing elit.', 'OBSERVATION', NULL, NULL, NULL, true, true, now() - INTERVAL '90 minutes', 6, false, 53, now() - INTERVAL '25 minutes', NULL, '9969413b-b394-4db4-985f-b00743ffb833', 'GHI', now() - INTERVAL '60 minutes'), /* reporting linked to a control */ (10, 2300010, 'INDIVIDUAL', NULL, '[ - { - "operatorName": "" - } + { + "operatorName": "" + } ]', ST_GeomFromText('MULTIPOINT((-3.91241571 48.67428686))', 4326), 'NAMO', 'Lorem LoremLorem ipsum dolor sit amet, consectetur adipiscing elit.', 'INFRACTION_SUSPICION', NULL, NULL, NULL, true, true, now() - INTERVAL '90 minutes', 6, false, 53, now() - INTERVAL '25 minutes', NULL, '3480657f-7845-4eb4-aa06-07b174b1da45', 'GHI', now() - INTERVAL '60 minutes'), /* reporting linked to a surveillance */ (11, 2300011, 'OTHER', NULL, '[ - { - "operatorName": "" - } + { + "operatorName": "" + } ]', ST_GeomFromText('MULTIPOINT((-4.76689484 48.52102012))', 4326), 'NAMO', 'La description du signalement', 'OBSERVATION', NULL, NULL, NULL, true, true, now() - INTERVAL '90 minutes', 6, false, 53, now() - INTERVAL '25 minutes', NULL, '9969413b-b394-4db4-985f-b00743ffb833', 'GHI', @@ -125,4 +166,6 @@ VALUES (3, 83); INSERT INTO public.reportings_control_plan_sub_themes VALUES (4, 24); INSERT INTO public.reportings_control_plan_sub_themes +VALUES (5, 24); +INSERT INTO public.reportings_control_plan_sub_themes VALUES (6, 83); diff --git a/frontend/cypress/e2e/side_window/mission_form/mission_actions.spec.ts b/frontend/cypress/e2e/side_window/mission_form/mission_actions.spec.ts index 027ad48c5..77c153ade 100644 --- a/frontend/cypress/e2e/side_window/mission_form/mission_actions.spec.ts +++ b/frontend/cypress/e2e/side_window/mission_form/mission_actions.spec.ts @@ -1,10 +1,29 @@ /// +import { setGeometry } from 'domain/shared_slices/Draw' + import { createPendingMission } from '../../utils/createPendingMission' import { getFutureDate } from '../../utils/getFutureDate' import type { EnvActionControl, EnvActionSurveillance, Infraction } from 'domain/entities/missions' - +import type { GeoJSON } from 'domain/types/GeoJSON' + +export const dispatch = action => cy.window().its('store').invoke('dispatch', action) + +export const surveillanceGeometry: GeoJSON.Geometry = { + coordinates: [ + [ + [ + [-5.445293386230469, 49.204467319852114], + [-6.05778117919922, 48.85600950618519], + [-5.67154308105469, 48.29540491855175], + [-5.010646779785157, 48.68245162584054], + [-5.445293386230469, 49.204467319852114] + ] + ] + ], + type: 'MultiPolygon' +} context('Side Window > Mission Form > Mission actions', () => { beforeEach(() => { cy.viewport(1280, 1024) @@ -215,7 +234,8 @@ context('Side Window > Mission Form > Mission actions', () => { cy.clickButton('Ajouter') cy.clickButton('Ajouter une surveillance') cy.wait(500) - cy.intercept('PUT', '/bff/v1/missions/*').as('updateMission') + cy.clickButton('Ajouter une zone de surveillance') + dispatch(setGeometry(surveillanceGeometry)) cy.get('[id="envActions[0].observations"]').type('Obs.', { force: true }) @@ -223,6 +243,8 @@ context('Side Window > Mission Form > Mission actions', () => { cy.getDataCy('surveillance-open-by').type('ABC') cy.getDataCy('surveillance-completed-by').type('ABC') + cy.intercept('PUT', '/bff/v1/missions/*').as('updateMission') + cy.wait('@updateMission').then(({ response }) => { expect(response && response.statusCode).equal(200) expect(response && response.body.envActions[0].observations).equal('Obs.') @@ -256,6 +278,8 @@ context('Side Window > Mission Form > Mission actions', () => { // Add a surveillance cy.clickButton('Ajouter') cy.clickButton('Ajouter une surveillance') + cy.clickButton('Ajouter une zone de surveillance') + dispatch(setGeometry(surveillanceGeometry)) cy.getDataCy('action-missing-fields-text').contains('3 champs nécessaires aux statistiques à compléter') @@ -280,6 +304,14 @@ context('Side Window > Mission Form > Mission actions', () => { cy.clickButton('Ajouter des contrôles') cy.wait(250) cy.getDataCy('action-missing-fields-text').contains('7 champs nécessaires aux statistiques à compléter') + cy.clickButton('Ajouter un point de contrôle') + cy.wait(200) + + const controlGeometry: GeoJSON.Geometry = { + coordinates: [[-1.84589767, 46.66739394]], + type: 'MultiPoint' + } + dispatch(setGeometry(controlGeometry)) cy.intercept('PUT', '/bff/v1/missions/*').as('updateMission') diff --git a/frontend/cypress/e2e/side_window/mission_form/mission_dates_validation.spec.ts b/frontend/cypress/e2e/side_window/mission_form/mission_dates_validation.spec.ts index 00bb22ae2..17d6eda78 100644 --- a/frontend/cypress/e2e/side_window/mission_form/mission_dates_validation.spec.ts +++ b/frontend/cypress/e2e/side_window/mission_form/mission_dates_validation.spec.ts @@ -1,8 +1,28 @@ /// +import { setGeometry } from 'domain/shared_slices/Draw' + import { getFutureDate } from '../../utils/getFutureDate' import { getUtcDateInMultipleFormats } from '../../utils/getUtcDateInMultipleFormats' +import type { GeoJSON } from 'domain/types/GeoJSON' + +const dispatch = action => cy.window().its('store').invoke('dispatch', action) + +const surveillanceGeometry: GeoJSON.Geometry = { + coordinates: [ + [ + [ + [-5.445293386230469, 49.204467319852114], + [-6.05778117919922, 48.85600950618519], + [-5.67154308105469, 48.29540491855175], + [-5.010646779785157, 48.68245162584054], + [-5.445293386230469, 49.204467319852114] + ] + ] + ], + type: 'MultiPolygon' +} context('Side Window > Mission Form > Mission dates', () => { beforeEach(() => { cy.viewport(1280, 1024) @@ -37,6 +57,8 @@ context('Side Window > Mission Form > Mission dates', () => { // Add a surveillance cy.clickButton('Ajouter') cy.clickButton('Ajouter une surveillance') + cy.clickButton('Ajouter une zone de surveillance') + dispatch(setGeometry(surveillanceGeometry)) cy.getDataCy('envaction-theme-selector').click() cy.getDataCy('envaction-theme-element').contains('Espèce protégée').click() cy.getDataCy('envaction-subtheme-selector').click({ force: true }) @@ -208,6 +230,9 @@ context('Side Window > Mission Form > Mission dates', () => { cy.clickButton('Ajouter') cy.clickButton('Ajouter une surveillance') + cy.clickButton('Ajouter une zone de surveillance') + dispatch(setGeometry(surveillanceGeometry)) + cy.getDataCy('surveillance-open-by').type('ABC', { force: true }) cy.getDataCy('surveillance-duration-matches-mission').should('have.class', 'rs-checkbox-checked')