diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/SaasAdapterConfig.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/SaasAdapterConfig.kt deleted file mode 100644 index 74e861a9d..000000000 --- a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/SaasAdapterConfig.kt +++ /dev/null @@ -1,67 +0,0 @@ -/******************************************************************************* - * 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 org.eclipse.tractusx.bpdm.pool.component.saas - -import jakarta.annotation.PostConstruct -import mu.KotlinLogging -import org.eclipse.tractusx.bpdm.pool.component.saas.config.SaasAdapterConfigProperties -import org.springframework.boot.context.properties.ConfigurationPropertiesScan -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.ComponentScan -import org.springframework.context.annotation.Configuration -import org.springframework.http.HttpHeaders -import org.springframework.http.MediaType -import org.springframework.http.codec.ClientCodecConfigurer -import org.springframework.scheduling.annotation.EnableAsync -import org.springframework.web.reactive.function.client.ExchangeStrategies -import org.springframework.web.reactive.function.client.WebClient - - -@EnableAsync -@Configuration -@ComponentScan -@ConfigurationPropertiesScan -class SaasAdapterConfig( - private val adapterProperties: SaasAdapterConfigProperties -) { - - companion object { - const val memorySize = 1 * 1024 * 1024 // 1mb - } - - private val logger = KotlinLogging.logger { } - - @PostConstruct - fun logCreation() { - logger.info { "Enable and configure SaaS adapter" } - } - - @Bean - fun adapterClient(): WebClient { - return WebClient.builder() - .exchangeStrategies(ExchangeStrategies.builder() - .codecs { codecs: ClientCodecConfigurer -> codecs.defaultCodecs().maxInMemorySize(memorySize) } - .build()) - .baseUrl(adapterProperties.host) - .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .defaultHeader("x-api-key", adapterProperties.apiKey) - .build() - } -} \ No newline at end of file diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/config/SaasAdapterConfigProperties.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/config/SaasAdapterConfigProperties.kt deleted file mode 100644 index 8fc956598..000000000 --- a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/config/SaasAdapterConfigProperties.kt +++ /dev/null @@ -1,48 +0,0 @@ -/******************************************************************************* - * 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 org.eclipse.tractusx.bpdm.pool.component.saas.config - -import org.springframework.boot.context.properties.ConfigurationProperties - - -@ConfigurationProperties(prefix = "bpdm.saas") -class SaasAdapterConfigProperties( - val enabled: Boolean = true, - val host: String = "http://localhost:1234", - val storage: String = "storage_id", - val datasource: String = "datasource_id", - val apiKey: String = "", - val importLimit: Int = 100, - val importSchedulerCronExpr: String = "-", - val legalEntityType: String = "LEGAL_ENTITY", - val siteType: String = "ORGANIZATIONAL_UNIT", - val addressType: String = "BP_ADDRESS", - val parentRelationType: String = "PARENT", - val bpnKey: String = "CX_BPN", - val treatInvalidBpnAsNew: Boolean = false, - val requestSizeLimit: Int = 500 -) { - private val exchangeApiUrl: String = "data-exchange/rest/v4" - private val referenceApiUrl: String = "referencedata/rest/v3" - - val readBusinessPartnerUrl = "/${exchangeApiUrl}/storages/${storage}/businesspartners" - val fetchBusinessPartnersBatchUrl = "/${referenceApiUrl}/businesspartners/fetch-batch" - -} \ No newline at end of file diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/controller/SaasController.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/controller/SaasController.kt deleted file mode 100644 index af768aef3..000000000 --- a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/controller/SaasController.kt +++ /dev/null @@ -1,62 +0,0 @@ -/******************************************************************************* - * 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 org.eclipse.tractusx.bpdm.pool.component.saas.controller - -import org.eclipse.tractusx.bpdm.common.dto.request.PaginationRequest -import org.eclipse.tractusx.bpdm.common.dto.response.PageResponse -import org.eclipse.tractusx.bpdm.pool.api.PoolSaasApi -import org.eclipse.tractusx.bpdm.pool.api.model.ImportIdEntry -import org.eclipse.tractusx.bpdm.pool.api.model.request.ImportIdFilterRequest -import org.eclipse.tractusx.bpdm.pool.api.model.response.ImportIdMappingResponse -import org.eclipse.tractusx.bpdm.pool.api.model.response.SyncResponse -import org.eclipse.tractusx.bpdm.pool.component.saas.config.SaasAdapterConfigProperties -import org.eclipse.tractusx.bpdm.pool.component.saas.service.ImportStarterService -import org.eclipse.tractusx.bpdm.pool.exception.BpdmRequestSizeException -import org.springframework.web.bind.annotation.RestController - -@RestController -class SaasController( - private val partnerImportService: ImportStarterService, - private val adapterConfigProperties: SaasAdapterConfigProperties -) : PoolSaasApi { - - override fun importBusinessPartners(): SyncResponse { - return partnerImportService.importAsync() - } - - - override fun getSyncStatus(): SyncResponse { - return partnerImportService.getImportStatus() - } - - - override fun getImportEntries(importIdFilterRequest: ImportIdFilterRequest): ImportIdMappingResponse { - if (importIdFilterRequest.importIdentifiers.size > adapterConfigProperties.requestSizeLimit) - BpdmRequestSizeException(importIdFilterRequest.importIdentifiers.size, adapterConfigProperties.requestSizeLimit) - return partnerImportService.getImportIdEntries(importIdFilterRequest.importIdentifiers) - } - - - override fun getImportEntries(paginationRequest: PaginationRequest): PageResponse { - if (paginationRequest.size > adapterConfigProperties.requestSizeLimit) - BpdmRequestSizeException(paginationRequest.size, adapterConfigProperties.requestSizeLimit) - return partnerImportService.getImportIdEntries(paginationRequest) - } -} \ No newline at end of file diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/dto/BusinessPartnerWithBpn.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/dto/BusinessPartnerWithBpn.kt deleted file mode 100644 index 4211e590b..000000000 --- a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/dto/BusinessPartnerWithBpn.kt +++ /dev/null @@ -1,27 +0,0 @@ -/******************************************************************************* - * 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 org.eclipse.tractusx.bpdm.pool.component.saas.dto - -import org.eclipse.tractusx.bpdm.common.dto.saas.BusinessPartnerSaas - -data class BusinessPartnerWithBpn( - val partner: BusinessPartnerSaas, - val bpn: String -) diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/dto/BusinessPartnerWithParentBpn.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/dto/BusinessPartnerWithParentBpn.kt deleted file mode 100644 index af6c55d6f..000000000 --- a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/dto/BusinessPartnerWithParentBpn.kt +++ /dev/null @@ -1,27 +0,0 @@ -/******************************************************************************* - * 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 org.eclipse.tractusx.bpdm.pool.component.saas.dto - -import org.eclipse.tractusx.bpdm.common.dto.saas.BusinessPartnerSaas - -data class BusinessPartnerWithParentBpn( - val partner: BusinessPartnerSaas, - val parentBpn: String -) diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/dto/BusinessPartnerWithParentId.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/dto/BusinessPartnerWithParentId.kt deleted file mode 100644 index e479e846a..000000000 --- a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/dto/BusinessPartnerWithParentId.kt +++ /dev/null @@ -1,27 +0,0 @@ -/******************************************************************************* - * 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 org.eclipse.tractusx.bpdm.pool.component.saas.dto - -import org.eclipse.tractusx.bpdm.common.dto.saas.BusinessPartnerSaas - -data class BusinessPartnerWithParentId( - val partner: BusinessPartnerSaas, - val parentId: String -) diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/dto/ImportIdEntry.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/dto/ImportIdEntry.kt deleted file mode 100644 index d8d5f3652..000000000 --- a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/dto/ImportIdEntry.kt +++ /dev/null @@ -1,25 +0,0 @@ -/******************************************************************************* - * 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 org.eclipse.tractusx.bpdm.pool.component.saas.dto - -data class ImportIdEntry( - val importId: String, - val bpn: String -) diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/dto/ImportIdFilterRequest.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/dto/ImportIdFilterRequest.kt deleted file mode 100644 index 1e1d1eb2d..000000000 --- a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/dto/ImportIdFilterRequest.kt +++ /dev/null @@ -1,24 +0,0 @@ -/******************************************************************************* - * 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 org.eclipse.tractusx.bpdm.pool.component.saas.dto - -data class ImportIdFilterRequest( - val importIdentifiers: Collection = emptyList() -) diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/dto/ImportIdMappingResponse.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/dto/ImportIdMappingResponse.kt deleted file mode 100644 index c063f7686..000000000 --- a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/dto/ImportIdMappingResponse.kt +++ /dev/null @@ -1,27 +0,0 @@ -/******************************************************************************* - * 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 org.eclipse.tractusx.bpdm.pool.component.saas.dto - -data class ImportIdMappingResponse( - val entries: Collection, - val notFound: Collection -) { - val size = entries.size -} diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/dto/ImportResponsePage.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/dto/ImportResponsePage.kt deleted file mode 100644 index b0442c6bc..000000000 --- a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/dto/ImportResponsePage.kt +++ /dev/null @@ -1,34 +0,0 @@ -/******************************************************************************* - * 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 org.eclipse.tractusx.bpdm.pool.component.saas.dto - - -import org.eclipse.tractusx.bpdm.common.dto.response.LogisticAddressResponse -import org.eclipse.tractusx.bpdm.pool.api.model.response.AddressPartnerCreateResponse -import org.eclipse.tractusx.bpdm.pool.api.model.response.LegalEntityPartnerCreateResponse -import org.eclipse.tractusx.bpdm.pool.api.model.response.SitePartnerCreateResponse - -data class ImportResponsePage( - val totalElements: Int, - val nextStartAfter: String?, - val legalEntities: UpsertCollection, - val sites: UpsertCollection, - val addresses: UpsertCollection -) diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/dto/LsaType.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/dto/LsaType.kt deleted file mode 100644 index a7ebbd34d..000000000 --- a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/dto/LsaType.kt +++ /dev/null @@ -1,26 +0,0 @@ -/******************************************************************************* - * 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 org.eclipse.tractusx.bpdm.pool.component.saas.dto - -enum class LsaType { - LEGAL_ENTITY, - SITE, - ADDRESS -} diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/dto/UpsertCollection.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/dto/UpsertCollection.kt deleted file mode 100644 index 3a78147db..000000000 --- a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/dto/UpsertCollection.kt +++ /dev/null @@ -1,25 +0,0 @@ -/******************************************************************************* - * 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 org.eclipse.tractusx.bpdm.pool.component.saas.dto - -data class UpsertCollection( - val created: Collection, - val updated: Collection -) diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/service/ImportStarterService.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/service/ImportStarterService.kt deleted file mode 100644 index 6e7a9d522..000000000 --- a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/service/ImportStarterService.kt +++ /dev/null @@ -1,102 +0,0 @@ -/******************************************************************************* - * 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 org.eclipse.tractusx.bpdm.pool.component.saas.service - -import mu.KotlinLogging -import org.eclipse.tractusx.bpdm.common.dto.request.PaginationRequest -import org.eclipse.tractusx.bpdm.common.dto.response.PageResponse -import org.eclipse.tractusx.bpdm.pool.api.model.ImportIdEntry -import org.eclipse.tractusx.bpdm.pool.api.model.SyncType -import org.eclipse.tractusx.bpdm.pool.api.model.response.ImportIdMappingResponse -import org.eclipse.tractusx.bpdm.pool.api.model.response.SyncResponse -import org.eclipse.tractusx.bpdm.pool.repository.ImportEntryRepository -import org.eclipse.tractusx.bpdm.pool.service.SyncRecordService -import org.eclipse.tractusx.bpdm.pool.service.toDto -import org.springframework.data.domain.PageRequest -import org.springframework.scheduling.annotation.Scheduled -import org.springframework.stereotype.Service - -/** - * Starts the partner import either blocking or non-blocking - */ -@Service -class ImportStarterService( - private val syncRecordService: SyncRecordService, - private val importService: PartnerImportService, - private val importEntryRepository: ImportEntryRepository -) { - - private val logger = KotlinLogging.logger { } - - /** - * Import records synchronously and return a [SyncResponse] about the import result information - */ - @Scheduled(cron = "\${bpdm.saas.import-scheduler-cron-expr:-}", zone = "UTC") - fun import(): SyncResponse { - return startImport(true) - } - - /** - * Import records asynchronously and return a [SyncResponse] with information about the started import - */ - fun importAsync(): SyncResponse { - return startImport(false) - } - - /** - * Fetch a [SyncResponse] about the state of the current import - */ - fun getImportStatus(): SyncResponse { - return syncRecordService.getOrCreateRecord(SyncType.SAAS_IMPORT).toDto() - } - - /** - * Filter import entries by the given [importIdentifiers] - */ - fun getImportIdEntries(importIdentifiers: Collection): ImportIdMappingResponse { - val foundEntries = importEntryRepository.findByImportIdentifierIn(importIdentifiers).map { ImportIdEntry(it.importIdentifier, it.bpn) } - val missingEntries = importIdentifiers.minus(foundEntries.map { it.importId }.toSet()) - - return ImportIdMappingResponse(foundEntries, missingEntries) - } - - /** - * Paginate over import entries by [paginationRequest] - */ - fun getImportIdEntries(paginationRequest: PaginationRequest): PageResponse { - val entriesPage = importEntryRepository.findAll(PageRequest.of(paginationRequest.page, paginationRequest.size)) - return entriesPage.toDto(entriesPage.content.map { ImportIdEntry(it.importIdentifier, it.bpn) }) - } - - - - private fun startImport(inSync: Boolean): SyncResponse { - val record = syncRecordService.setSynchronizationStart(SyncType.SAAS_IMPORT) - logger.debug { "Initializing SaaS import starting with ID ${record.errorSave}' for modified records from '${record.fromTime}' with async: ${!inSync}" } - - if (inSync) - importService.importPaginated(record.fromTime, record.errorSave) - else - importService.importPaginatedAsync(record.fromTime, record.errorSave) - - return record.toDto() - } - -} \ No newline at end of file diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/service/PartnerImportPageService.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/service/PartnerImportPageService.kt deleted file mode 100644 index ef6491da3..000000000 --- a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/service/PartnerImportPageService.kt +++ /dev/null @@ -1,373 +0,0 @@ -/******************************************************************************* - * 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 org.eclipse.tractusx.bpdm.pool.component.saas.service - -import mu.KotlinLogging -import org.eclipse.tractusx.bpdm.common.dto.IdentifierLsaType -import org.eclipse.tractusx.bpdm.common.dto.response.LogisticAddressResponse -import org.eclipse.tractusx.bpdm.common.dto.saas.BusinessPartnerSaas -import org.eclipse.tractusx.bpdm.common.dto.saas.ThoroughfareSaas -import org.eclipse.tractusx.bpdm.common.exception.BpdmMappingException -import org.eclipse.tractusx.bpdm.common.service.SaasMappings -import org.eclipse.tractusx.bpdm.pool.api.model.response.AddressPartnerCreateResponse -import org.eclipse.tractusx.bpdm.pool.api.model.response.LegalEntityPartnerCreateResponse -import org.eclipse.tractusx.bpdm.pool.api.model.response.SitePartnerCreateResponse -import org.eclipse.tractusx.bpdm.pool.component.saas.config.SaasAdapterConfigProperties -import org.eclipse.tractusx.bpdm.pool.component.saas.dto.* -import org.eclipse.tractusx.bpdm.pool.entity.ImportEntry -import org.eclipse.tractusx.bpdm.pool.repository.ImportEntryRepository -import org.eclipse.tractusx.bpdm.pool.repository.LegalEntityRepository -import org.eclipse.tractusx.bpdm.pool.repository.LogisticAddressRepository -import org.eclipse.tractusx.bpdm.pool.repository.SiteRepository -import org.eclipse.tractusx.bpdm.pool.service.BusinessPartnerBuildService -import org.eclipse.tractusx.bpdm.pool.service.MetadataService -import org.springframework.data.domain.Pageable -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional -import java.time.Instant - -@Service -class PartnerImportPageService( - private val adapterProperties: SaasAdapterConfigProperties, - private val metadataService: MetadataService, - private val mappingService: SaasToRequestMapper, - private val businessPartnerBuildService: BusinessPartnerBuildService, - private val importEntryRepository: ImportEntryRepository, - private val saasClient: SaasClient, - private val legalEntityRepository: LegalEntityRepository, - private val siteRepository: SiteRepository, - private val logisticAddressRepository: LogisticAddressRepository -) { - private val logger = KotlinLogging.logger { } - - private val validChildParentRelations = listOf( - Pair(LsaType.ADDRESS, LsaType.SITE), - Pair(LsaType.ADDRESS, LsaType.LEGAL_ENTITY), - Pair(LsaType.SITE, LsaType.LEGAL_ENTITY) - ) - - @Transactional - fun import(modifiedAfter: Instant, startAfter: String?): ImportResponsePage { - logger.debug { "Import new business partner starting after ID '$startAfter'" } - - val partnerCollection = saasClient.readBusinessPartners(modifiedAfter, startAfter) - - logger.debug { "Received ${partnerCollection.values.size} to import from SaaS" } - - addNewMetadata(partnerCollection.values) - - val validPartners = partnerCollection.values.filter { isValid(it) } - val (noBpn, validBpn) = partitionHasNoBpnAndValidBpn(validPartners) - - val (createdLegalEntities, createdSites, createdAddresses) = createPartners(noBpn) - val (updatedLegalEntities, updatedSites, updatedAddresses) = updatePartners(validBpn) - - return ImportResponsePage( - partnerCollection.total, - partnerCollection.nextStartAfter, - UpsertCollection(createdLegalEntities, updatedLegalEntities), - UpsertCollection(createdSites, updatedSites), - UpsertCollection(createdAddresses, updatedAddresses) - ) - } - - private fun addNewMetadata(partners: Collection){ - val (legalEntitiesSaas, _, addressesSaas) = partitionIntoLSA(partners) { it.extractLsaType() } - - addNewMetadataIdentifierTypes(legalEntitiesSaas, IdentifierLsaType.LEGAL_ENTITY) - addNewMetadataIdentifierTypes(addressesSaas, IdentifierLsaType.ADDRESS) - - addNewMetadataLegalForms(legalEntitiesSaas) - } - - private fun addNewMetadataIdentifierTypes(partners: Collection, lsaType: IdentifierLsaType) { - partners - .flatMap { it.identifiers } - .mapNotNull { if (it.type?.technicalKey == null) null else it.type } - .associateBy { it.technicalKey } - .minus(metadataService.getIdentifierTypes(Pageable.unpaged(), lsaType).content.map { it.technicalKey }.toSet()) - .values - .map { mappingService.toIdentifierTypeDto(it, lsaType) } - .forEach { metadataService.createIdentifierType(it) } - } - - private fun addNewMetadataLegalForms(partners: Collection) { - partners - .filter { it.legalForm?.technicalKey != null } - .map { it.legalForm!! to it } - .associateBy { it.first.technicalKey } - .minus(metadataService.getLegalForms(Pageable.unpaged()).content.map { it.technicalKey }.toSet()) - .values - .map { mappingService.toRequest(it.first, it.second) } - .forEach { metadataService.createLegalForm(it) } - } - - private fun createPartners(partners: Collection): - Triple, Collection, Collection> { - val (legalEntitiesSaas, sitesSaas, addressesSaas) = partitionIntoLSA(partners) { it.extractLsaType() } - - val partnersWithParent = determineParents(sitesSaas + addressesSaas) - val (_, sitesWithParent, addressesWithParent) = partitionIntoLSA(partnersWithParent) { it.partner.extractLsaType() } - - val legalEntities = legalEntitiesSaas.mapNotNull { mappingService.toLegalEntityCreateRequestOrNull(it) } - val sites = sitesWithParent.mapNotNull { mappingService.toSiteCreateRequestOrNull(it) } - val addresses = addressesWithParent.mapNotNull { mappingService.toAddressCreateRequestOrNull(it) } - - val createdLegalEntities = if (legalEntities.isNotEmpty()) businessPartnerBuildService.createLegalEntities(legalEntities).entities else emptyList() - val createdSites = if (sites.isNotEmpty()) businessPartnerBuildService.createSites(sites).entities else emptyList() - val createdAddresses = if (addresses.isNotEmpty()) businessPartnerBuildService.createAddresses(addresses).entities else emptyList() - - val legalEntityImportEntries = createdLegalEntities.mapNotNull { if (it.index != null) ImportEntry(it.index!!, it.legalEntity.bpn) else null } - val siteImportEntries = createdSites.mapNotNull { if (it.index != null) ImportEntry(it.index!!, it.site.bpn) else null } - val addressImportEntries = createdAddresses.mapNotNull { if (it.index != null) ImportEntry(it.index!!, it.address.bpn) else null } - - importEntryRepository.saveAll(legalEntityImportEntries + siteImportEntries + addressImportEntries) - - return Triple(createdLegalEntities, createdSites, createdAddresses) - } - - private fun updatePartners(partners: Collection): - Triple, Collection, Collection> { - val (legalEntitiesSaas, sitesSaas, addressesSaas) = partitionIntoLSA(partners) { it.partner.extractLsaType() } - - val legalEntities = legalEntitiesSaas.mapNotNull { mappingService.toLegalEntityUpdateRequestOrNull(it) } - val sites = sitesSaas.mapNotNull { mappingService.toSiteUpdateRequestOrNull(it) } - val addresses = addressesSaas.mapNotNull { mappingService.toAddressUpdateRequestOrNull(it) } - - val createdLegalEntities = if (legalEntities.isNotEmpty()) businessPartnerBuildService.updateLegalEntities(legalEntities).entities else emptyList() - val createdSites = if (sites.isNotEmpty()) businessPartnerBuildService.updateSites(sites).entities else emptyList() - val createdAddresses = if (addresses.isNotEmpty()) businessPartnerBuildService.updateAddresses(addresses).entities else emptyList() - - return Triple(createdLegalEntities, createdSites, createdAddresses) - } - - private fun determineParents(children: Collection): Collection { - if (children.isEmpty()) return emptyList() - - val childrenWithParentId = determineParentId(children) - - val parents = saasClient.readBusinessPartnersByExternalIds(childrenWithParentId.map { it.parentId }).values - val validParents = filterValidRelations(parents.filter { isValid(it) }, childrenWithParentId) - - val parentsByImportId = determineParentBPNs(validParents).associateBy { it.partner.externalId!! } - - return childrenWithParentId.mapNotNull { childWithParentId -> - val parent = parentsByImportId[childWithParentId.parentId] - if (parent != null) { - BusinessPartnerWithParentBpn(childWithParentId.partner, parent.bpn) - } else { - logger.warn { "Can not resolve parent with Import-ID ${childWithParentId.parentId} for SaaS record with ID ${childWithParentId.partner.id}" } - null - } - } - } - - private fun determineParentBPNs(parents: Collection): Collection{ - val parentByImportId = parents.associateBy { it.externalId!! } - - val (parentsWithoutBpn, parentsWithBpn) = partitionHasNoBpnAndValidBpn(parents) - - //create missing parents in the Pool - val (newLegalEntities, newSites, _) = createPartners(parentsWithoutBpn) - - val createdParents = newLegalEntities.map { Pair(parentByImportId[it.index], it.legalEntity.bpn) } - .plus(newSites.map { Pair(parentByImportId[it.index], it.site.bpn) }) - .filter { (parent, _) -> parent != null } - .map { BusinessPartnerWithBpn(it.first!!, it.second) } - - return parentsWithBpn + createdParents - } - - private fun determineParentId(children: Collection): Collection { - return children.mapNotNull { child -> - val parentId = child.relations.firstOrNull { id -> id.type?.technicalKey == adapterProperties.parentRelationType }?.startNode - if (parentId != null) { - BusinessPartnerWithParentId(child, parentId) - } else { - logger.warn { "Can not resolve parent SaaS record for child with ID ${child.id}: Record contains no parent ID" } - null - } - } - } - - private fun isValid(partner: BusinessPartnerSaas): Boolean { - - try { - SaasMappings.convertSaasAdressesToLogisticAddressDto(partner.addresses, partner.externalId) - } catch(e: BpdmMappingException) { - logger.warn { "SaaS Partner with id ${partner.id} is invalid. Message: ${e.localizedMessage}" } - return false - } - - if (partner.externalId == null) { - logger.warn { "SaaS record with ID ${partner.id} has no external ID that can be used as import ID" } - return false - } - - return true - } - - private fun partitionIntoLSA(partners: Collection, determineTypeFunction: (T) -> LsaType?): - Triple, Collection, Collection> { - - val lsaGroups = partners.groupBy { determineTypeFunction(it) }.filter { it.key != null } - - val legalEntities = lsaGroups[LsaType.LEGAL_ENTITY] ?: emptyList() - val sites = lsaGroups[LsaType.SITE] ?: emptyList() - val addresses = lsaGroups[LsaType.ADDRESS] ?: emptyList() - - return Triple(legalEntities, sites, addresses) - } - - private fun BusinessPartnerSaas.extractLsaType(): LsaType { - val validTypes = types.mapNotNull { - when (it.technicalKey) { - adapterProperties.legalEntityType -> LsaType.LEGAL_ENTITY - adapterProperties.siteType -> LsaType.SITE - adapterProperties.addressType -> LsaType.ADDRESS - else -> null - } - } - - if (validTypes.isEmpty()) { - logger.warn { "SaaS Business partner with id $id does not contain any LSA type. Assume Legal Entity" } - return LsaType.LEGAL_ENTITY - } - - val type = validTypes.first() - - if (validTypes.size > 1) { - logger.warn { "SaaS Business partner with id $id contains more than one LSA type. Taking first encountered type $type" } - } - - return type - } - - /** - * Partition business partner collection into records for which no BPN can be retrieved and records which have a BPN that also is found in the Pool - * BPN is determined in two priorities: - * 1. Try to get BPN from import entry - * 2. Try to get BPN from record in identifiers and check whether this BPN actually exists in the Pool (valid BPN) - * If we encounter valid BPNs with no import entry we create an import entry for it (as self correction logic) - * Optionally, depending on the configuration we either ignore records with invalid BPNs or we treat them as having no BPN - */ - private fun partitionHasNoBpnAndValidBpn(partners: Collection): Pair, Collection>{ - //search BPN in import entry based on CX-Pool identifier - val (withEntry, withoutEntry) = partitionHasImportEntry(partners) - //if no entry has been found look for BPN in identifiers of records - val (hasBpnIdentifier, hasNoBpnIdentifier) = partitionContainsBpnIdentifier(withoutEntry) - //if BPN is identifiers but no import record exists, check whether the BPN is known to the BPDM Pool - val (bpnFound, bpnMissing) = partitionBpnFound(hasBpnIdentifier) - - val consequence = if(adapterProperties.treatInvalidBpnAsNew) "Record will be treated as having no BPN." else "Record will be ignored." - bpnMissing.forEach { - logger.warn { "Business partner with Id ${it.partner.externalId} contains BPN ${it.bpn} but such BPN can't be found in the Pool." + consequence } - } - - val hasValidBpn = withEntry + bpnFound - val hasNoBpn = if(adapterProperties.treatInvalidBpnAsNew) hasNoBpnIdentifier + bpnMissing.map { it.partner } else hasNoBpnIdentifier - - //Create missing import entries for records which have known BPNs - importEntryRepository.saveAll(bpnFound.map { ImportEntry(it.partner.externalId!!, it.bpn) }) - - return Pair(hasNoBpn, hasValidBpn) - } - - private fun partitionContainsBpnIdentifier(partners: Collection): Pair, Collection> { - val (hasBpn, nopBpn) = partners - .map { Pair(it, it.extractId(adapterProperties.bpnKey)) } - .partition { (_, bpn) -> bpn != null } - return Pair( - hasBpn.map { (partner, id) -> BusinessPartnerWithBpn(partner, id!!) }, - nopBpn.map { (partner, _) -> partner }) - } - - private fun partitionHasImportEntry(partners: Collection): Pair, Collection> { - val importEntries = importEntryRepository.findByImportIdentifierIn(partners.map { it.externalId!! }) - .associateBy { it.importIdentifier } - - val (hasImportedBpn, noBpn) = partners - .map { Pair(it, importEntries[it.externalId]?.bpn) } - .partition { (_, bpn) -> bpn != null } - - return Pair( - hasImportedBpn.map { (partner, bpn) -> BusinessPartnerWithBpn(partner, bpn!!) }, - noBpn.map { (partner, _) -> partner } - ) - } - - private fun partitionBpnFound(partners: Collection): Pair, Collection> { - - val partnersByBpn = partners.associateBy { it.bpn } - val (bpnLs, bpnSs, bpnAs) = partitionLSA(partnersByBpn.keys) - - // TODO use BPN projection as optimization - val foundBpnLs = legalEntityRepository.findDistinctByBpnIn(bpnLs).map { it.bpn } - val foundBpnSs = siteRepository.findDistinctByBpnIn(bpnSs).map { it.bpn } - val foundBpnAs = logisticAddressRepository.findDistinctByBpnIn(bpnAs).map { it.bpn } - - val foundBpns = foundBpnLs + foundBpnSs + foundBpnAs - val bpnMissing = partnersByBpn - foundBpns.toSet() - val bpnExists = partnersByBpn - bpnMissing.keys - - return Pair(bpnExists.values, bpnMissing.values) - } - - private fun partitionLSA(bpns: Collection): Triple, Collection, Collection>{ - val bpnLs = mutableListOf() - val bpnSs = mutableListOf() - val bpnAs = mutableListOf() - - bpns.map { it.uppercase()}.forEach { - when(it.take(4)) - { - "BPNL" -> bpnLs.add(it) - "BPNS" -> bpnSs.add(it) - "BPNA" -> bpnAs.add(it) - else -> logger.warn { "Encountered non-valid BPN: $it" } - } - } - - return Triple(bpnLs, bpnSs, bpnAs) - } - - private fun filterValidRelations( - parents: Collection, - children: Collection - ): Collection { - val parentIdToChild = children.associateBy { it.parentId } - - return parents.filter { parent -> - val parentType = parent.extractLsaType() - val childType = parentIdToChild[parent.externalId]?.partner?.extractLsaType() - - if (childType != null) { - val childParentRelation = Pair(childType, parentType) - validChildParentRelations.contains(childParentRelation) - } else { - false - } - } - } - - private fun BusinessPartnerSaas.extractId(idKey: String): String? = - identifiers.find { it.type?.technicalKey == idKey }?.value - -} \ No newline at end of file diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/service/PartnerImportService.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/service/PartnerImportService.kt deleted file mode 100644 index b539f93dd..000000000 --- a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/service/PartnerImportService.kt +++ /dev/null @@ -1,87 +0,0 @@ -/******************************************************************************* - * 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 org.eclipse.tractusx.bpdm.pool.component.saas.service - -import jakarta.persistence.EntityManager -import mu.KotlinLogging -import org.eclipse.tractusx.bpdm.pool.api.model.SyncType -import org.eclipse.tractusx.bpdm.pool.service.SyncRecordService -import org.springframework.scheduling.annotation.Async -import org.springframework.stereotype.Service -import java.time.Instant - -/** - * Imports business partner entries from SaaS - */ -@Service -class PartnerImportService( - private val syncRecordService: SyncRecordService, - private val partnerImportPageService: PartnerImportPageService, - private val entityManager: EntityManager -) { - private val logger = KotlinLogging.logger { } - - /** - * Asynchronous version of [importPaginated] - */ - @Async - fun importPaginatedAsync(fromTime: Instant, saveState: String?) { - importPaginated(fromTime, saveState) - } - - /** - * Import SaaS partner records last modified after [fromTime] and with SaaS internal ID greater than [saveState] - * - * Data is imported in a paginated way. On an error during a page import the latest [saveState] ID is saved so that the import can later be resumed - */ - fun importPaginated(fromTime: Instant, saveState: String?) { - logger.info { "Starting SaaS import starting with ID ${saveState}' for modified records from '$fromTime'" } - - var startAfter: String? = saveState - var importedCount = 0 - - do { - try { - val response = partnerImportPageService.import(fromTime, startAfter) - startAfter = response.nextStartAfter - val createdCount = response.legalEntities.created.size + response.sites.created.size + response.addresses.created.size - val updatedCount = response.legalEntities.updated.size + response.sites.updated.size + response.addresses.updated.size - importedCount += createdCount + updatedCount - val progress = importedCount / response.totalElements.toFloat() - syncRecordService.setProgress(SyncType.SAAS_IMPORT, importedCount, progress) - } catch (exception: RuntimeException) { - logger.error(exception) { "Exception encountered on SaaS import" } - syncRecordService.setSynchronizationError( - SyncType.SAAS_IMPORT, - exception.message ?: "No Message", - startAfter - ) - return - } - - //Clear session after each page import to improve JPA performance - entityManager.clear() - } while (startAfter != null) - - syncRecordService.setSynchronizationSuccess(SyncType.SAAS_IMPORT) - - logger.info { "SaaS import finished successfully" } - } -} \ No newline at end of file diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/service/SaasClient.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/service/SaasClient.kt deleted file mode 100644 index 26cc8f3f8..000000000 --- a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/service/SaasClient.kt +++ /dev/null @@ -1,78 +0,0 @@ -/******************************************************************************* - * 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 org.eclipse.tractusx.bpdm.pool.component.saas.service - -import org.eclipse.tractusx.bpdm.common.dto.saas.BusinessPartnerSaas -import org.eclipse.tractusx.bpdm.common.dto.saas.PagedResponseSaas -import org.eclipse.tractusx.bpdm.pool.component.saas.config.SaasAdapterConfigProperties -import org.springframework.stereotype.Service -import org.springframework.web.reactive.function.client.WebClient -import org.springframework.web.reactive.function.client.bodyToMono -import java.time.Instant -import java.time.format.DateTimeFormatter - -@Service -class SaasClient( - private val webClient: WebClient, - private val adapterProperties: SaasAdapterConfigProperties, -) { - - fun readBusinessPartners(modifiedAfter: Instant, startAfter: String?): PagedResponseSaas { - return webClient - .get() - .uri { builder -> - builder - .path(adapterProperties.readBusinessPartnerUrl) - .queryParam("modifiedAfter", toModifiedAfterFormat(modifiedAfter)) - .queryParam("limit", adapterProperties.importLimit) - .queryParam("datasource", adapterProperties.datasource) - .queryParam("featuresOn", "USE_NEXT_START_AFTER", "FETCH_RELATIONS") - if (startAfter != null) builder.queryParam("startAfter", startAfter) - builder.build() - } - .retrieve() - .bodyToMono>() - .block()!! - } - - fun readBusinessPartnersByExternalIds(idValues: Collection): PagedResponseSaas { - if (idValues.isEmpty()) return PagedResponseSaas(limit = 0, total = 0, values = emptyList()) - - return webClient - .get() - .uri { builder -> - builder - .path(adapterProperties.readBusinessPartnerUrl) - .queryParam("limit", idValues.size) - .queryParam("datasource", adapterProperties.datasource) - .queryParam("featuresOn", "USE_NEXT_START_AFTER", "FETCH_RELATIONS") - .queryParam("externalId", idValues.joinToString(",")) - builder.build() - } - .retrieve() - .bodyToMono>() - .block()!! - } - - private fun toModifiedAfterFormat(dateTime: Instant): String { - return DateTimeFormatter.ISO_INSTANT.format(dateTime) - } - -} \ No newline at end of file diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/service/SaasToRequestMapper.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/service/SaasToRequestMapper.kt deleted file mode 100644 index abe47a4de..000000000 --- a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/service/SaasToRequestMapper.kt +++ /dev/null @@ -1,158 +0,0 @@ -/******************************************************************************* - * 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 org.eclipse.tractusx.bpdm.pool.component.saas.service - -import mu.KotlinLogging -import org.eclipse.tractusx.bpdm.common.dto.IdentifierLsaType -import org.eclipse.tractusx.bpdm.common.dto.IdentifierTypeDto -import org.eclipse.tractusx.bpdm.common.dto.response.type.TypeKeyNameDto -import org.eclipse.tractusx.bpdm.common.dto.saas.BusinessPartnerSaas -import org.eclipse.tractusx.bpdm.common.dto.saas.LegalFormSaas -import org.eclipse.tractusx.bpdm.common.dto.saas.TypeKeyNameUrlSaas -import org.eclipse.tractusx.bpdm.common.service.SaasMappings.toLogisticAddressDto -import org.eclipse.tractusx.bpdm.common.service.SaasMappings.toLegalEntityDto -import org.eclipse.tractusx.bpdm.common.service.SaasMappings.toSiteDto -import org.eclipse.tractusx.bpdm.pool.api.model.request.* -import org.eclipse.tractusx.bpdm.pool.component.saas.dto.BusinessPartnerWithBpn -import org.eclipse.tractusx.bpdm.pool.component.saas.dto.BusinessPartnerWithParentBpn -import org.springframework.stereotype.Service - -@Service -class SaasToRequestMapper { - private val logger = KotlinLogging.logger { } - - fun toLegalEntityCreateRequest(partnerWithImportId: BusinessPartnerSaas): LegalEntityPartnerCreateRequest { - return LegalEntityPartnerCreateRequest( - legalEntity = partnerWithImportId.toLegalEntityDto(), - index = partnerWithImportId.externalId - ) - } - - fun toLegalEntityCreateRequestOrNull(partnerWithImportId: BusinessPartnerSaas): LegalEntityPartnerCreateRequest? { - return try { - toLegalEntityCreateRequest(partnerWithImportId) - } catch (e: Throwable) { - logger.info("Reason:", e) - logger.warn { "Business Partner with ID ${partnerWithImportId.externalId} could not be mapped to ${LegalEntityPartnerCreateRequest::class.simpleName}" } - null - } - } - - fun toLegalEntityUpdateRequest(partnerWithBpn: BusinessPartnerWithBpn): LegalEntityPartnerUpdateRequest { - return LegalEntityPartnerUpdateRequest( - bpn = partnerWithBpn.bpn, - legalEntity = partnerWithBpn.partner.toLegalEntityDto() - ) - } - - fun toLegalEntityUpdateRequestOrNull(partnerWithBpn: BusinessPartnerWithBpn): LegalEntityPartnerUpdateRequest? { - return try { - toLegalEntityUpdateRequest(partnerWithBpn) - } catch (_: Throwable) { - logger.warn { "Business Partner with ID ${partnerWithBpn.partner.externalId} could not be mapped to ${LegalEntityPartnerUpdateRequest::class.simpleName}" } - null - } - } - - fun toSiteCreateRequest(partnerWithParent: BusinessPartnerWithParentBpn): SitePartnerCreateRequest { - return SitePartnerCreateRequest( - site = partnerWithParent.partner.toSiteDto(), - bpnParent = partnerWithParent.parentBpn, - index = partnerWithParent.partner.externalId - ) - } - - - fun toSiteCreateRequestOrNull(partnerWithImportId: BusinessPartnerWithParentBpn): SitePartnerCreateRequest? { - return try { - toSiteCreateRequest(partnerWithImportId) - } catch (_: Throwable) { - logger.warn { "Business Partner with ID ${partnerWithImportId.partner.externalId} could not be mapped to ${SitePartnerCreateRequest::class.simpleName}" } - null - } - } - - fun toSiteUpdateRequest(partnerWithBpn: BusinessPartnerWithBpn): SitePartnerUpdateRequest { - return SitePartnerUpdateRequest( - bpn = partnerWithBpn.bpn, - site = partnerWithBpn.partner.toSiteDto() - ) - } - - fun toSiteUpdateRequestOrNull(partnerWithBpn: BusinessPartnerWithBpn): SitePartnerUpdateRequest? { - return try { - toSiteUpdateRequest(partnerWithBpn) - } catch (_: Throwable) { - logger.warn { "Business Partner with ID ${partnerWithBpn.partner.externalId} could not be mapped to ${SitePartnerUpdateRequest::class.simpleName}" } - null - } - } - - fun toAddressCreateRequest(partnerWithParent: BusinessPartnerWithParentBpn): AddressPartnerCreateRequest { - return AddressPartnerCreateRequest( - address = partnerWithParent.partner.toLogisticAddressDto(), - bpnParent = partnerWithParent.parentBpn, - index = partnerWithParent.partner.externalId - ) - } - - fun toAddressCreateRequestOrNull(partnerWithParent: BusinessPartnerWithParentBpn): AddressPartnerCreateRequest? { - return try { - toAddressCreateRequest(partnerWithParent) - } catch (_: Throwable) { - logger.warn { "Business Partner with ID ${partnerWithParent.partner.externalId} could not be mapped to ${AddressPartnerCreateRequest::class.simpleName}" } - null - } - } - - fun toAddressUpdateRequest(partnerWithBpn: BusinessPartnerWithBpn): AddressPartnerUpdateRequest { - return AddressPartnerUpdateRequest( - bpn = partnerWithBpn.bpn, - address = partnerWithBpn.partner.toLogisticAddressDto() - ) - } - - fun toAddressUpdateRequestOrNull(partnerWithBpn: BusinessPartnerWithBpn): AddressPartnerUpdateRequest? { - return try { - toAddressUpdateRequest(partnerWithBpn) - } catch (_: Throwable) { - logger.warn { "Business Partner with ID ${partnerWithBpn.partner.externalId} could not be mapped to ${AddressPartnerUpdateRequest::class.simpleName}" } - null - } - } - - - fun toRequest(idType: TypeKeyNameUrlSaas): TypeKeyNameDto { - return TypeKeyNameDto(idType.technicalKey!!, idType.name ?: "") - } - - fun toRequest(legalForm: LegalFormSaas, partner: BusinessPartnerSaas): LegalFormRequest { - return LegalFormRequest( - technicalKey = legalForm.technicalKey!!, - name = legalForm.name!!, - abbreviation = legalForm.mainAbbreviation - ) - } - - fun toIdentifierTypeDto(it: TypeKeyNameUrlSaas, lsaType: IdentifierLsaType): IdentifierTypeDto { - return IdentifierTypeDto(it.technicalKey!!, lsaType, it.name ?: it.technicalKey!!, listOf()) - } - -} \ No newline at end of file diff --git a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/config/SaasEnabledConfig.kt b/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/config/SaasEnabledConfig.kt deleted file mode 100644 index fd1473392..000000000 --- a/bpdm-pool/src/main/kotlin/org/eclipse/tractusx/bpdm/pool/config/SaasEnabledConfig.kt +++ /dev/null @@ -1,33 +0,0 @@ -/******************************************************************************* - * 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 org.eclipse.tractusx.bpdm.pool.config - -import org.eclipse.tractusx.bpdm.pool.component.saas.SaasAdapterConfig -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty -import org.springframework.context.annotation.Configuration -import org.springframework.context.annotation.Import - -@Configuration -@ConditionalOnProperty( - value = ["bpdm.saas.enabled"], - havingValue = "true", - matchIfMissing = false) -@Import(SaasAdapterConfig::class) -class SaasEnabledConfig \ No newline at end of file diff --git a/bpdm-pool/src/main/resources/application-saas.properties b/bpdm-pool/src/main/resources/application-saas.properties deleted file mode 100644 index 3cd9a7018..000000000 --- a/bpdm-pool/src/main/resources/application-saas.properties +++ /dev/null @@ -1,34 +0,0 @@ -################################################################################ -# 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 -################################################################################ -bpdm.saas.enabled=true -bpdm.saas.host=${BPDM_SAAS_HOST} -bpdm.saas.api=data-exchange/rest/v4 -#Storage ID to import from -bpdm.saas.storage=${BPDM_SAAS_STORAGE} -#Datasource ID for records to import -bpdm.saas.datasource=${BPDM_SAAS_DATASOURCE} -bpdm.saas.api-key=${BPDM_SAAS_KEY} -bpdm.saas.import-limit=100 -# Special value "-" disables scheduling. See javadoc of org.springframework.scheduling.support.CronExpression.parse for format. -bpdm.saas.import-scheduler-cron-expr=- -bpdm.saas.export-page-size=100 -# Whether to import records that have a BPN in SaaS (but not in the Pool) as new records -# Otherwise those records are ignored with a warning -bpdm.saas.treat-invalid-bpn-as-new=false -bpdm.saas.request-size-limit=500 diff --git a/bpdm-pool/src/test/kotlin/org/eclipse/tractusx/bpdm/pool/component/opensearch/InvalidIndexStartupIT.kt b/bpdm-pool/src/test/kotlin/org/eclipse/tractusx/bpdm/pool/component/opensearch/InvalidIndexStartupIT.kt index 2003ef552..afb00029e 100644 --- a/bpdm-pool/src/test/kotlin/org/eclipse/tractusx/bpdm/pool/component/opensearch/InvalidIndexStartupIT.kt +++ b/bpdm-pool/src/test/kotlin/org/eclipse/tractusx/bpdm/pool/component/opensearch/InvalidIndexStartupIT.kt @@ -120,16 +120,22 @@ class InvalidIndexStartupIT @Autowired constructor( assertThat(getResponse.found()).isFalse //import a business partner to DB - testHelpers.importAndGetResponse(listOf(SaasValues.legalEntity1), webTestClient, wireMockServer) - + testHelpers.createTestMetadata() + testHelpers.createBusinessPartnerStructure( + listOf( + LegalEntityStructureRequest( + legalEntity = RequestValues.legalEntityCreate1, + ) + ) + ) //export to index and check whether the imported business partner can be found as normal testHelpers.startSyncAndAwaitSuccess(webTestClient, EndpointValues.OPENSEARCH_SYNC_PATH) val searchResult = poolClient.legalEntities().getLegalEntities( - LegalEntityPropertiesSearchRequest.EmptySearchRequest - , AddressPropertiesSearchRequest.EmptySearchRequest - , SitePropertiesSearchRequest.EmptySearchRequest - , PaginationRequest() + LegalEntityPropertiesSearchRequest.EmptySearchRequest, + AddressPropertiesSearchRequest.EmptySearchRequest, + SitePropertiesSearchRequest.EmptySearchRequest, + PaginationRequest() ) assertThat(searchResult.content).isNotEmpty diff --git a/bpdm-pool/src/test/kotlin/org/eclipse/tractusx/bpdm/pool/component/opensearch/ValidIndexStartupIT.kt b/bpdm-pool/src/test/kotlin/org/eclipse/tractusx/bpdm/pool/component/opensearch/ValidIndexStartupIT.kt index 64f16e9cf..4deae832e 100644 --- a/bpdm-pool/src/test/kotlin/org/eclipse/tractusx/bpdm/pool/component/opensearch/ValidIndexStartupIT.kt +++ b/bpdm-pool/src/test/kotlin/org/eclipse/tractusx/bpdm/pool/component/opensearch/ValidIndexStartupIT.kt @@ -77,13 +77,26 @@ class ValidIndexStartupIT @Autowired constructor( @DirtiesContext fun setupIndexForNextTest() { testHelpers.truncateDbTables() + testHelpers.createTestMetadata() //Clear and set up a fresh valid OpenSearch context // webTestClient.invokeDeleteEndpointWithoutResponse(EndpointValues.OPENSEARCH_SYNC_PATH) poolClient.opensearch().clear() //Import values to DB - val partnersToImport = listOf(SaasValues.legalEntity1, SaasValues.legalEntity2, SaasValues.legalEntity3) - testHelpers.importAndGetResponse(partnersToImport, webTestClient, wireMockServer) + testHelpers.createBusinessPartnerStructure( + listOf( + LegalEntityStructureRequest( + legalEntity = RequestValues.legalEntityCreate1, + ), + LegalEntityStructureRequest( + legalEntity = RequestValues.legalEntityCreate2, + ), + LegalEntityStructureRequest( + legalEntity = RequestValues.legalEntityCreate3, + ) + ) + ) + //Export to OpenSearch index testHelpers.startSyncAndAwaitSuccess(webTestClient, EndpointValues.OPENSEARCH_SYNC_PATH) //Make sure entries are indeed there diff --git a/bpdm-pool/src/test/kotlin/org/eclipse/tractusx/bpdm/pool/component/opensearch/controller/OpenSearchControllerIT.kt b/bpdm-pool/src/test/kotlin/org/eclipse/tractusx/bpdm/pool/component/opensearch/controller/OpenSearchControllerIT.kt index 488ea0b95..e72cda537 100644 --- a/bpdm-pool/src/test/kotlin/org/eclipse/tractusx/bpdm/pool/component/opensearch/controller/OpenSearchControllerIT.kt +++ b/bpdm-pool/src/test/kotlin/org/eclipse/tractusx/bpdm/pool/component/opensearch/controller/OpenSearchControllerIT.kt @@ -34,8 +34,8 @@ import org.eclipse.tractusx.bpdm.pool.api.model.request.LegalEntityPropertiesSea import org.eclipse.tractusx.bpdm.pool.api.model.request.SitePropertiesSearchRequest import org.eclipse.tractusx.bpdm.pool.api.model.response.LegalEntityMatchResponse import org.eclipse.tractusx.bpdm.pool.component.opensearch.impl.service.OpenSearchSyncStarterService -import org.eclipse.tractusx.bpdm.pool.component.saas.config.SaasAdapterConfigProperties -import org.eclipse.tractusx.bpdm.pool.component.saas.service.ImportStarterService + + import org.eclipse.tractusx.bpdm.pool.util.* import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -59,15 +59,18 @@ import org.springframework.test.web.reactive.server.WebTestClient @ContextConfiguration(initializers = [PostgreSQLContextInitializer::class, OpenSearchContextInitializer::class]) class OpenSearchControllerIT @Autowired constructor( private val webTestClient: WebTestClient, - private val importService: ImportStarterService, private val openSearchSyncService: OpenSearchSyncStarterService, - private val saasAdapterConfigProperties: SaasAdapterConfigProperties, private val objectMapper: ObjectMapper, private val testHelpers: TestHelpers, private val poolClient: PoolClientImpl ) { companion object { + // Configuration properties of Saas mock + private val exchangeApiUrl: String = "data-exchange/rest/v4" + private val storage: String = "storage_id" + val readBusinessPartnerUrl = "/${exchangeApiUrl}/storages/${storage}/businesspartners" + @RegisterExtension var wireMockServer: WireMockExtension = WireMockExtension.newInstance() .options(WireMockConfiguration.wireMockConfig().dynamicPort()) @@ -101,15 +104,28 @@ class OpenSearchControllerIT @Autowired constructor( ) wireMockServer.stubFor( - WireMock.get(WireMock.urlPathMatching(saasAdapterConfigProperties.readBusinessPartnerUrl)) + WireMock.get(WireMock.urlPathMatching(readBusinessPartnerUrl)) .willReturn( WireMock.aResponse() .withHeader("Content-Type", "application/json") .withBody(objectMapper.writeValueAsString(importCollection)) ) ) + testHelpers.createTestMetadata() + testHelpers.createBusinessPartnerStructure( + listOf( + LegalEntityStructureRequest( + legalEntity = RequestValues.legalEntityCreate1, + ), + LegalEntityStructureRequest( + legalEntity = RequestValues.legalEntityCreate2, + ), + LegalEntityStructureRequest( + legalEntity = RequestValues.legalEntityCreate3, + ) + ) + ) - importService.import() } diff --git a/bpdm-pool/src/test/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/controller/SaasControllerImportIT.kt b/bpdm-pool/src/test/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/controller/SaasControllerImportIT.kt deleted file mode 100644 index 33ac33be9..000000000 --- a/bpdm-pool/src/test/kotlin/org/eclipse/tractusx/bpdm/pool/component/saas/controller/SaasControllerImportIT.kt +++ /dev/null @@ -1,665 +0,0 @@ -/******************************************************************************* - * 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 org.eclipse.tractusx.bpdm.pool.component.saas.controller - -import com.fasterxml.jackson.databind.ObjectMapper -import com.github.tomakehurst.wiremock.client.WireMock.* -import com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig -import com.github.tomakehurst.wiremock.junit5.WireMockExtension -import com.github.tomakehurst.wiremock.stubbing.Scenario -import org.assertj.core.api.Assertions.assertThat -import org.eclipse.tractusx.bpdm.common.dto.request.AddressPartnerBpnSearchRequest -import org.eclipse.tractusx.bpdm.common.dto.request.PaginationRequest -import org.eclipse.tractusx.bpdm.common.dto.request.SiteBpnSearchRequest -import org.eclipse.tractusx.bpdm.common.dto.response.* -import org.eclipse.tractusx.bpdm.common.dto.saas.* -import org.eclipse.tractusx.bpdm.pool.Application -import org.eclipse.tractusx.bpdm.pool.api.client.PoolClientImpl -import org.eclipse.tractusx.bpdm.pool.component.saas.config.SaasAdapterConfigProperties -import org.eclipse.tractusx.bpdm.pool.config.BpnConfigProperties -import org.eclipse.tractusx.bpdm.pool.repository.ImportEntryRepository -import org.eclipse.tractusx.bpdm.pool.util.* -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.test.web.reactive.server.WebTestClient -import java.time.Instant - - -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = [Application::class, TestHelpers::class]) -@ActiveProfiles("test") -@ContextConfiguration(initializers = [PostgreSQLContextInitializer::class]) -class SaasControllerImportIT @Autowired constructor( - private val webTestClient: WebTestClient, - bpnConfigProperties: BpnConfigProperties, - private val objectMapper: ObjectMapper, - private val testHelpers: TestHelpers, - private val saasAdapterConfigProperties: SaasAdapterConfigProperties, - private val importEntryRepository: ImportEntryRepository, - private val poolClient: PoolClientImpl -) { - companion object { - @RegisterExtension - var wireMockServer: WireMockExtension = WireMockExtension.newInstance() - .options(wireMockConfig().dynamicPort()) - .build() - - @JvmStatic - @DynamicPropertySource - fun configureProperties(registry: DynamicPropertyRegistry) { - registry.add("bpdm.saas.host") { wireMockServer.baseUrl() } - } - } - - private val legalEntityImportId1 = "Legal Entity Import ID 1" - private val legalEntityImportId2 = "Legal Entity Import ID 2" - private val legalEntityImportId3 = "Legal Entity Import ID 3" - - private val siteImportId1 = "Site Import ID 1" - private val siteImportId2 = "Site Import ID 2" - private val siteImportId3 = "Site Import ID 3" - - private val addressImportId1 = "Address Import ID 1" - private val addressImportId2 = "Address Import ID 2" - private val addressImportId3 = "Address Import ID 3" - - private val importReadyLegalEntity1 = SaasValues.legalEntity1.copy(externalId = legalEntityImportId1) - private val importReadyLegalEntity2 = SaasValues.legalEntity2.copy(externalId = legalEntityImportId2) - private val importReadyLegalEntity3 = SaasValues.legalEntity3.copy(externalId = legalEntityImportId3) - - private val importReadySite1 = SaasValues.site1.copy(externalId = siteImportId1) - private val importReadySite2 = SaasValues.site2.copy(externalId = siteImportId2) - private val importReadySite3 = SaasValues.site3.copy(externalId = siteImportId3) - - private val importReadyAddress1 = SaasValues.addressPartner1.copy(externalId = addressImportId1) - private val importReadyAddress2 = SaasValues.addressPartner2.copy(externalId = addressImportId2) - private val importReadyAddress3 = SaasValues.addressPartner3.copy(externalId = addressImportId3) - - private val idTypeBpn = TypeKeyNameUrlSaas(saasAdapterConfigProperties.bpnKey, bpnConfigProperties.name, "") - private val issuerBpn = TypeKeyNameUrlSaas(name = bpnConfigProperties.agencyName) - private val statusBpn = TypeKeyNameSaas("UNKNOWN", "Unknown") - - @BeforeEach - fun beforeEach() { - testHelpers.truncateDbTables() - testHelpers.createTestMetadata() - wireMockServer.resetAll() - } - - /** - * Given new partners of type legal entity in SaaS - * When Import from SaaS - * Then partners imported - */ - @Test - fun `import new legal entity partners`() { - - val partnersToImport = listOf( - importReadyLegalEntity1, - importReadyLegalEntity2, - importReadyLegalEntity3 - ) - - val bpns = importPartners(partnersToImport) - - val partnersExpected = listOf( - ResponseValues.legalEntity1, - ResponseValues.legalEntity2, - ResponseValues.legalEntity3 - ) - - val actualPartners = getLegalEntities(bpns) - - assertLegalEntityResponseEquals(actualPartners, partnersExpected) - } - - /** - * Given new partners of type site in SaaS - * When Import from SaaS - * Then partners imported - */ - @Test - fun `import new site partners`() { - - //Import the given parent legal entities first - val legalEntityParents = listOf( - importReadyLegalEntity1, - importReadyLegalEntity2 - ) - - val bpnLs = importPartners(legalEntityParents) - val bpnL1 = bpnLs[0] - val bpnL2 = bpnLs[1] - - //Now import sites - val sitesToImport = listOf( - importReadySite1.copyWithParent(legalEntityImportId1), - importReadySite2.copyWithParent(legalEntityImportId2), - importReadySite3.copyWithParent(legalEntityImportId3) //not created yet - ) - - val bpnSs = importPartners(sitesToImport, legalEntityParents.plus(importReadyLegalEntity3)) - val createdParentBpn = getImportedBpns(listOf(importReadyLegalEntity3)).single() - - //Assert actual created sites with expected - val expectedSites = listOf( - ResponseValues.site1, - ResponseValues.site2, - ResponseValues.site3.copy(bpnLegalEntity = createdParentBpn) - ) - - val actualSites = getSites(bpnSs).content - testHelpers.assertRecursively(actualSites).ignoringFieldsMatchingRegexes(".*bpn").isEqualTo(expectedSites) - } - - /** - * Given new partners of type address in SaaS - * When Import from SaaS - * Then partners imported - */ - @Test - fun `import new address partners`() { - - val importReadyAddress1 = SaasValues.addressPartnerSaas1.copy(externalId = addressImportId1) - val importReadyAddress2 = SaasValues.addressPartnerSaas2.copy(externalId = addressImportId2) - val importReadyAddress3 = SaasValues.addressPartnerSaas3.copy(externalId = addressImportId3) - - //Import the given parent legal entities first - val legalEntityParents = listOf( - importReadyLegalEntity1, - importReadyLegalEntity2 - ) - - val bpnLs = importPartners(legalEntityParents) - val bpnL2 = bpnLs[1] - - //Import Parent Site - val importedSiteParent = importReadySite1.copyWithParent(importReadyLegalEntity1.externalId!!) - - val importedSiteBpn = importPartners(listOf(importedSiteParent), listOf(importReadyLegalEntity1)).single() - - val notImportedSiteParent = importReadySite3.copyWithParent(importReadyLegalEntity3.externalId!!) - - //Import addresses - val addressesToImport = listOf( - importReadyAddress1.copyWithParent(importedSiteParent.externalId!!), - importReadyAddress2.copyWithParent(importReadyLegalEntity2.externalId!!), - importReadyAddress3.copyWithParent(notImportedSiteParent.externalId!!) //doesn't exist yet - ) - - val addressBpns = importPartners( - addressesToImport, - listOf(importedSiteParent, importReadyLegalEntity2, notImportedSiteParent), - listOf(importReadyLegalEntity3) - ) - - val notImportedSiteParentBpn = getImportedBpns(listOf(notImportedSiteParent)).single() - - //Assert actual with expected - val actual = getAddresses(addressBpns).content - val expected = listOf( - ResponseValues.addressPartner1, - ResponseValues.addressPartner2, - ResponseValues.addressPartner3 - ) - - testHelpers.assertRecursively(actual.toList().sortedBy { it.physicalPostalAddress.industrialZone }) - .ignoringFieldsMatchingRegexes(".*bpn").isEqualTo(actual.toList().sortedBy { it.physicalPostalAddress.industrialZone }) - } - - /** - * Given new partners - * When Import partners multiple times - * Then no duplicate partners - */ - @Test - fun importPartnersMultipleTimes() { - val partnersToImport = listOf( - importReadyLegalEntity1, - importReadyLegalEntity2, - importReadyLegalEntity3 - ) - - val partnersExpected = listOf( - ResponseValues.legalEntity1, - ResponseValues.legalEntity2, - ResponseValues.legalEntity3 - ) - - //Import partner first time and check whether successfully imported - val bpnsFirst = importPartners(partnersToImport) - val actualFirst = getLegalEntities(bpnsFirst) - assertLegalEntityResponseEquals(actualFirst, partnersExpected) - - //Import partner second time and check for no duplicates or other changes - val bpnsSecond = importPartners(partnersToImport) - val actualSecond = getLegalEntities(bpnsSecond) - testHelpers.assertRecursively(bpnsFirst).isEqualTo(bpnsSecond) - assertLegalEntityResponseEquals(actualSecond, partnersExpected) - } - - /** - * Given new partners in SaaS - * When import with pagination - * Then partners imported - */ - @Test - fun importNewPartnersWithPagination() { - val expectedPartners = listOf( - ResponseValues.legalEntity1, - ResponseValues.legalEntity2, - ResponseValues.legalEntity3 - ) - - val page1 = PagedResponseSaas( - 1, - null, - importReadyLegalEntity2.id, - 1, - listOf(importReadyLegalEntity1) - ) - - wireMockServer.stubFor( - get(urlPathMatching(saasAdapterConfigProperties.readBusinessPartnerUrl)) - .withQueryParam("featuresOn", equalTo("USE_NEXT_START_AFTER")) - .withQueryParam("startAfter", absent()) - .willReturn( - aResponse() - .withHeader("Content-Type", "application/json") - .withBody(objectMapper.writeValueAsString(page1)) - ) - ) - - val page2 = PagedResponseSaas( - 1, - null, - importReadyLegalEntity3.id, - 1, - listOf(importReadyLegalEntity2) - ) - - wireMockServer.stubFor( - get(urlPathMatching(saasAdapterConfigProperties.readBusinessPartnerUrl)) - .withQueryParam("featuresOn", equalTo("USE_NEXT_START_AFTER")) - .withQueryParam("startAfter", equalTo(page1.nextStartAfter)) - .willReturn( - aResponse() - .withHeader("Content-Type", "application/json") - .withBody(objectMapper.writeValueAsString(page2)) - ) - ) - - val page3 = PagedResponseSaas( - 1, - null, - null, - 1, - listOf(importReadyLegalEntity3) - ) - - wireMockServer.stubFor( - get(urlPathMatching(saasAdapterConfigProperties.readBusinessPartnerUrl)) - .withQueryParam("featuresOn", equalTo("USE_NEXT_START_AFTER")) - .withQueryParam("startAfter", equalTo(page2.nextStartAfter)) - .willReturn( - aResponse() - .withHeader("Content-Type", "application/json") - .withBody(objectMapper.writeValueAsString(page3)) - ) - ) - - testHelpers.startSyncAndAwaitSuccess(webTestClient, EndpointValues.SAAS_SYNCH_PATH) - val bpns = getImportedBpns(listOf(importReadyLegalEntity1, importReadyLegalEntity2, importReadyLegalEntity3)) - val actual = getLegalEntities(bpns) - - assertLegalEntityResponseEquals(actual, expectedPartners) - } - - /* - Given paginated import of new legal entities in error state - When resume import - Then remaining legal entities imported - */ - @Test - fun `resume paginated on error`() { - - val page1 = PagedResponseSaas( - 1, - null, - importReadyLegalEntity2.id, - 1, - listOf(importReadyLegalEntity1) - ) - - val page2 = PagedResponseSaas( - 1, - null, - importReadyLegalEntity3.id, - 1, - listOf(importReadyLegalEntity2) - ) - - val page3 = PagedResponseSaas( - 1, - null, - null, - 1, - listOf(importReadyLegalEntity3) - ) - - //Create mock response that lead to error on second page and on second attempt normal behaviour - val scenarioName = "Import Error Scenario" - val secondTryState = "Second Try" - - wireMockServer.stubFor( - get(urlPathMatching(saasAdapterConfigProperties.readBusinessPartnerUrl)) - .inScenario(scenarioName) - .whenScenarioStateIs(Scenario.STARTED) - .withQueryParam("featuresOn", equalTo("USE_NEXT_START_AFTER")) - .withQueryParam("startAfter", absent()) - .willReturn( - aResponse() - .withHeader("Content-Type", "application/json") - .withBody(objectMapper.writeValueAsString(page1)) - ) - ) - - wireMockServer.stubFor( - get(urlPathMatching(saasAdapterConfigProperties.readBusinessPartnerUrl)) - .inScenario(scenarioName) - .whenScenarioStateIs(Scenario.STARTED) - .withQueryParam("featuresOn", equalTo("USE_NEXT_START_AFTER")) - .withQueryParam("startAfter", equalTo(page1.nextStartAfter)) - .willReturn(aResponse().withStatus(500)) - .willSetStateTo(secondTryState) - ) - - wireMockServer.stubFor( - get(urlPathMatching(saasAdapterConfigProperties.readBusinessPartnerUrl)) - .inScenario(scenarioName) - .whenScenarioStateIs(secondTryState) - .withQueryParam("featuresOn", equalTo("USE_NEXT_START_AFTER")) - .withQueryParam("startAfter", equalTo(page1.nextStartAfter)) - .willReturn( - aResponse() - .withHeader("Content-Type", "application/json") - .withBody(objectMapper.writeValueAsString(page2)) - ) - ) - - wireMockServer.stubFor( - get(urlPathMatching(saasAdapterConfigProperties.readBusinessPartnerUrl)) - .inScenario(scenarioName) - .whenScenarioStateIs(secondTryState) - .withQueryParam("featuresOn", equalTo("USE_NEXT_START_AFTER")) - .withQueryParam("startAfter", equalTo(page2.nextStartAfter)) - .willReturn( - aResponse() - .withHeader("Content-Type", "application/json") - .withBody(objectMapper.writeValueAsString(page3)) - ) - ) - - //First one should fail - testHelpers.startSyncAndAwaitError(webTestClient, EndpointValues.SAAS_SYNCH_PATH) - //Second one should go through - testHelpers.startSyncAndAwaitSuccess(webTestClient, EndpointValues.SAAS_SYNCH_PATH) - - //Check whether all legal entities have been created - val expectedPartners = listOf( - ResponseValues.legalEntity1, - ResponseValues.legalEntity2, - ResponseValues.legalEntity3 - ) - val bpns = getImportedBpns(listOf(importReadyLegalEntity1, importReadyLegalEntity2, importReadyLegalEntity3)) - val actual = getLegalEntities(bpns) - - assertLegalEntityResponseEquals(actual, expectedPartners) - } - - /** - * Given updates on legal entities which have been import before - * When import partners - * Then partners are updated - */ - @Test - fun `update modified legal entities`() { - //First create legal entities with BPN in the system - val partnersToImport = listOf( - importReadyLegalEntity1, - importReadyLegalEntity2, - importReadyLegalEntity3 - ) - - val bpns = importPartners(partnersToImport) - val bpnL1 = bpns[0] - val bpnL2 = bpns[1] - val bpnL3 = bpns[2] - - //Next swap BPN and externalId (Import ID) to simulate update of each field value - val partnersToUpdate = listOf( - importReadyLegalEntity1.copyWithBpn(bpnL2).copy(externalId = importReadyLegalEntity2.externalId), - importReadyLegalEntity2.copyWithBpn(bpnL3).copy(externalId = importReadyLegalEntity3.externalId), - importReadyLegalEntity3.copy(externalId = importReadyLegalEntity1.externalId) //Only set external ID here to simulate a record which has no BPN yet - ) - - importPartners(partnersToUpdate) - - //assert actual is expected - val expectedPartners = listOf( - ResponseValues.legalEntity1.copy(bpn = bpnL2), - ResponseValues.legalEntity2.copy(bpn = bpnL3), - ResponseValues.legalEntity3.copy(bpn = bpnL1) - ) - val actual = getLegalEntities(bpns) - testHelpers.assertRecursively(actual).ignoringFieldsMatchingRegexes(".*currentness").isEqualTo(expectedPartners) - } - - /** - * Given updated partners of type site in SaaS - * When Import from SaaS - * Then partners updated - */ - @Test - fun `import updated site partners`() { - //First create sites - val sitesToCreate = listOf( - importReadySite1.copyWithParent(importReadyLegalEntity1.externalId!!), - importReadySite2.copyWithParent(importReadyLegalEntity2.externalId!!), - importReadySite3.copyWithParent(importReadyLegalEntity3.externalId!!) - ) - - val parents = listOf(importReadyLegalEntity1, importReadyLegalEntity2, importReadyLegalEntity3) - - val bpns = importPartners(sitesToCreate, parents) - val bpnS1 = bpns[0] - val bpnS2 = bpns[1] - val bpnS3 = bpns[2] - - val bpnLs = getImportedBpns(parents) - val bpnL1 = bpnLs[0] - val bpnL2 = bpnLs[1] - val bpnL3 = bpnLs[2] - - //Next swap BPN and externalId (Import ID) to simulate update of each field value - val partnersToUpdate = listOf( - importReadySite1.copyWithBpn(bpnS2).copy(externalId = importReadySite2.externalId), - importReadySite2.copyWithBpn(bpnS3).copy(externalId = importReadySite3.externalId), - importReadySite3.copy(externalId = importReadySite1.externalId) //Only set external ID here to simulate a record which has no BPN yet - ) - - importPartners(partnersToUpdate) - - //assert actual is expected - val expectedPartners = listOf( - ResponseValues.site1.copy(bpn = bpnS2, bpnLegalEntity = bpnL2), - ResponseValues.site2.copy(bpn = bpnS3, bpnLegalEntity = bpnL3), - ResponseValues.site3.copy(bpn = bpnS1, bpnLegalEntity = bpnL1), - ) - val actual = getSites(bpns).content - testHelpers.assertRecursively(actual).isEqualTo(expectedPartners) - } - - /** - * Given updated partners of type address in SaaS - * When Import from SaaS - * Then partners imported - */ - @Test - fun `import updated address partners`() { - //First create sites - val addressesToCreate = listOf( - SaasValues.addressPartnerSaas1.copy(externalId = addressImportId1).copyWithParent(importReadyLegalEntity1.externalId!!), - SaasValues.addressPartnerSaas2.copy(externalId = addressImportId2).copyWithParent(importReadyLegalEntity2.externalId!!), - SaasValues.addressPartnerSaas3.copy(externalId = addressImportId3).copyWithParent(importReadyLegalEntity3.externalId!!) - ) - - val parents = listOf(importReadyLegalEntity1, importReadyLegalEntity2, importReadyLegalEntity3) - - val bpns = importPartners(addressesToCreate, parents) - val bpnA1 = bpns[0] - val bpnA2 = bpns[1] - val bpnA3 = bpns[2] - - val bpnLs = getImportedBpns(parents) - val bpnL1 = bpnLs[0] - val bpnL2 = bpnLs[1] - val bpnL3 = bpnLs[2] - - //Next swap BPN and externalId (Import ID) to simulate update of each field value - val partnersToUpdate = listOf( - importReadyAddress1.copyWithBpn(bpnA2).copy(externalId = importReadyAddress2.externalId), - importReadyAddress2.copyWithBpn(bpnA3).copy(externalId = importReadyAddress3.externalId), - importReadyAddress3.copy(externalId = importReadyAddress1.externalId) //Only set external ID here to simulate a record which has no BPN yet - ) - - importPartners(partnersToUpdate) - - //assert actual is expected - val expectedPartners = listOf( - ResponseValues.addressPartner1.copy(bpn = bpnA2), - ResponseValues.addressPartner2.copy(bpn = bpnA3), - ResponseValues.addressPartner3.copy(bpn = bpnA1) - ) - val actual = getAddresses(bpns).content - testHelpers.assertRecursively(actual.toList().sortedBy { it.physicalPostalAddress.industrialZone }) - .ignoringFieldsMatchingRegexes(".*bpn").isEqualTo(actual.toList().sortedBy { it.physicalPostalAddress.industrialZone }) - } - - private fun assertLegalEntityResponseEquals( - actualPartners: Collection, - expectedPartners: Collection - ) { - assertThat(actualPartners) - .usingRecursiveComparison() - .ignoringFieldsMatchingRegexes(".*uuid", ".*bpn") - .ignoringAllOverriddenEquals() - .ignoringCollectionOrder() - .ignoringFieldsOfTypes(Instant::class.java) - .isEqualTo(expectedPartners) - } - - private fun getImportedBpns(partners: List): List { - val importEntries = importEntryRepository.findByImportIdentifierIn(partners.map { it.externalId!! }) - return partners.map { - importEntries.find { entry -> entry.importIdentifier == it.externalId }?.bpn!! - } - } - - private fun getLegalEntities(bpns: Collection): Collection = - poolClient.legalEntities().searchSites(bpns).body!! - - private fun getSites(bpns: Collection): PageResponse = - poolClient.sites().searchSites(SiteBpnSearchRequest(sites = bpns), PaginationRequest()) - - private fun getAddresses(bpns: Collection): PageResponse = poolClient.addresses().searchAddresses(AddressPartnerBpnSearchRequest(addresses = bpns), PaginationRequest()) - - private fun importPartners( - partnersToImport: List, - parents: Collection = emptyList(), - parentsOfParents: Collection = emptyList() - ): List { - wireMockServer.resetAll() - - val importScenario = "IMPORT" - val queriedNewState = "QUERIED_NEW" - val firstCallParentState = "FIRST_CALL_PARENTS" - - //Mock new SaaS business partners to import - wireMockServer.stubFor( - get(urlPathMatching(saasAdapterConfigProperties.readBusinessPartnerUrl)) - .inScenario(importScenario) - .whenScenarioStateIs(Scenario.STARTED) - .willReturn( - aResponse() - .withHeader("Content-Type", "application/json") - .withBody(objectMapper.writeValueAsString(partnersToImport.toPageResponse())) - ) - .willSetStateTo(queriedNewState) - ) - - //Mock parents that should be returned first time - wireMockServer.stubFor( - get(urlPathMatching(saasAdapterConfigProperties.readBusinessPartnerUrl)) - .inScenario(importScenario) - .whenScenarioStateIs(queriedNewState) - .willReturn( - aResponse() - .withHeader("Content-Type", "application/json") - .withBody(objectMapper.writeValueAsString(parents.toPageResponse())) - ) - .willSetStateTo(firstCallParentState) - ) - - //Mock parents that should be returned second time - wireMockServer.stubFor( - get(urlPathMatching(saasAdapterConfigProperties.readBusinessPartnerUrl)) - .inScenario(importScenario) - .whenScenarioStateIs(firstCallParentState) - .willReturn( - aResponse() - .withHeader("Content-Type", "application/json") - .withBody(objectMapper.writeValueAsString(parentsOfParents.toPageResponse())) - ) - ) - - //Start import - testHelpers.startSyncAndAwaitSuccess(webTestClient, EndpointValues.SAAS_SYNCH_PATH) - - //return the BPNs of imported business partners in same order as given - return getImportedBpns(partnersToImport) - } - - private fun BusinessPartnerSaas.copyWithBpn(bpn: String) = - copy(identifiers = identifiers.plus(IdentifierSaas(idTypeBpn, bpn, issuerBpn, statusBpn))) - - private fun BusinessPartnerSaas.copyWithParent(parentId: String) = - copy(relations = listOf(SaasValues.parentRelation.copy(startNode = parentId))) - - private fun Collection.toPageResponse() = - PagedResponseSaas(size, null, null, size, this) - -} \ No newline at end of file diff --git a/bpdm-pool/src/test/kotlin/org/eclipse/tractusx/bpdm/pool/util/TestHelpers.kt b/bpdm-pool/src/test/kotlin/org/eclipse/tractusx/bpdm/pool/util/TestHelpers.kt index f55a2d54c..0c57f0340 100644 --- a/bpdm-pool/src/test/kotlin/org/eclipse/tractusx/bpdm/pool/util/TestHelpers.kt +++ b/bpdm-pool/src/test/kotlin/org/eclipse/tractusx/bpdm/pool/util/TestHelpers.kt @@ -19,28 +19,16 @@ package org.eclipse.tractusx.bpdm.pool.util -import com.fasterxml.jackson.databind.ObjectMapper -import com.github.tomakehurst.wiremock.client.WireMock -import com.github.tomakehurst.wiremock.junit5.WireMockExtension import jakarta.persistence.EntityManager import jakarta.persistence.EntityManagerFactory import org.assertj.core.api.Assertions import org.assertj.core.api.RecursiveComparisonAssert -import org.eclipse.tractusx.bpdm.common.dto.request.PaginationRequest -import org.eclipse.tractusx.bpdm.common.dto.response.PageResponse -import org.eclipse.tractusx.bpdm.common.dto.saas.BusinessPartnerSaas -import org.eclipse.tractusx.bpdm.common.dto.saas.PagedResponseSaas import org.eclipse.tractusx.bpdm.pool.api.client.PoolClientImpl import org.eclipse.tractusx.bpdm.pool.api.model.SyncStatus -import org.eclipse.tractusx.bpdm.pool.api.model.request.AddressPropertiesSearchRequest import org.eclipse.tractusx.bpdm.pool.api.model.request.IdentifiersSearchRequest -import org.eclipse.tractusx.bpdm.pool.api.model.request.LegalEntityPropertiesSearchRequest -import org.eclipse.tractusx.bpdm.pool.api.model.request.SitePropertiesSearchRequest import org.eclipse.tractusx.bpdm.pool.api.model.response.ErrorCode import org.eclipse.tractusx.bpdm.pool.api.model.response.ErrorInfo -import org.eclipse.tractusx.bpdm.pool.api.model.response.LegalEntityMatchResponse import org.eclipse.tractusx.bpdm.pool.api.model.response.SyncResponse -import org.eclipse.tractusx.bpdm.pool.component.saas.config.SaasAdapterConfigProperties import org.eclipse.tractusx.bpdm.pool.config.BpnConfigProperties import org.junit.Assert import org.junit.jupiter.api.assertThrows @@ -58,8 +46,6 @@ private const val BPDM_DB_SCHEMA_NAME: String = "bpdm" @Component class TestHelpers( entityManagerFactory: EntityManagerFactory, - private val objectMapper: ObjectMapper, - private val saasAdapterConfigProperties: SaasAdapterConfigProperties, private val bpnConfigProperties: BpnConfigProperties, private val poolClient: PoolClientImpl ) { @@ -237,34 +223,7 @@ class TestHelpers( return syncResponse } - fun importAndGetResponse( - partnersToImport: Collection, - client: WebTestClient, - wireMockServer: WireMockExtension - ): PageResponse { - val importCollection = PagedResponseSaas( - partnersToImport.size, - null, - null, - partnersToImport.size, - partnersToImport - ) - - wireMockServer.stubFor( - WireMock.get(WireMock.urlPathMatching(saasAdapterConfigProperties.readBusinessPartnerUrl)).willReturn( - WireMock.aResponse() - .withHeader("Content-Type", "application/json") - .withBody(objectMapper.writeValueAsString(importCollection)) - ) - ) - - startSyncAndAwaitSuccess(client, EndpointValues.SAAS_SYNCH_PATH) - return poolClient.legalEntities().getLegalEntities( - LegalEntityPropertiesSearchRequest.EmptySearchRequest, AddressPropertiesSearchRequest.EmptySearchRequest, - SitePropertiesSearchRequest.EmptySearchRequest, PaginationRequest() - ) - } fun assertRecursively(actual: T): RecursiveComparisonAssert<*> { return Assertions.assertThat(actual)