From d3d0322942f882dafd657b2fea5fa295c6e01a8c Mon Sep 17 00:00:00 2001 From: Martin Kaeser Date: Thu, 20 Jul 2023 12:42:20 +0200 Subject: [PATCH 1/3] feat (bridge): Bridge tests for recorded sync state --- .../catenax/bpdm/bridge/dummy/SyncStateIT.kt | 246 ++++++++++++++++++ .../bpdm/bridge/dummy/util/DbTestHelpers.kt | 54 ++++ 2 files changed, 300 insertions(+) create mode 100644 bpdm-bridge-dummy/src/test/kotlin/com/catenax/bpdm/bridge/dummy/SyncStateIT.kt create mode 100644 bpdm-bridge-dummy/src/test/kotlin/com/catenax/bpdm/bridge/dummy/util/DbTestHelpers.kt diff --git a/bpdm-bridge-dummy/src/test/kotlin/com/catenax/bpdm/bridge/dummy/SyncStateIT.kt b/bpdm-bridge-dummy/src/test/kotlin/com/catenax/bpdm/bridge/dummy/SyncStateIT.kt new file mode 100644 index 000000000..88be0e0ec --- /dev/null +++ b/bpdm-bridge-dummy/src/test/kotlin/com/catenax/bpdm/bridge/dummy/SyncStateIT.kt @@ -0,0 +1,246 @@ +/******************************************************************************* + * Copyright (c) 2021,2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package com.catenax.bpdm.bridge.dummy + +import com.catenax.bpdm.bridge.dummy.service.SyncService +import com.catenax.bpdm.bridge.dummy.util.DbTestHelpers +import com.catenax.bpdm.bridge.dummy.util.PostgreSQLContextInitializer +import com.fasterxml.jackson.databind.ObjectMapper +import com.github.tomakehurst.wiremock.client.WireMock +import com.github.tomakehurst.wiremock.core.WireMockConfiguration +import com.github.tomakehurst.wiremock.junit5.WireMockExtension +import com.github.tomakehurst.wiremock.verification.LoggedRequest +import org.assertj.core.api.Assertions +import org.eclipse.tractusx.bpdm.common.service.BaseSyncRecordService +import org.eclipse.tractusx.bpdm.gate.api.model.request.ChangeLogSearchRequest +import org.eclipse.tractusx.bpdm.gate.api.model.response.ChangelogGateDto +import org.eclipse.tractusx.bpdm.gate.api.model.response.PageChangeLogDto +import org.junit.jupiter.api.Assertions.assertThrows +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.context.ActiveProfiles +import org.springframework.test.context.ContextConfiguration +import org.springframework.test.context.DynamicPropertyRegistry +import org.springframework.test.context.DynamicPropertySource +import org.springframework.web.reactive.function.client.WebClientResponseException +import java.time.Instant + +val TS_INITIAL_POLL_FROM: Instant = BaseSyncRecordService.syncStartTime + +@SpringBootTest( + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = [Application::class] +) +@ActiveProfiles("test") +@ContextConfiguration(initializers = [PostgreSQLContextInitializer::class]) +class SyncStateIT @Autowired constructor( + val syncService: SyncService, + val testHelpers: DbTestHelpers, + val objectMapper: ObjectMapper +) { + + companion object { + const val GATE_GET_INPUT_CHANGELOG_PATH = "/api/catena/input/changelog/search" + + @JvmField + @RegisterExtension + val gateWireMockServer: WireMockExtension = WireMockExtension.newInstance() + .options(WireMockConfiguration.wireMockConfig().dynamicPort()) + .build() + + @JvmStatic + @DynamicPropertySource + fun properties(registry: DynamicPropertyRegistry) { + registry.add("bpdm.gate.base-url") { gateWireMockServer.baseUrl() } + } + } + + @BeforeEach + fun beforeEach() { + testHelpers.truncateDbTables() + + gateWireMockServer.resetAll() + } + + /** + * When a sync is successful, + * Then for the next sync the bridge polls for changes only after the time the last sync started. + */ + @Test + fun `successful sync`() { + // all 3 syncs successful + + val responseGateChangelog: PageChangeLogDto = + PageChangeLogDto( + totalElements = 0, + totalPages = 0, + page = 0, + contentSize = 0, + content = listOf(), + invalidEntries = 0, + errors = listOf() + ) + + // Gate changelog endpoint returns okay with no changes + gateWireMockServer.stubFor( + WireMock.post(WireMock.urlPathEqualTo(GATE_GET_INPUT_CHANGELOG_PATH)) + .willReturn( + WireMock.okJson(objectMapper.writeValueAsString(responseGateChangelog)) + ) + ) + + val tsBefore1stSuccessfulSync = Instant.now() + syncService.sync() + val tsAfter1stSuccessfulSync = Instant.now() + + val tsBefore2ndSuccessfulSync = Instant.now() + syncService.sync() + val tsAfter2ndSuccessfulSync = Instant.now() + + syncService.sync() + + val loggedRequests = gateWireMockServer.findAll( + WireMock.postRequestedFor(WireMock.urlPathEqualTo(GATE_GET_INPUT_CHANGELOG_PATH)) + ) + + Assertions.assertThat(loggedRequests.size).isEqualTo(3) + + // 1st sync polls from initial timestamp (2000-01-01) + val pollFrom1stSync = parseBody(loggedRequests[0]).fromTime + Assertions.assertThat(pollFrom1stSync).isEqualTo(TS_INITIAL_POLL_FROM) + + // 2nd sync polls from around timestamp of 1st successful sync + val pollFrom2ndSync = parseBody(loggedRequests[1]).fromTime + Assertions.assertThat(pollFrom2ndSync).isBetween(tsBefore1stSuccessfulSync, tsAfter1stSuccessfulSync) + + // 3rd sync polls from around timestamp of 2nd successful sync + val pollFrom3rdSync = parseBody(loggedRequests[2]).fromTime + Assertions.assertThat(pollFrom3rdSync).isBetween(tsBefore2ndSuccessfulSync, tsAfter2ndSuccessfulSync) + } + + /** + * When a sync fails, + * Then for the next sync the bridge polls for changes after the exact same time as for the last sync. + */ + @Test + fun `sync with errors`() { + // 2nd & 3rd sync fail; 1st, 4th, 5th sync successful + + val responseGateChangelog: PageChangeLogDto = + PageChangeLogDto( + totalElements = 0, + totalPages = 0, + page = 0, + contentSize = 0, + content = listOf(), + invalidEntries = 0, + errors = listOf() + ) + + // Gate changelog endpoint returns okay with no changes + gateWireMockServer.stubFor( + WireMock.post(WireMock.urlPathEqualTo(GATE_GET_INPUT_CHANGELOG_PATH)) + .willReturn( + WireMock.okJson(objectMapper.writeValueAsString(responseGateChangelog)) + ) + ) + + // 1st sync is successful + val tsBefore1stSuccessfulSync = Instant.now() + syncService.sync() + val tsAfter1stSuccessfulSync = Instant.now() + + // Gate changelog endpoint returns error code + gateWireMockServer.stubFor( + WireMock.post(WireMock.urlPathEqualTo(GATE_GET_INPUT_CHANGELOG_PATH)) + .willReturn( + WireMock.serverError() + ) + ) + + // 2nd sync fails + val tsBeforeFailedSync = Instant.now() + assertThrows(WebClientResponseException.InternalServerError::class.java) { + syncService.sync() + } + val tsAfterFailedSync = Instant.now() + + // 3rd sync fails + assertThrows(WebClientResponseException.InternalServerError::class.java) { + syncService.sync() + } + + // Gate changelog endpoint again returns okay with no changes + gateWireMockServer.stubFor( + WireMock.post(WireMock.urlPathEqualTo(GATE_GET_INPUT_CHANGELOG_PATH)) + .willReturn( + WireMock.okJson(objectMapper.writeValueAsString(responseGateChangelog)) + ) + ) + + // 4th sync successful + val tsBefore2ndSuccessfulSync = Instant.now() + syncService.sync() + val tsAfter2ndSuccessfulSync = Instant.now() + + // 5th sync successful + syncService.sync() + + val loggedRequests = gateWireMockServer.findAll( + WireMock.postRequestedFor(WireMock.urlPathEqualTo(GATE_GET_INPUT_CHANGELOG_PATH)) + ) + + // 5 sync requests -> changelog endpoint was polled 5 times + Assertions.assertThat(loggedRequests.size).isEqualTo(5) + + // 1st sync polls from initial timestamp (2000-01-01) + val pollFrom1stSync = parseBody(loggedRequests[0]).fromTime + Assertions.assertThat(pollFrom1stSync).isEqualTo(TS_INITIAL_POLL_FROM) + + // 2nd sync polls from around timestamp of 1st successful sync + val pollFrom2ndSync = parseBody(loggedRequests[1]).fromTime + Assertions.assertThat(pollFrom2ndSync).isBetween(tsBefore1stSuccessfulSync, tsAfter1stSuccessfulSync) + + // 3rd sync still polls from same timestamp because last sync has failed! + val pollFrom3rdSync = parseBody(loggedRequests[2]).fromTime + Assertions.assertThat(pollFrom3rdSync).isEqualTo(pollFrom2ndSync) + + // 4th sync still polls from same timestamp because last sync has failed! + val pollFrom4thSync = parseBody(loggedRequests[3]).fromTime + Assertions.assertThat(pollFrom4thSync).isEqualTo(pollFrom2ndSync) + + // 5th sync polls from around timestamp of 2nd successful sync + val pollFrom5thSync = parseBody(loggedRequests[4]).fromTime + // TODO Didn't expect this! + // If there is one or more sync errors followed by a successful sync, the poll-from timestamp is updated to the start-time of the first failed sync, + // not of the successful sync! +// Assertions.assertThat(pollFrom5thSync).isBetween(tsBefore2ndSuccessfulSync, tsAfter2ndSuccessfulSync) + Assertions.assertThat(pollFrom5thSync).isBetween(tsBeforeFailedSync, tsAfterFailedSync) + } + + private inline fun parseBody(loggedRequest: LoggedRequest): T { + return objectMapper.readValue( + loggedRequest.bodyAsString, + T::class.java + ) + } +} \ No newline at end of file diff --git a/bpdm-bridge-dummy/src/test/kotlin/com/catenax/bpdm/bridge/dummy/util/DbTestHelpers.kt b/bpdm-bridge-dummy/src/test/kotlin/com/catenax/bpdm/bridge/dummy/util/DbTestHelpers.kt new file mode 100644 index 000000000..3a75ebb64 --- /dev/null +++ b/bpdm-bridge-dummy/src/test/kotlin/com/catenax/bpdm/bridge/dummy/util/DbTestHelpers.kt @@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright (c) 2021,2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package com.catenax.bpdm.bridge.dummy.util + +import jakarta.persistence.EntityManager +import jakarta.persistence.EntityManagerFactory +import org.springframework.stereotype.Component + +private const val BPDM_DB_SCHEMA_NAME: String = "bpdm-bridge-dummy" + +@Component +class DbTestHelpers(entityManagerFactory: EntityManagerFactory) { + + val em: EntityManager = entityManagerFactory.createEntityManager() + + fun truncateDbTables() { + em.transaction.begin() + + em.createNativeQuery( + """ + DO $$ DECLARE table_names RECORD; + BEGIN + FOR table_names IN SELECT table_name + FROM information_schema.tables + WHERE table_schema='${BPDM_DB_SCHEMA_NAME}' + AND table_name NOT IN ('flyway_schema_history') + LOOP + EXECUTE format('TRUNCATE TABLE "${BPDM_DB_SCHEMA_NAME}".%I CONTINUE IDENTITY CASCADE;', table_names.table_name); + END LOOP; + END $$; + """.trimIndent() + ).executeUpdate() + + em.transaction.commit() + } + +} \ No newline at end of file From e3eb007a81c6a9e73dc764ebb47c68c6395e7e8f Mon Sep 17 00:00:00 2001 From: Martin Kaeser Date: Thu, 20 Jul 2023 13:03:22 +0200 Subject: [PATCH 2/3] feat (bridge): Bridge tests for recorded sync state - fix SyncRecordService behavior --- .../kotlin/com/catenax/bpdm/bridge/dummy/SyncStateIT.kt | 8 +------- .../tractusx/bpdm/common/service/BaseSyncRecordService.kt | 2 +- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/bpdm-bridge-dummy/src/test/kotlin/com/catenax/bpdm/bridge/dummy/SyncStateIT.kt b/bpdm-bridge-dummy/src/test/kotlin/com/catenax/bpdm/bridge/dummy/SyncStateIT.kt index 88be0e0ec..ab8f39a4c 100644 --- a/bpdm-bridge-dummy/src/test/kotlin/com/catenax/bpdm/bridge/dummy/SyncStateIT.kt +++ b/bpdm-bridge-dummy/src/test/kotlin/com/catenax/bpdm/bridge/dummy/SyncStateIT.kt @@ -178,11 +178,9 @@ class SyncStateIT @Autowired constructor( ) // 2nd sync fails - val tsBeforeFailedSync = Instant.now() assertThrows(WebClientResponseException.InternalServerError::class.java) { syncService.sync() } - val tsAfterFailedSync = Instant.now() // 3rd sync fails assertThrows(WebClientResponseException.InternalServerError::class.java) { @@ -230,11 +228,7 @@ class SyncStateIT @Autowired constructor( // 5th sync polls from around timestamp of 2nd successful sync val pollFrom5thSync = parseBody(loggedRequests[4]).fromTime - // TODO Didn't expect this! - // If there is one or more sync errors followed by a successful sync, the poll-from timestamp is updated to the start-time of the first failed sync, - // not of the successful sync! -// Assertions.assertThat(pollFrom5thSync).isBetween(tsBefore2ndSuccessfulSync, tsAfter2ndSuccessfulSync) - Assertions.assertThat(pollFrom5thSync).isBetween(tsBeforeFailedSync, tsAfterFailedSync) + Assertions.assertThat(pollFrom5thSync).isBetween(tsBefore2ndSuccessfulSync, tsAfter2ndSuccessfulSync) } private inline fun parseBody(loggedRequest: LoggedRequest): T { diff --git a/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/service/BaseSyncRecordService.kt b/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/service/BaseSyncRecordService.kt index 1fa2dca72..b45119d23 100644 --- a/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/service/BaseSyncRecordService.kt +++ b/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/service/BaseSyncRecordService.kt @@ -72,12 +72,12 @@ abstract class BaseSyncRecordService, SYNC_RECORD : BaseSync record.fromTime = record.startedAt ?: syncStartTime record.errorDetails = null record.errorSave = null - record.startedAt = Instant.now().truncatedTo(ChronoUnit.MICROS) record.finishedAt = null record.count = 0 record.progress = 0f } record.status = SyncStatus.RUNNING + record.startedAt = Instant.now().truncatedTo(ChronoUnit.MICROS) return save(record) } From f7850417b46ab521f4b4e709af2b3c62632186e5 Mon Sep 17 00:00:00 2001 From: Martin Kaeser Date: Thu, 20 Jul 2023 15:40:34 +0200 Subject: [PATCH 3/3] refactor (common): Simplify SyncRecordService, improve naming and docs --- .../bridge/dummy/service/SyncRecordService.kt | 8 +++-- .../catenax/bpdm/bridge/dummy/SyncStateIT.kt | 2 +- .../bpdm/common/model/BaseSyncRecord.kt | 30 +++++++++++++++++ .../tractusx/bpdm/common/model/SyncStatus.kt | 9 ++++- .../common/service/BaseSyncRecordService.kt | 33 ++++++++++++------- .../bpdm/pool/service/SyncRecordService.kt | 8 +++-- 6 files changed, 72 insertions(+), 18 deletions(-) diff --git a/bpdm-bridge-dummy/src/main/kotlin/com/catenax/bpdm/bridge/dummy/service/SyncRecordService.kt b/bpdm-bridge-dummy/src/main/kotlin/com/catenax/bpdm/bridge/dummy/service/SyncRecordService.kt index f4a61a084..a32d1cb14 100644 --- a/bpdm-bridge-dummy/src/main/kotlin/com/catenax/bpdm/bridge/dummy/service/SyncRecordService.kt +++ b/bpdm-bridge-dummy/src/main/kotlin/com/catenax/bpdm/bridge/dummy/service/SyncRecordService.kt @@ -35,8 +35,12 @@ class SyncRecordService( override val logger = KotlinLogging.logger { } - override fun newSyncRecord(type: SyncType, syncStartTime: Instant) = - SyncRecord(type, SyncStatus.NOT_SYNCED, syncStartTime) + override fun newSyncRecord(type: SyncType, initialFromTime: Instant) = + SyncRecord( + type = type, + status = SyncStatus.NOT_SYNCED, + fromTime = initialFromTime + ) override fun save(record: SyncRecord) = syncRecordRepository.save(record) diff --git a/bpdm-bridge-dummy/src/test/kotlin/com/catenax/bpdm/bridge/dummy/SyncStateIT.kt b/bpdm-bridge-dummy/src/test/kotlin/com/catenax/bpdm/bridge/dummy/SyncStateIT.kt index ab8f39a4c..44be89fba 100644 --- a/bpdm-bridge-dummy/src/test/kotlin/com/catenax/bpdm/bridge/dummy/SyncStateIT.kt +++ b/bpdm-bridge-dummy/src/test/kotlin/com/catenax/bpdm/bridge/dummy/SyncStateIT.kt @@ -45,7 +45,7 @@ import org.springframework.test.context.DynamicPropertySource import org.springframework.web.reactive.function.client.WebClientResponseException import java.time.Instant -val TS_INITIAL_POLL_FROM: Instant = BaseSyncRecordService.syncStartTime +val TS_INITIAL_POLL_FROM: Instant = BaseSyncRecordService.INITIAL_FROM_TIME @SpringBootTest( webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = [Application::class] diff --git a/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/model/BaseSyncRecord.kt b/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/model/BaseSyncRecord.kt index 644ce3e28..8d858a2db 100644 --- a/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/model/BaseSyncRecord.kt +++ b/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/model/BaseSyncRecord.kt @@ -21,22 +21,52 @@ package org.eclipse.tractusx.bpdm.common.model import java.time.Instant +/** + * Info about most current sync run by type + */ interface BaseSyncRecord { + /** + * Discriminator to allow multiple sync records in the DB + */ var type: SYNC_TYPE + /** + * @see SyncStatus + */ var status: SyncStatus + /** + * Sync run considers only data after this instant + */ var fromTime: Instant + /** + * Progress from 0 ot 1 + */ var progress: Float + /** + * Progress counter + */ var count: Int + /** + * Plain message for error status + */ var errorDetails: String? + /** + * Optional serialized state to allow resume after error status + */ var errorSave: String? + /** + * Instant this sync run was started + */ var startedAt: Instant? + /** + * Instant this sync run was finished + */ var finishedAt: Instant? } diff --git a/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/model/SyncStatus.kt b/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/model/SyncStatus.kt index aa926981a..2114a4475 100644 --- a/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/model/SyncStatus.kt +++ b/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/model/SyncStatus.kt @@ -19,10 +19,17 @@ package org.eclipse.tractusx.bpdm.common.model -enum class SyncStatus{ +enum class SyncStatus { + // never synced NOT_SYNCED, + + // sync currently running RUNNING, + + // last sync successful SUCCESS, + + // last sync failed ERROR } diff --git a/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/service/BaseSyncRecordService.kt b/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/service/BaseSyncRecordService.kt index b45119d23..6d092a936 100644 --- a/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/service/BaseSyncRecordService.kt +++ b/bpdm-common/src/main/kotlin/org/eclipse/tractusx/bpdm/common/service/BaseSyncRecordService.kt @@ -32,17 +32,27 @@ import java.time.ZoneOffset import java.time.temporal.ChronoUnit /** + * This services manages data about the most current sync run for each SYNC_TYPE. + * + * How to use: + * - At the start of a sync run you should call #setSynchronizationStart(type). + * The returned record contains the automatically updated timestamp after which new data should be considered (#fromTime). + * If the last sync has failed #errorSave might contain state info to allow resuming near the failure position. + * If another sync for the same type is currently running a BpdmSyncConflictException is thrown. + * - If the sync run was successful you should call #setSynchronizationSuccess(type). + * - If the sync run has failed you should call #setSynchronizationError(type, errorMessage, saveState). + * * Uses transaction isolation level "serializable" in order to make sure that in case of parallel execution on different spring boot instances, * only one instance can get the sync record and make changes like setting it to "running" state at the same time. */ abstract class BaseSyncRecordService, SYNC_RECORD : BaseSyncRecord> { companion object { - val syncStartTime: Instant = LocalDateTime.of(2000, 1, 1, 0, 0).toInstant(ZoneOffset.UTC) + val INITIAL_FROM_TIME: Instant = LocalDateTime.of(2000, 1, 1, 0, 0).toInstant(ZoneOffset.UTC) } protected abstract val logger: KLogger - protected abstract fun newSyncRecord(type: SYNC_TYPE, syncStartTime: Instant): SYNC_RECORD + protected abstract fun newSyncRecord(type: SYNC_TYPE, initialFromTime: Instant): SYNC_RECORD protected abstract fun save(record: SYNC_RECORD): SYNC_RECORD @@ -52,7 +62,7 @@ abstract class BaseSyncRecordService, SYNC_RECORD : BaseSync open fun getOrCreateRecord(type: SYNC_TYPE): SYNC_RECORD { return findByType(type) ?: run { logger.info { "Create new sync record entry for type $type" } - val newEntry = newSyncRecord(type, syncStartTime) + val newEntry = newSyncRecord(type, INITIAL_FROM_TIME) save(newEntry) } } @@ -66,18 +76,17 @@ abstract class BaseSyncRecordService, SYNC_RECORD : BaseSync logger.debug { "Set sync of type ${record.type} to status ${SyncStatus.RUNNING}" } - record.errorDetails = null - if (record.status != SyncStatus.ERROR) { - record.fromTime = record.startedAt ?: syncStartTime - record.errorDetails = null - record.errorSave = null - record.finishedAt = null - record.count = 0 - record.progress = 0f + // For error status the field fromTime is preserved to not miss any data + record.fromTime = record.startedAt ?: INITIAL_FROM_TIME } + + record.errorDetails = null record.status = SyncStatus.RUNNING record.startedAt = Instant.now().truncatedTo(ChronoUnit.MICROS) + record.finishedAt = null + record.count = 0 + record.progress = 0f return save(record) } @@ -138,7 +147,7 @@ abstract class BaseSyncRecordService, SYNC_RECORD : BaseSync record.finishedAt = null record.count = 0 record.progress = 0f - record.fromTime = syncStartTime + record.fromTime = INITIAL_FROM_TIME return save(record) } diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/service/SyncRecordService.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/service/SyncRecordService.kt index 4a57a927c..ebcbe7019 100644 --- a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/service/SyncRecordService.kt +++ b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/service/SyncRecordService.kt @@ -35,8 +35,12 @@ class SyncRecordService( override val logger = KotlinLogging.logger { } - override fun newSyncRecord(type: SyncType, syncStartTime: Instant) = - SyncRecord(type, SyncStatus.NOT_SYNCED, syncStartTime) + override fun newSyncRecord(type: SyncType, initialFromTime: Instant) = + SyncRecord( + type = type, + status = SyncStatus.NOT_SYNCED, + fromTime = initialFromTime + ) override fun save(record: SyncRecord) = syncRecordRepository.save(record)