From 3788d6c608359d1f9d46925ab78bf91bfc423f24 Mon Sep 17 00:00:00 2001 From: Ivan Gabriele Date: Wed, 2 Oct 2024 17:23:44 +0200 Subject: [PATCH 1/2] Handle pno relations when predictedArrivalDatetimeUtc are far apart in Backend --- .../kotlin/fr/gouv/cnsp/monitorfish/Utils.kt | 18 ++ .../JpaLogbookReportRepository.kt | 27 ++- .../interfaces/DBLogbookReportRepository.kt | 7 - .../fr/gouv/cnsp/monitorfish/UtilsUTests.kt | 174 ++++++++++++++++++ 4 files changed, 215 insertions(+), 11 deletions(-) diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/Utils.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/Utils.kt index c1ea97ddc1..2672f38afd 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/Utils.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/Utils.kt @@ -1,5 +1,7 @@ package fr.gouv.cnsp.monitorfish +import java.time.ZonedDateTime + class Utils { companion object { /** @@ -16,5 +18,21 @@ class Utils { return normalizedLeftString == normalizedRightString } + + /** + * Checks if the ZonedDateTime is between the start and end times. + */ + fun isZonedDateTimeBetween( + zonedDateTime: ZonedDateTime, + start: ZonedDateTime, + end: ZonedDateTime, + isInclusive: Boolean = false, + ): Boolean { + return if (isInclusive) { + zonedDateTime >= start && zonedDateTime <= end + } else { + zonedDateTime > start && zonedDateTime < end + } + } } } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaLogbookReportRepository.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaLogbookReportRepository.kt index d7116aaaa2..59f364bd85 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaLogbookReportRepository.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaLogbookReportRepository.kt @@ -59,10 +59,8 @@ class JpaLogbookReportRepository( .toSet() return logbookReportsWithDatCorAndDel - .filter { report -> - // Exclude reports that are referenced by other reports or have a DEL operation type - report.operationType != LogbookOperationType.DEL && report.reportId !in referencedReportIds - } + // Exclude reports that are referenced by other reports or have a DEL operation type + .filter { it.operationType != LogbookOperationType.DEL && it.reportId !in referencedReportIds } .map { report -> val pno = PriorNotification.fromLogbookMessage(report.toLogbookMessage(objectMapper)) // All messages returned from the SQL query are acknowledged @@ -70,6 +68,27 @@ class JpaLogbookReportRepository( return@map pno } + // We filter by predicted arrival date here rather than in the SQL query + // because the DAT or COR predicted arrival dates can be far away from each other + // which is quite complicated to handle in pure SQL. + // + // Example: if a DAT that has a `predictedArrivalDatetimeUtc` on DAY 2 at 4pm + // is corrected by a COR with a `predictedArrivalDatetimeUtc` on DAY 1 at 4pm, + // filtering (in the SQL) between `willArriveAfter` = DAY 2 at 3pm and `willArriveBefore` = DAY 2 at 5pm + // would only return the DAT without including the related COR. + // + // /!\ This is not foolproof: + // A difference of more than 48h between DAT and COR `predictedArrivalDatetimeUtc` will still cause issues. + .filter { + it.logbookMessageAndValue.value.predictedArrivalDatetimeUtc?.let { predictedArrivalDatetimeUtc -> + Utils.isZonedDateTimeBetween( + predictedArrivalDatetimeUtc, + ZonedDateTime.parse(filter.willArriveAfter), + ZonedDateTime.parse(filter.willArriveBefore), + isInclusive = true, + ) + } == true + } } @Cacheable(value = ["pno_to_verify"]) diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/interfaces/DBLogbookReportRepository.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/interfaces/DBLogbookReportRepository.kt index c2c8cccf9a..b4bbac9124 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/interfaces/DBLogbookReportRepository.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/interfaces/DBLogbookReportRepository.kt @@ -59,12 +59,6 @@ interface DBLogbookReportRepository : unaccent(lower(lr.vessel_name)) ILIKE CONCAT('%', unaccent(lower(:searchQuery)), '%') OR lower(lr.cfr) ILIKE CONCAT('%', lower(:searchQuery), '%') ) - - -- Will Arrive After - AND lr.value->>'predictedArrivalDatetimeUtc' >= :willArriveAfter - - -- Will Arrive Before - AND lr.value->>'predictedArrivalDatetimeUtc' <= :willArriveBefore ), distinct_cfrs AS ( @@ -203,7 +197,6 @@ interface DBLogbookReportRepository : ), dels_targeting_searched_pno AS ( - -- A DEL message has no flag_state, which we need to acknowledge messages of non french vessels. -- So we use the flag_state of the deleted message. SELECT del.referenced_report_id, del.operation_number, searched_pno.flag_state diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/UtilsUTests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/UtilsUTests.kt index ef92df529e..527d68c7f3 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/UtilsUTests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/UtilsUTests.kt @@ -2,8 +2,12 @@ package fr.gouv.cnsp.monitorfish import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test +import java.time.ZonedDateTime class UtilsUTests { + private val defaultStart = ZonedDateTime.parse("2024-01-01T12:00:00Z") + private val defaultEnd = ZonedDateTime.parse("2024-01-01T14:00:00Z") + @Test fun `areStringsEqual should always return true when both strings are null or emptyish`() { assertThat(Utils.areStringsEqual("", "")).isTrue() @@ -24,4 +28,174 @@ class UtilsUTests { assertThat(Utils.areStringsEqual("", "test")).isFalse() assertThat(Utils.areStringsEqual(null, "test")).isFalse() } + + @Test + fun `isBetween Should return TRUE when CustomDateTime date is between start and end boundaries`() { + // Given + val start = ZonedDateTime.parse("2024-01-01T12:00:00Z") + val end = ZonedDateTime.parse("2024-01-01T14:00:00Z") + + // When + val result = Utils.isZonedDateTimeBetween(ZonedDateTime.parse("2024-01-01T13:00:00Z"), start, end) + + // Then + assertThat(result).isTrue + } + + @Test + fun `isBetween Should return FALSE when CustomDateTime date is outside the boundaries`() { + // When + val firstRresult = + Utils.isZonedDateTimeBetween(ZonedDateTime.parse("2024-01-01T11:00:00Z"), defaultStart, defaultEnd) + + // Then + assertThat(firstRresult).isFalse + + // When + val secondRresult = + Utils.isZonedDateTimeBetween(ZonedDateTime.parse("2024-01-01T15:00:00Z"), defaultStart, defaultEnd) + + // Then + assertThat(secondRresult).isFalse + } + + @Test + fun `isBetween Should return FALSE when CustomDateTime date is on the boundary and isInclusive is FALSE`() { + // Given + val isInclusive = false + + // When + val firstResult = Utils.isZonedDateTimeBetween( + ZonedDateTime.parse("2024-01-01T12:00:00Z"), + defaultStart, + defaultEnd, + isInclusive, + ) + + // Then + assertThat(firstResult).isFalse + + // When + val secondResult = Utils.isZonedDateTimeBetween( + ZonedDateTime.parse("2024-01-01T14:00:00Z"), + defaultStart, + defaultEnd, + isInclusive, + ) + + // Then + assertThat(secondResult).isFalse + } + + @Test + fun `isBetween Should return TRUE when CustomDateTime date is on the boundary and isInclusive is TRUE`() { + // Given + val isInclusive = true + + // When + val firstResult = Utils.isZonedDateTimeBetween( + ZonedDateTime.parse("2024-01-01T12:00:00Z"), + defaultStart, + defaultEnd, + isInclusive, + ) + + // Then + assertThat(firstResult).isTrue + + // When + val secondResult = Utils.isZonedDateTimeBetween( + ZonedDateTime.parse("2024-01-01T14:00:00Z"), + defaultStart, + defaultEnd, + isInclusive, + ) + + // Then + assertThat(secondResult).isTrue + } + + @Test + fun `isBetween Should handle inclusive and exclusive boundaries as expected when boundaries are equal`() { + // Given + val start = ZonedDateTime.parse("2024-01-01T12:00:00Z") + val end = ZonedDateTime.parse("2024-01-01T12:00:00Z") + var isInclusive = false + + // When + val firstResult = + Utils.isZonedDateTimeBetween(ZonedDateTime.parse("2024-01-01T12:00:00Z"), start, end, isInclusive) + + // Then + assertThat(firstResult).isFalse + + // Given + isInclusive = true + + // When + val secondResult = + Utils.isZonedDateTimeBetween(ZonedDateTime.parse("2024-01-01T12:00:00Z"), start, end, isInclusive) + + // Then + assertThat(secondResult).isTrue + } + + @Test + fun `isBetween Should handle different time zones correctly`() { + // Given + val start = ZonedDateTime.parse("2024-01-01T12:00:00+02:00") + val end = ZonedDateTime.parse("2024-01-01T14:00:00+02:00") + + // When + val firstResult = Utils.isZonedDateTimeBetween(ZonedDateTime.parse("2024-01-01T13:00:00+02:00"), start, end) + + // Then + assertThat(firstResult).isTrue + + // When + val secondResult = Utils.isZonedDateTimeBetween(ZonedDateTime.parse("2024-01-01T15:00:00+02:00"), start, end) + + // Then + assertThat(secondResult).isFalse + } + + @Test + fun `isBetween Should handle milliseconds correctly`() { + // When + val firstResult = + Utils.isZonedDateTimeBetween(ZonedDateTime.parse("2024-01-01T11:59:59.999Z"), defaultStart, defaultEnd) + + // Then + assertThat(firstResult).isFalse + + // When + val secondResult = + Utils.isZonedDateTimeBetween(ZonedDateTime.parse("2024-01-01T12:00:00.001Z"), defaultStart, defaultEnd) + + // Then + assertThat(secondResult).isTrue + + // When + val thirdResult = + Utils.isZonedDateTimeBetween(ZonedDateTime.parse("2024-01-01T13:59:59.999Z"), defaultStart, defaultEnd) + + // Then + assertThat(thirdResult).isTrue + + // When + val fourthResult = + Utils.isZonedDateTimeBetween(ZonedDateTime.parse("2024-01-01T14:00:00.001Z"), defaultStart, defaultEnd) + + // Then + assertThat(fourthResult).isFalse + } + + @Test + fun `isBetween Should return FALSE when start is after end`() { + // When + val result = Utils.isZonedDateTimeBetween(ZonedDateTime.parse("2024-01-01T13:00:00Z"), defaultEnd, defaultStart) + + // Then + assertThat(result).isFalse + } } From b68d905a3cd95082a89ce39dadffe4486c81cf3b Mon Sep 17 00:00:00 2001 From: Ivan Gabriele Date: Wed, 2 Oct 2024 17:24:42 +0200 Subject: [PATCH 2/2] Add non-reg inte test for DAT / COR far away predictedArrivalDatetimeUtc --- .../testdata/V666.2__Insert_dummy_vessels.sql | 2 + ...6.5.1__Insert_more_pno_logbook_reports.sql | 16 +++ .../json/V666.2__Insert_dummy_vessels.jsonc | 13 ++ ...5.1__Insert_more_pno_logbook_reports.jsonc | 128 +++++++++++++++++- .../fr/gouv/cnsp/monitorfish/UtilsUTests.kt | 52 +++---- .../JpaLogbookReportRepositoryITests.kt | 64 +++++++-- 6 files changed, 235 insertions(+), 40 deletions(-) diff --git a/backend/src/main/resources/db/testdata/V666.2__Insert_dummy_vessels.sql b/backend/src/main/resources/db/testdata/V666.2__Insert_dummy_vessels.sql index d81c7acd6a..646b1f50f1 100644 --- a/backend/src/main/resources/db/testdata/V666.2__Insert_dummy_vessels.sql +++ b/backend/src/main/resources/db/testdata/V666.2__Insert_dummy_vessels.sql @@ -84,3 +84,5 @@ INSERT INTO vessels (id, cfr, mmsi, ircs, external_immatriculation, vessel_name, INSERT INTO vessels (id, cfr, mmsi, ircs, external_immatriculation, vessel_name, flag_state, length, under_charter) VALUES (128, 'CFR128', 'MMSI128', 'IRCS128', 'EXTIMM128', 'THE FLOATING KANGAROO', 'AU', 31, false); INSERT INTO vessels (id, cfr, mmsi, ircs, external_immatriculation, vessel_name, flag_state, length, under_charter) VALUES (129, 'CFR129', 'MMSI129', 'IRCS129', 'EXTIMM129', 'BON VENT', 'FR', 34.5, false); + +INSERT INTO vessels (id, cfr, mmsi, ircs, external_immatriculation, vessel_name, flag_state, length, under_charter) VALUES (130, 'CFR130', 'MMSI130', 'IRCS130', 'EXTIMM130', 'L''HIPPO CAMPE', 'FR', 19.2, false); diff --git a/backend/src/main/resources/db/testdata/V666.5.1__Insert_more_pno_logbook_reports.sql b/backend/src/main/resources/db/testdata/V666.5.1__Insert_more_pno_logbook_reports.sql index 628fd84a1c..f6883f2b24 100644 --- a/backend/src/main/resources/db/testdata/V666.5.1__Insert_more_pno_logbook_reports.sql +++ b/backend/src/main/resources/db/testdata/V666.5.1__Insert_more_pno_logbook_reports.sql @@ -93,6 +93,14 @@ INSERT INTO logbook_raw_messages (operation_number, xml_message) VALUES ('FAKE_O INSERT INTO logbook_raw_messages (operation_number, xml_message) VALUES ('FAKE_OPERATION_121_RET', 'Message FLUX xml'); +INSERT INTO logbook_raw_messages (operation_number, xml_message) VALUES ('FAKE_OPERATION_122', 'Message FLUX xml'); + +INSERT INTO logbook_raw_messages (operation_number, xml_message) VALUES ('FAKE_OPERATION_122_RET', 'Message FLUX xml'); + +INSERT INTO logbook_raw_messages (operation_number, xml_message) VALUES ('FAKE_OPERATION_122_COR', 'Message FLUX xml'); + +INSERT INTO logbook_raw_messages (operation_number, xml_message) VALUES ('FAKE_OPERATION_122_COR_RET', 'Message FLUX xml'); + INSERT INTO logbook_reports (id, report_id, referenced_report_id, cfr, enriched, flag_state, integration_datetime_utc, log_type, operation_datetime_utc, operation_number, operation_type, report_datetime_utc, software, transmission_format, vessel_name, trip_gears, trip_segments, value) VALUES (101, 'FAKE_OPERATION_101', NULL, 'FAK000999999', true, 'FRA', NOW() AT TIME ZONE 'UTC' - INTERVAL '15 minutes', 'PNO', NOW() AT TIME ZONE 'UTC' - INTERVAL '15 minutes', 'FAKE_OPERATION_101', 'DAT', NOW() AT TIME ZONE 'UTC' - INTERVAL '15 minutes', 'JT/VISIOCaptures V1.4.7', 'ERS', 'PHENOMENE', '[{"gear":"TBN","mesh":100,"dimensions":"250;180"},{"gear":"OTT","mesh":120.5,"dimensions":"250;280"}]', '[{"segment":"SWW04","segmentName":"Chaluts pélagiques"},{"segment":"SWW06","segmentName":"Sennes"}]', '{"riskFactor":2.1,"catchOnboard":[{"weight":25,"nbFish":null,"species":"COD","faoZone":"27.8.a","effortZone":"C","economicZone":"FRA","statisticalRectangle":"23E6"}],"isBeingSent":false,"isInVerificationScope":false,"isSent":false,"isVerified":false,"pnoTypes":[{"pnoTypeName":"Préavis type A","minimumNotificationPeriod":4,"hasDesignatedPorts":false},{"pnoTypeName":"Préavis type B","minimumNotificationPeriod":8,"hasDesignatedPorts":true}],"port":"FRSML","predictedArrivalDatetimeUtc":null,"predictedLandingDatetimeUtc":null,"purpose":"LAN","tripStartDate":null}'); UPDATE logbook_reports SET value = JSONB_SET(value, '{predictedArrivalDatetimeUtc}', TO_JSONB(TO_CHAR(NOW() AT TIME ZONE 'UTC' + INTERVAL '3 hours', 'YYYY-MM-DD"T"HH24:MI:SS"Z"')), true) WHERE id = 101; UPDATE logbook_reports SET value = JSONB_SET(value, '{predictedLandingDatetimeUtc}', TO_JSONB(TO_CHAR(NOW() AT TIME ZONE 'UTC' + INTERVAL '3.5 hours', 'YYYY-MM-DD"T"HH24:MI:SS"Z"')), true) WHERE id = 101; @@ -241,3 +249,11 @@ INSERT INTO logbook_reports (id, report_id, referenced_report_id, integration_da INSERT INTO logbook_reports (id, report_id, referenced_report_id, cfr, enriched, flag_state, integration_datetime_utc, log_type, operation_datetime_utc, operation_number, operation_type, report_datetime_utc, software, transmission_format, trip_gears, trip_segments, vessel_name, value) VALUES (121, 'FAKE_OPERATION_121', NULL, 'ABC000180832', true, 'FRA', NOW() - INTERVAL '15 minutes', 'DEP', NOW() - INTERVAL '15 minutes', 'FAKE_OPERATION_121', 'DAT', NOW() - INTERVAL '15 minutes', 'TurboCatch (3.7-1)', 'ERS', NULL, NULL, 'MARIAGE ÎLE HASARD', '{"gearOnboard":[{"gear":"GTR","mesh":100}],"departurePort":"AEJAZ","anticipatedActivity":"FSH","tripStartDate":"NOW() - INTERVAL ''15 minutes''","departureDatetimeUtc":"NOW() - INTERVAL ''15 minutes''"}'); INSERT INTO logbook_reports (id, report_id, referenced_report_id, integration_datetime_utc, operation_datetime_utc, operation_number, operation_type, transmission_format, value) VALUES (1120, NULL, 'FAKE_OPERATION_121', NOW() - INTERVAL '14 minutes', NOW() - INTERVAL '14 minutes', 'FAKE_OPERATION_121_RET', 'RET', 'ERS', '{"returnStatus":"000"}'); + +INSERT INTO logbook_reports (id, report_id, referenced_report_id, cfr, enriched, flag_state, integration_datetime_utc, log_type, operation_datetime_utc, operation_number, operation_type, report_datetime_utc, software, transmission_format, trip_gears, trip_segments, vessel_name, value) VALUES (122, 'FAKE_OPERATION_122', NULL, 'CFR130', true, 'FRA', '2024-09-01 13:00:00', 'PNO', '2024-09-01 13:00:00', 'FAKE_OPERATION_122', 'DAT', '2024-09-01 13:00:00', 'TurboCatch (3.7-1)', 'ERS', NULL, NULL, 'L''HIPPO CAMPE', '{"riskFactor":2.9,"catchOnboard":[{"weight":150,"nbFish":null,"species":"ANF","faoZone":"27.8.a","effortZone":"C","economicZone":"FRA","statisticalRectangle":"23E6"}],"pnoTypes":[{"pnoTypeName":"Préavis type Z","minimumNotificationPeriod":4,"hasDesignatedPorts":false}],"port":"BROIA","predictedArrivalDatetimeUtc":"2024-09-02T21:00:00Z","predictedLandingDatetimeUtc":"2024-09-02T21:30:00Z","purpose":"LAN","tripStartDate":"2024-09-01T06:00:00Z"}'); + +INSERT INTO logbook_reports (id, report_id, referenced_report_id, integration_datetime_utc, operation_datetime_utc, operation_number, operation_type, transmission_format, value) VALUES (1122, NULL, 'FAKE_OPERATION_122', '2024-09-01 13:05:00', '2024-09-01 13:05:00', 'FAKE_OPERATION_122_RET', 'RET', 'ERS', '{"returnStatus":"000"}'); + +INSERT INTO logbook_reports (id, report_id, referenced_report_id, cfr, enriched, flag_state, integration_datetime_utc, log_type, operation_datetime_utc, operation_number, operation_type, report_datetime_utc, software, transmission_format, trip_gears, trip_segments, vessel_name, value) VALUES (2122, 'FAKE_OPERATION_122_COR', 'FAKE_OPERATION_122', 'CFR130', true, 'FRA', '2024-09-01 13:20:00', 'PNO', '2024-09-01 13:20:00', 'FAKE_OPERATION_122', 'COR', '2024-09-01 13:20:00', 'TurboCatch (3.7-1)', 'ERS', NULL, NULL, 'L''HIPPO CAMPE', '{"riskFactor":2.9,"catchOnboard":[{"weight":150,"nbFish":null,"species":"ANF","faoZone":"27.8.a","effortZone":"C","economicZone":"FRA","statisticalRectangle":"23E6"}],"pnoTypes":[{"pnoTypeName":"Préavis type Z","minimumNotificationPeriod":4,"hasDesignatedPorts":false}],"port":"BROIA","predictedArrivalDatetimeUtc":"2024-09-01T15:00:00Z","predictedLandingDatetimeUtc":"2024-09-01T15:30:00Z","purpose":"LAN","tripStartDate":"2024-09-01T06:00:00Z"}'); + +INSERT INTO logbook_reports (id, report_id, referenced_report_id, integration_datetime_utc, operation_datetime_utc, operation_number, operation_type, transmission_format, value) VALUES (3122, NULL, 'FAKE_OPERATION_122_COR', '2024-09-01 13:25:00', '2024-09-01 13:25:00', 'FAKE_OPERATION_122_COR_RET', 'RET', 'ERS', '{"returnStatus":"000"}'); diff --git a/backend/src/main/resources/db/testdata/json/V666.2__Insert_dummy_vessels.jsonc b/backend/src/main/resources/db/testdata/json/V666.2__Insert_dummy_vessels.jsonc index 221866afd4..480565df23 100644 --- a/backend/src/main/resources/db/testdata/json/V666.2__Insert_dummy_vessels.jsonc +++ b/backend/src/main/resources/db/testdata/json/V666.2__Insert_dummy_vessels.jsonc @@ -825,6 +825,19 @@ "flag_state": "FR", "length": 34.5, "under_charter": false + }, + + // - Vessel: L'HIPPO CAMPE + { + "id": 130, + "cfr": "CFR130", + "mmsi": "MMSI130", + "ircs": "IRCS130", + "external_immatriculation": "EXTIMM130", + "vessel_name": "L'HIPPO CAMPE", + "flag_state": "FR", + "length": 19.2, + "under_charter": false } ] } diff --git a/backend/src/main/resources/db/testdata/json/V666.5.1__Insert_more_pno_logbook_reports.jsonc b/backend/src/main/resources/db/testdata/json/V666.5.1__Insert_more_pno_logbook_reports.jsonc index 8f7d2797fe..9e6889b8a6 100644 --- a/backend/src/main/resources/db/testdata/json/V666.5.1__Insert_more_pno_logbook_reports.jsonc +++ b/backend/src/main/resources/db/testdata/json/V666.5.1__Insert_more_pno_logbook_reports.jsonc @@ -47,7 +47,11 @@ { "operation_number": "FAKE_OPERATION_120", "xml_message": "Message FLUX xml" }, { "operation_number": "FAKE_OPERATION_121", "xml_message": "Message FLUX xml" }, { "operation_number": "FAKE_OPERATION_120_RET", "xml_message": "Message FLUX xml" }, - { "operation_number": "FAKE_OPERATION_121_RET", "xml_message": "Message FLUX xml" } + { "operation_number": "FAKE_OPERATION_121_RET", "xml_message": "Message FLUX xml" }, + { "operation_number": "FAKE_OPERATION_122", "xml_message": "Message FLUX xml" }, + { "operation_number": "FAKE_OPERATION_122_RET", "xml_message": "Message FLUX xml" }, + { "operation_number": "FAKE_OPERATION_122_COR", "xml_message": "Message FLUX xml" }, + { "operation_number": "FAKE_OPERATION_122_COR_RET", "xml_message": "Message FLUX xml" } ] }, { @@ -1528,7 +1532,7 @@ "trip_segments": null, "vessel_name": "MARIAGE ÎLE HASARD", "value:jsonb": { - "gearOnboard": [{"gear": "GTR", "mesh": 100.0}], + "gearOnboard": [{ "gear": "GTR", "mesh": 100.0 }], "departurePort": "AEJAZ", "anticipatedActivity": "FSH", "tripStartDate": "NOW() - INTERVAL '15 minutes'", @@ -1547,6 +1551,126 @@ "value:jsonb": { "returnStatus": "000" } + }, + + // - Vessel: L'HIPPO CAMPE + // - Flag state: FR + // - DAT with a predicted arrival date 30h after the COR predicted arrival + { + "id": 122, + "report_id": "FAKE_OPERATION_122", + "referenced_report_id": null, + "cfr": "CFR130", + "enriched": true, + "flag_state": "FRA", + "integration_datetime_utc": "2024-09-01 13:00:00", + "log_type": "PNO", + "operation_datetime_utc": "2024-09-01 13:00:00", + "operation_number": "FAKE_OPERATION_122", + "operation_type": "DAT", + "report_datetime_utc": "2024-09-01 13:00:00", + "software": "TurboCatch (3.7-1)", + "transmission_format": "ERS", + "trip_gears": null, + "trip_segments": null, + "vessel_name": "L'HIPPO CAMPE", + "value:jsonb": { + "riskFactor": 2.9, + "catchOnboard": [ + { + "weight": 150.0, + "nbFish": null, + "species": "ANF", + "faoZone": "27.8.a", + "effortZone": "C", + "economicZone": "FRA", + "statisticalRectangle": "23E6" + } + ], + "pnoTypes": [ + { + "pnoTypeName": "Préavis type Z", + "minimumNotificationPeriod": 4.0, + "hasDesignatedPorts": false + } + ], + "port": "BROIA", + "predictedArrivalDatetimeUtc": "2024-09-02T21:00:00Z", + "predictedLandingDatetimeUtc": "2024-09-02T21:30:00Z", + "purpose": "LAN", + "tripStartDate": "2024-09-01T06:00:00Z" + } + }, + { + "id": 1122, + "report_id": null, + "referenced_report_id": "FAKE_OPERATION_122", + "integration_datetime_utc": "2024-09-01 13:05:00", + "operation_datetime_utc": "2024-09-01 13:05:00", + "operation_number": "FAKE_OPERATION_122_RET", + "operation_type": "RET", + "transmission_format": "ERS", + "value:jsonb": { + "returnStatus": "000" + } + }, + { + "id": 2122, + "report_id": "FAKE_OPERATION_122_COR", + "referenced_report_id": "FAKE_OPERATION_122", + "cfr": "CFR130", + "enriched": true, + "flag_state": "FRA", + "integration_datetime_utc": "2024-09-01 13:20:00", + "log_type": "PNO", + "operation_datetime_utc": "2024-09-01 13:20:00", + "operation_number": "FAKE_OPERATION_122", + "operation_type": "COR", + "report_datetime_utc": "2024-09-01 13:20:00", + "software": "TurboCatch (3.7-1)", + "transmission_format": "ERS", + "trip_gears": null, + "trip_segments": null, + "vessel_name": "L'HIPPO CAMPE", + "value:jsonb": { + "riskFactor": 2.9, + "catchOnboard": [ + { + "weight": 150.0, + "nbFish": null, + "species": "ANF", + "faoZone": "27.8.a", + "effortZone": "C", + "economicZone": "FRA", + "statisticalRectangle": "23E6" + } + ], + "pnoTypes": [ + { + "pnoTypeName": "Préavis type Z", + "minimumNotificationPeriod": 4.0, + "hasDesignatedPorts": false + } + ], + "port": "BROIA", + "predictedArrivalDatetimeUtc": "2024-09-01T15:00:00Z", + "predictedLandingDatetimeUtc": "2024-09-01T15:30:00Z", + "purpose": "LAN", + "tripStartDate": "2024-09-01T06:00:00Z" + } + }, + { + "id": 3122, + "report_id": null, + "referenced_report_id": "FAKE_OPERATION_122_COR", + "integration_datetime_utc": "2024-09-01 13:25:00", + "operation_datetime_utc": "2024-09-01 13:25:00", + "operation_number": "FAKE_OPERATION_122_COR_RET", + "operation_type": "RET", + "transmission_format": "ERS", + "value:jsonb": { + "returnStatus": "000" + } } ] } diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/UtilsUTests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/UtilsUTests.kt index 527d68c7f3..b51ff7b11b 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/UtilsUTests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/UtilsUTests.kt @@ -65,23 +65,25 @@ class UtilsUTests { val isInclusive = false // When - val firstResult = Utils.isZonedDateTimeBetween( - ZonedDateTime.parse("2024-01-01T12:00:00Z"), - defaultStart, - defaultEnd, - isInclusive, - ) + val firstResult = + Utils.isZonedDateTimeBetween( + ZonedDateTime.parse("2024-01-01T12:00:00Z"), + defaultStart, + defaultEnd, + isInclusive, + ) // Then assertThat(firstResult).isFalse // When - val secondResult = Utils.isZonedDateTimeBetween( - ZonedDateTime.parse("2024-01-01T14:00:00Z"), - defaultStart, - defaultEnd, - isInclusive, - ) + val secondResult = + Utils.isZonedDateTimeBetween( + ZonedDateTime.parse("2024-01-01T14:00:00Z"), + defaultStart, + defaultEnd, + isInclusive, + ) // Then assertThat(secondResult).isFalse @@ -93,23 +95,25 @@ class UtilsUTests { val isInclusive = true // When - val firstResult = Utils.isZonedDateTimeBetween( - ZonedDateTime.parse("2024-01-01T12:00:00Z"), - defaultStart, - defaultEnd, - isInclusive, - ) + val firstResult = + Utils.isZonedDateTimeBetween( + ZonedDateTime.parse("2024-01-01T12:00:00Z"), + defaultStart, + defaultEnd, + isInclusive, + ) // Then assertThat(firstResult).isTrue // When - val secondResult = Utils.isZonedDateTimeBetween( - ZonedDateTime.parse("2024-01-01T14:00:00Z"), - defaultStart, - defaultEnd, - isInclusive, - ) + val secondResult = + Utils.isZonedDateTimeBetween( + ZonedDateTime.parse("2024-01-01T14:00:00Z"), + defaultStart, + defaultEnd, + isInclusive, + ) // Then assertThat(secondResult).isTrue diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaLogbookReportRepositoryITests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaLogbookReportRepositoryITests.kt index eda93c2083..f157c0fc19 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaLogbookReportRepositoryITests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaLogbookReportRepositoryITests.kt @@ -540,7 +540,7 @@ class JpaLogbookReportRepositoryITests : AbstractDBTests() { @Test @Transactional - fun `findAllPriorNotifications Should return PNO logbook reports from ESP & FRA vessels`() { + fun `findAllAcknowledgedPriorNotifications Should return PNO logbook reports from ESP & FRA vessels`() { // Given val filter = PriorNotificationsFilter( @@ -566,7 +566,7 @@ class JpaLogbookReportRepositoryITests : AbstractDBTests() { @Test @Transactional - fun `findAllPriorNotifications Should return PNO logbook reports with or without reportings`() { + fun `findAllAcknowledgedPriorNotifications Should return PNO logbook reports with or without reportings`() { val expectedLogbookReportIdsWithOneOrMoreReportings = listOf(102L, 104L) // Given @@ -610,7 +610,7 @@ class JpaLogbookReportRepositoryITests : AbstractDBTests() { @Test @Transactional - fun `findAllPriorNotifications Should return PNO logbook reports for less or more than 12 meters long vessels`() { + fun `findAllAcknowledgedPriorNotifications Should return PNO logbook reports for less or more than 12 meters long vessels`() { // Given val firstFilter = PriorNotificationsFilter( @@ -658,7 +658,7 @@ class JpaLogbookReportRepositoryITests : AbstractDBTests() { @Test @Transactional - fun `findAllPriorNotifications Should return PNO logbook reports for vessels controlled after or before January 1st, 2024`() { + fun `findAllAcknowledgedPriorNotifications Should return PNO logbook reports for vessels controlled after or before January 1st, 2024`() { // Given val firstFilter = PriorNotificationsFilter( @@ -714,7 +714,7 @@ class JpaLogbookReportRepositoryITests : AbstractDBTests() { @Test @Transactional - fun `findAllPriorNotifications Should return PNO logbook reports for FRSML & FRVNE ports`() { + fun `findAllAcknowledgedPriorNotifications Should return PNO logbook reports for FRSML & FRVNE ports`() { // Given val filter = PriorNotificationsFilter( @@ -737,7 +737,7 @@ class JpaLogbookReportRepositoryITests : AbstractDBTests() { @Test @Transactional - fun `findAllPriorNotifications Should return PNO logbook reports When using a vessel name`() { + fun `findAllAcknowledgedPriorNotifications Should return PNO logbook reports When using a vessel name`() { // Given val firstFilter = PriorNotificationsFilter( @@ -785,7 +785,7 @@ class JpaLogbookReportRepositoryITests : AbstractDBTests() { @Test @Transactional - fun `findAllPriorNotifications Should return PNO logbook reports When using a CFR`() { + fun `findAllAcknowledgedPriorNotifications Should return PNO logbook reports When using a CFR`() { // Given val firstFilter = PriorNotificationsFilter( @@ -833,7 +833,7 @@ class JpaLogbookReportRepositoryITests : AbstractDBTests() { @Test @Transactional - fun `findAllPriorNotifications Should return PNO logbook reports for COD & HKE species`() { + fun `findAllAcknowledgedPriorNotifications Should return PNO logbook reports for COD & HKE species`() { // Given val filter = PriorNotificationsFilter( @@ -857,7 +857,7 @@ class JpaLogbookReportRepositoryITests : AbstractDBTests() { @Test @Transactional - fun `findAllPriorNotifications Should return PNO logbook reports for Préavis type A & Préavis type C types`() { + fun `findAllAcknowledgedPriorNotifications Should return PNO logbook reports for Préavis type A & Préavis type C types`() { // Given val filter = PriorNotificationsFilter( @@ -881,7 +881,7 @@ class JpaLogbookReportRepositoryITests : AbstractDBTests() { @Test @Transactional - fun `findAllPriorNotifications Should return PNO logbook reports for SWW06 & NWW03 segments`() { + fun `findAllAcknowledgedPriorNotifications Should return PNO logbook reports for SWW06 & NWW03 segments`() { // Given val filter = PriorNotificationsFilter( @@ -909,7 +909,7 @@ class JpaLogbookReportRepositoryITests : AbstractDBTests() { @Test @Transactional - fun `findAllPriorNotifications Should return PNO logbook reports for OTT & TB gears`() { + fun `findAllAcknowledgedPriorNotifications Should return PNO logbook reports for OTT & TB gears`() { // Given val filter = PriorNotificationsFilter( @@ -933,7 +933,7 @@ class JpaLogbookReportRepositoryITests : AbstractDBTests() { @Test @Transactional - fun `findAllPriorNotifications Should return PNO logbook reports for vessels arriving after or before January 1st, 2024`() { + fun `findAllAcknowledgedPriorNotifications Should return PNO logbook reports for vessels arriving after or before January 1st, 2024`() { // Given val firstFilter = PriorNotificationsFilter( @@ -975,7 +975,7 @@ class JpaLogbookReportRepositoryITests : AbstractDBTests() { @Test @Transactional - fun `findAllPriorNotifications Should return the expected PNO logbook reports with multiple filters`() { + fun `findAllAcknowledgedPriorNotifications Should return the expected PNO logbook reports with multiple filters`() { // Given val filter = PriorNotificationsFilter( @@ -1010,9 +1010,45 @@ class JpaLogbookReportRepositoryITests : AbstractDBTests() { ).isTrue() } + // Non-regression test @Test @Transactional - fun `findPriorNotificationsToVerify Should return logbook reports PNO to verify`() { + fun `findAllAcknowledgedPriorNotifications Should return the expected result with a COR predicted arrival date far away from the DAT one`() { + // FAKE_OPERATION_122 `predictedArrivalDatetimeUtc` is 2024-09-02T21:00:00Z + // FAKE_OPERATION_122_COR `predictedArrivalDatetimeUtc` is 2024-09-01T15:00:00Z + + // Given + val firstFilter = + PriorNotificationsFilter( + willArriveAfter = "2024-09-01T13:00:00Z", + willArriveBefore = "2024-09-01T17:00:00Z", + ) + + // When + val firstResult = jpaLogbookReportRepository.findAllAcknowledgedPriorNotifications(firstFilter) + + // Then + assertThat(firstResult).hasSize(1) + val firstResultPriorNotification = firstResult.first() + assertThat(firstResultPriorNotification.reportId).isEqualTo("FAKE_OPERATION_122_COR") + + // Given + val secondFilter = + PriorNotificationsFilter( + willArriveAfter = "2024-09-02T19:00:00Z", + willArriveBefore = "2024-09-02T23:00:00Z", + ) + + // When + val secondResult = jpaLogbookReportRepository.findAllAcknowledgedPriorNotifications(secondFilter) + + // Then + assertThat(secondResult).isEmpty() + } + + @Test + @Transactional + fun `findAllPriorNotificationsToVerify Should return logbook reports PNO to verify`() { // When val result = jpaLogbookReportRepository.findAllPriorNotificationsToVerify()